--- /dev/null
+CONFIG_AHCI_MVEBU=y
+CONFIG_ALIGNMENT_TRAP=y
+CONFIG_ARCH_HAS_ATOMIC64_DEC_IF_POSITIVE=y
+CONFIG_ARCH_HAS_ELF_RANDOMIZE=y
+CONFIG_ARCH_HAS_GCOV_PROFILE_ALL=y
+CONFIG_ARCH_HAS_SG_CHAIN=y
+CONFIG_ARCH_HAS_TICK_BROADCAST=y
+CONFIG_ARCH_HAVE_CUSTOM_GPIO_H=y
+CONFIG_ARCH_HIBERNATION_POSSIBLE=y
+CONFIG_ARCH_MIGHT_HAVE_PC_PARPORT=y
+CONFIG_ARCH_MULTIPLATFORM=y
+# CONFIG_ARCH_MULTI_CPU_AUTO is not set
+CONFIG_ARCH_MULTI_V6_V7=y
+CONFIG_ARCH_MULTI_V7=y
+CONFIG_ARCH_MVEBU=y
+CONFIG_ARCH_NR_GPIO=0
+CONFIG_ARCH_REQUIRE_GPIOLIB=y
+# CONFIG_ARCH_SELECT_MEMORY_MODEL is not set
+# CONFIG_ARCH_SPARSEMEM_DEFAULT is not set
+CONFIG_ARCH_SUPPORTS_ATOMIC_RMW=y
+CONFIG_ARCH_SUPPORTS_BIG_ENDIAN=y
+CONFIG_ARCH_SUPPORTS_UPROBES=y
+CONFIG_ARCH_SUSPEND_POSSIBLE=y
+CONFIG_ARCH_USE_BUILTIN_BSWAP=y
+CONFIG_ARCH_USE_CMPXCHG_LOCKREF=y
+CONFIG_ARCH_WANT_GENERAL_HUGETLB=y
+CONFIG_ARCH_WANT_IPC_PARSE_VERSION=y
+CONFIG_ARCH_WANT_OPTIONAL_GPIOLIB=y
+CONFIG_ARM=y
+CONFIG_ARMADA_370_CLK=y
+CONFIG_ARMADA_370_XP_TIMER=y
+CONFIG_ARMADA_38X_CLK=y
+CONFIG_ARMADA_THERMAL=y
+CONFIG_ARMADA_XP_CLK=y
+CONFIG_ARM_APPENDED_DTB=y
+CONFIG_ARM_ATAG_DTB_COMPAT=y
+# CONFIG_ARM_ATAG_DTB_COMPAT_CMDLINE_EXTEND is not set
+CONFIG_ARM_ATAG_DTB_COMPAT_CMDLINE_FROM_BOOTLOADER=y
+CONFIG_ARM_CPU_SUSPEND=y
+CONFIG_ARM_CRYPTO=y
+CONFIG_ARM_ERRATA_720789=y
+CONFIG_ARM_GIC=y
+CONFIG_ARM_HAS_SG_CHAIN=y
+CONFIG_ARM_HEAVY_MB=y
+CONFIG_ARM_L1_CACHE_SHIFT=6
+CONFIG_ARM_L1_CACHE_SHIFT_6=y
+# CONFIG_ARM_LPAE is not set
+CONFIG_ARM_MVEBU_V7_CPUIDLE=y
+CONFIG_ARM_PATCH_PHYS_VIRT=y
+CONFIG_ARM_THUMB=y
+# CONFIG_ARM_THUMBEE is not set
+CONFIG_ARM_UNWIND=y
+CONFIG_ARM_VIRT_EXT=y
+CONFIG_ASYNC_TX_ENABLE_CHANNEL_SWITCH=y
+CONFIG_ATA=y
+CONFIG_ATAGS=y
+CONFIG_AUTO_ZRELADDR=y
+CONFIG_BLK_DEV_LOOP=y
+CONFIG_BOUNCE=y
+# CONFIG_CACHE_FEROCEON_L2 is not set
+CONFIG_CACHE_L2X0=y
+CONFIG_CLKDEV_LOOKUP=y
+CONFIG_CLKSRC_MMIO=y
+CONFIG_CLKSRC_OF=y
+CONFIG_CLKSRC_PROBE=y
+CONFIG_CLONE_BACKWARDS=y
+CONFIG_COMMON_CLK=y
+CONFIG_CPUFREQ_DT=y
+CONFIG_CPU_32v6K=y
+CONFIG_CPU_32v7=y
+CONFIG_CPU_ABRT_EV7=y
+# CONFIG_CPU_BIG_ENDIAN is not set
+# CONFIG_CPU_BPREDICT_DISABLE is not set
+CONFIG_CPU_CACHE_V7=y
+CONFIG_CPU_CACHE_VIPT=y
+CONFIG_CPU_COPY_V6=y
+CONFIG_CPU_CP15=y
+CONFIG_CPU_CP15_MMU=y
+CONFIG_CPU_FREQ=y
+CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND=y
+# CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE is not set
+CONFIG_CPU_FREQ_GOV_COMMON=y
+# CONFIG_CPU_FREQ_GOV_CONSERVATIVE is not set
+CONFIG_CPU_FREQ_GOV_ONDEMAND=y
+CONFIG_CPU_FREQ_GOV_PERFORMANCE=y
+# CONFIG_CPU_FREQ_GOV_POWERSAVE is not set
+# CONFIG_CPU_FREQ_GOV_USERSPACE is not set
+CONFIG_CPU_FREQ_STAT=y
+CONFIG_CPU_HAS_ASID=y
+# CONFIG_CPU_ICACHE_DISABLE is not set
+CONFIG_CPU_IDLE=y
+CONFIG_CPU_IDLE_GOV_LADDER=y
+CONFIG_CPU_PABRT_V7=y
+CONFIG_CPU_PJ4B=y
+CONFIG_CPU_PM=y
+CONFIG_CPU_RMAP=y
+CONFIG_CPU_THERMAL=y
+CONFIG_CPU_TLB_V7=y
+CONFIG_CPU_V7=y
+CONFIG_CRC16=y
+CONFIG_CRYPTO_ABLK_HELPER=y
+CONFIG_CRYPTO_AEAD=y
+CONFIG_CRYPTO_AEAD2=y
+CONFIG_CRYPTO_AES_ARM=y
+CONFIG_CRYPTO_AES_ARM_BS=y
+# CONFIG_CRYPTO_AES_ARM_CE is not set
+CONFIG_CRYPTO_CRC32C=y
+CONFIG_CRYPTO_CRYPTD=y
+CONFIG_CRYPTO_DEFLATE=y
+CONFIG_CRYPTO_DES=y
+CONFIG_CRYPTO_DEV_MARVELL_CESA=y
+# CONFIG_CRYPTO_GHASH_ARM_CE is not set
+CONFIG_CRYPTO_HASH=y
+CONFIG_CRYPTO_HASH2=y
+CONFIG_CRYPTO_HW=y
+CONFIG_CRYPTO_LZO=y
+CONFIG_CRYPTO_MANAGER=y
+CONFIG_CRYPTO_MANAGER2=y
+CONFIG_CRYPTO_NULL2=y
+CONFIG_CRYPTO_RNG2=y
+CONFIG_CRYPTO_SHA1=y
+CONFIG_CRYPTO_SHA1_ARM=y
+# CONFIG_CRYPTO_SHA1_ARM_CE is not set
+CONFIG_CRYPTO_SHA1_ARM_NEON=y
+CONFIG_CRYPTO_SHA256_ARM=y
+# CONFIG_CRYPTO_SHA2_ARM_CE is not set
+CONFIG_CRYPTO_SHA512_ARM=y
+CONFIG_CRYPTO_WORKQUEUE=y
+CONFIG_DCACHE_WORD_ACCESS=y
+CONFIG_DEBUG_INFO=y
+CONFIG_DEBUG_LL=y
+CONFIG_DEBUG_LL_INCLUDE="debug/8250.S"
+CONFIG_DEBUG_MVEBU_UART0=y
+# CONFIG_DEBUG_MVEBU_UART0_ALTERNATE is not set
+# CONFIG_DEBUG_MVEBU_UART1_ALTERNATE is not set
+CONFIG_DEBUG_UART_8250=y
+# CONFIG_DEBUG_UART_8250_FLOW_CONTROL is not set
+CONFIG_DEBUG_UART_8250_SHIFT=2
+# CONFIG_DEBUG_UART_8250_WORD is not set
+CONFIG_DEBUG_UART_PHYS=0xd0012000
+CONFIG_DEBUG_UART_VIRT=0xfec12000
+CONFIG_DEBUG_UNCOMPRESS=y
+CONFIG_DEBUG_USER=y
+CONFIG_DMADEVICES=y
+CONFIG_DMA_ENGINE=y
+CONFIG_DMA_ENGINE_RAID=y
+CONFIG_DMA_OF=y
+CONFIG_DTC=y
+# CONFIG_DW_DMAC_PCI is not set
+CONFIG_EARLY_PRINTK=y
+CONFIG_EDAC_ATOMIC_SCRUB=y
+CONFIG_EDAC_SUPPORT=y
+CONFIG_EXT4_FS=y
+# CONFIG_F2FS_CHECK_FS is not set
+CONFIG_F2FS_FS=y
+# CONFIG_F2FS_FS_SECURITY is not set
+CONFIG_F2FS_FS_XATTR=y
+CONFIG_F2FS_STAT_FS=y
+CONFIG_FIXED_PHY=y
+CONFIG_FIX_EARLYCON_MEM=y
+CONFIG_FS_MBCACHE=y
+CONFIG_GENERIC_ALLOCATOR=y
+CONFIG_GENERIC_BUG=y
+CONFIG_GENERIC_CLOCKEVENTS=y
+CONFIG_GENERIC_CLOCKEVENTS_BROADCAST=y
+CONFIG_GENERIC_IDLE_POLL_SETUP=y
+CONFIG_GENERIC_IO=y
+CONFIG_GENERIC_IRQ_CHIP=y
+CONFIG_GENERIC_IRQ_SHOW=y
+CONFIG_GENERIC_IRQ_SHOW_LEVEL=y
+CONFIG_GENERIC_PCI_IOMAP=y
+CONFIG_GENERIC_PHY=y
+CONFIG_GENERIC_SCHED_CLOCK=y
+CONFIG_GENERIC_SMP_IDLE_THREAD=y
+CONFIG_GENERIC_STRNCPY_FROM_USER=y
+CONFIG_GENERIC_STRNLEN_USER=y
+CONFIG_GLOB=y
+CONFIG_GPIOLIB=y
+CONFIG_GPIO_DEVRES=y
+CONFIG_GPIO_MVEBU=y
+CONFIG_GPIO_MVEBU_PWM=y
+CONFIG_GPIO_PCA953X=y
+# CONFIG_GPIO_PCA953X_IRQ is not set
+CONFIG_GPIO_SYSFS=y
+CONFIG_HANDLE_DOMAIN_IRQ=y
+CONFIG_HARDIRQS_SW_RESEND=y
+CONFIG_HAS_DMA=y
+CONFIG_HAS_IOMEM=y
+CONFIG_HAS_IOPORT_MAP=y
+# CONFIG_HAVE_64BIT_ALIGNED_ACCESS is not set
+CONFIG_HAVE_ARCH_AUDITSYSCALL=y
+CONFIG_HAVE_ARCH_BITREVERSE=y
+CONFIG_HAVE_ARCH_JUMP_LABEL=y
+CONFIG_HAVE_ARCH_KGDB=y
+CONFIG_HAVE_ARCH_PFN_VALID=y
+CONFIG_HAVE_ARCH_SECCOMP_FILTER=y
+CONFIG_HAVE_ARCH_TRACEHOOK=y
+CONFIG_HAVE_ARM_SCU=y
+CONFIG_HAVE_ARM_TWD=y
+# CONFIG_HAVE_BOOTMEM_INFO_NODE is not set
+CONFIG_HAVE_BPF_JIT=y
+CONFIG_HAVE_CC_STACKPROTECTOR=y
+CONFIG_HAVE_CLK=y
+CONFIG_HAVE_CLK_PREPARE=y
+CONFIG_HAVE_CONTEXT_TRACKING=y
+CONFIG_HAVE_C_RECORDMCOUNT=y
+CONFIG_HAVE_DEBUG_KMEMLEAK=y
+CONFIG_HAVE_DMA_API_DEBUG=y
+CONFIG_HAVE_DMA_ATTRS=y
+CONFIG_HAVE_DMA_CONTIGUOUS=y
+CONFIG_HAVE_DYNAMIC_FTRACE=y
+CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS=y
+CONFIG_HAVE_FTRACE_MCOUNT_RECORD=y
+CONFIG_HAVE_FUNCTION_GRAPH_TRACER=y
+CONFIG_HAVE_FUNCTION_TRACER=y
+CONFIG_HAVE_GENERIC_DMA_COHERENT=y
+CONFIG_HAVE_IDE=y
+CONFIG_HAVE_IRQ_TIME_ACCOUNTING=y
+CONFIG_HAVE_MEMBLOCK=y
+CONFIG_HAVE_MOD_ARCH_SPECIFIC=y
+CONFIG_HAVE_NET_DSA=y
+CONFIG_HAVE_OPROFILE=y
+CONFIG_HAVE_OPTPROBES=y
+CONFIG_HAVE_PERF_EVENTS=y
+CONFIG_HAVE_PERF_REGS=y
+CONFIG_HAVE_PERF_USER_STACK_DUMP=y
+CONFIG_HAVE_PROC_CPU=y
+CONFIG_HAVE_REGS_AND_STACK_ACCESS_API=y
+CONFIG_HAVE_SMP=y
+CONFIG_HAVE_SYSCALL_TRACEPOINTS=y
+CONFIG_HAVE_UID16=y
+CONFIG_HAVE_VIRT_CPU_ACCOUNTING_GEN=y
+CONFIG_HIGHMEM=y
+# CONFIG_HIGHPTE is not set
+CONFIG_HOTPLUG_CPU=y
+CONFIG_HWBM=y
+CONFIG_HWMON=y
+CONFIG_HZ_FIXED=0
+CONFIG_HZ_PERIODIC=y
+CONFIG_I2C=y
+CONFIG_I2C_BOARDINFO=y
+CONFIG_I2C_CHARDEV=y
+CONFIG_I2C_MV64XXX=y
+CONFIG_INITRAMFS_SOURCE=""
+CONFIG_IOMMU_HELPER=y
+CONFIG_IRQCHIP=y
+CONFIG_IRQ_DOMAIN=y
+CONFIG_IRQ_DOMAIN_DEBUG=y
+CONFIG_IRQ_DOMAIN_HIERARCHY=y
+CONFIG_IRQ_FORCED_THREADING=y
+CONFIG_IRQ_WORK=y
+# CONFIG_IWMMXT is not set
+CONFIG_JBD2=y
+CONFIG_LEDS_GPIO=y
+CONFIG_LEDS_PCA963X=y
+CONFIG_LEDS_TLC591XX=y
+CONFIG_LIBFDT=y
+CONFIG_LOCK_SPIN_ON_OWNER=y
+CONFIG_LZO_COMPRESS=y
+CONFIG_LZO_DECOMPRESS=y
+CONFIG_MACH_ARMADA_370=y
+# CONFIG_MACH_ARMADA_375 is not set
+CONFIG_MACH_ARMADA_38X=y
+# CONFIG_MACH_ARMADA_39X is not set
+CONFIG_MACH_ARMADA_XP=y
+# CONFIG_MACH_DOVE is not set
+CONFIG_MACH_MVEBU_ANY=y
+CONFIG_MACH_MVEBU_V7=y
+CONFIG_MAGIC_SYSRQ=y
+CONFIG_MANGLE_BOOTARGS=y
+CONFIG_MARVELL_PHY=y
+CONFIG_MDIO_BOARDINFO=y
+CONFIG_MDIO_I2C=y
+CONFIG_MEMORY=y
+CONFIG_MIGHT_HAVE_CACHE_L2X0=y
+CONFIG_MIGHT_HAVE_PCI=y
+CONFIG_MMC=y
+CONFIG_MMC_BLOCK=y
+CONFIG_MMC_MVSDIO=y
+CONFIG_MMC_SDHCI=y
+# CONFIG_MMC_SDHCI_PCI is not set
+CONFIG_MMC_SDHCI_PLTFM=y
+CONFIG_MMC_SDHCI_PXAV3=y
+# CONFIG_MMC_TIFM_SD is not set
+CONFIG_MODULES_USE_ELF_REL=y
+CONFIG_MTD_CFI_STAA=y
+CONFIG_MTD_M25P80=y
+CONFIG_MTD_NAND=y
+CONFIG_MTD_NAND_ECC=y
+CONFIG_MTD_NAND_PXA3xx=y
+CONFIG_MTD_SPI_NOR=y
+CONFIG_MTD_SPLIT_FIRMWARE=y
+CONFIG_MTD_UBI=y
+CONFIG_MTD_UBI_BEB_LIMIT=20
+CONFIG_MTD_UBI_BLOCK=y
+# CONFIG_MTD_UBI_FASTMAP is not set
+# CONFIG_MTD_UBI_GLUEBI is not set
+CONFIG_MTD_UBI_WL_THRESHOLD=4096
+CONFIG_MULTI_IRQ_HANDLER=y
+CONFIG_MUTEX_SPIN_ON_OWNER=y
+CONFIG_MVEBU_CLK_COMMON=y
+CONFIG_MVEBU_CLK_COREDIV=y
+CONFIG_MVEBU_CLK_CPU=y
+CONFIG_MVEBU_DEVBUS=y
+CONFIG_MVEBU_MBUS=y
+CONFIG_MVMDIO=y
+CONFIG_MVNETA=y
+CONFIG_MVNETA_BM=y
+CONFIG_MVSW61XX_PHY=y
+CONFIG_MV_XOR=y
+CONFIG_NEED_DMA_MAP_STATE=y
+CONFIG_NEON=y
+CONFIG_NET_FLOW_LIMIT=y
+CONFIG_NLS=y
+CONFIG_NOP_USB_XCEIV=y
+CONFIG_NO_BOOTMEM=y
+CONFIG_NR_CPUS=4
+CONFIG_OF=y
+CONFIG_OF_ADDRESS=y
+CONFIG_OF_ADDRESS_PCI=y
+CONFIG_OF_EARLY_FLATTREE=y
+CONFIG_OF_FLATTREE=y
+CONFIG_OF_GPIO=y
+CONFIG_OF_IRQ=y
+CONFIG_OF_MDIO=y
+CONFIG_OF_MTD=y
+CONFIG_OF_NET=y
+CONFIG_OF_PCI=y
+CONFIG_OF_PCI_IRQ=y
+CONFIG_OF_RESERVED_MEM=y
+CONFIG_OLD_SIGACTION=y
+CONFIG_OLD_SIGSUSPEND3=y
+CONFIG_ORION_WATCHDOG=y
+CONFIG_OUTER_CACHE=y
+CONFIG_OUTER_CACHE_SYNC=y
+CONFIG_PAGE_OFFSET=0xC0000000
+CONFIG_PCI=y
+# CONFIG_PCI_DOMAINS_GENERIC is not set
+CONFIG_PCI_MVEBU=y
+CONFIG_PERF_USE_VMALLOC=y
+CONFIG_PGTABLE_LEVELS=2
+CONFIG_PHYLIB=y
+CONFIG_PHYLINK=y
+CONFIG_PINCTRL=y
+CONFIG_PINCTRL_ARMADA_370=y
+CONFIG_PINCTRL_ARMADA_38X=y
+CONFIG_PINCTRL_ARMADA_XP=y
+CONFIG_PINCTRL_MVEBU=y
+# CONFIG_PINCTRL_SINGLE is not set
+CONFIG_PJ4B_ERRATA_4742=y
+# CONFIG_PL310_ERRATA_588369 is not set
+# CONFIG_PL310_ERRATA_727915 is not set
+# CONFIG_PL310_ERRATA_753970 is not set
+# CONFIG_PL310_ERRATA_769419 is not set
+CONFIG_PLAT_ORION=y
+CONFIG_PM_OPP=y
+CONFIG_PWM=y
+CONFIG_PWM_SYSFS=y
+CONFIG_RATIONAL=y
+CONFIG_RCU_STALL_COMMON=y
+CONFIG_REGMAP=y
+CONFIG_REGMAP_I2C=y
+CONFIG_REGULATOR=y
+CONFIG_REGULATOR_FIXED_VOLTAGE=y
+CONFIG_RFS_ACCEL=y
+CONFIG_RPS=y
+CONFIG_RTC_CLASS=y
+CONFIG_RTC_DRV_ARMADA38X=y
+CONFIG_RTC_DRV_MV=y
+CONFIG_RWSEM_SPIN_ON_OWNER=y
+CONFIG_RWSEM_XCHGADD_ALGORITHM=y
+CONFIG_SATA_AHCI_PLATFORM=y
+CONFIG_SATA_MV=y
+CONFIG_SCHED_HRTICK=y
+# CONFIG_SCHED_INFO is not set
+CONFIG_SCSI=y
+CONFIG_SENSORS_PWM_FAN=y
+CONFIG_SENSORS_TMP421=y
+CONFIG_SERIAL_8250_DW=y
+CONFIG_SERIAL_8250_FSL=y
+CONFIG_SFP=y
+CONFIG_SMP=y
+CONFIG_SMP_ON_UP=y
+CONFIG_SOC_BUS=y
+CONFIG_SPARSE_IRQ=y
+CONFIG_SPI=y
+CONFIG_SPI_MASTER=y
+CONFIG_SPI_ORION=y
+CONFIG_SRAM=y
+CONFIG_SRCU=y
+CONFIG_SWCONFIG=y
+CONFIG_SWIOTLB=y
+CONFIG_SWPHY=y
+CONFIG_SWP_EMULATE=y
+CONFIG_SYS_SUPPORTS_APM_EMULATION=y
+CONFIG_THERMAL=y
+CONFIG_THERMAL_DEFAULT_GOV_STEP_WISE=y
+CONFIG_THERMAL_GOV_STEP_WISE=y
+CONFIG_THERMAL_HWMON=y
+CONFIG_THERMAL_OF=y
+# CONFIG_THUMB2_KERNEL is not set
+CONFIG_TICK_CPU_ACCOUNTING=y
+CONFIG_TIMER_STATS=y
+CONFIG_TREE_RCU=y
+CONFIG_UBIFS_FS=y
+# CONFIG_UBIFS_FS_ADVANCED_COMPR is not set
+CONFIG_UBIFS_FS_LZO=y
+CONFIG_UBIFS_FS_ZLIB=y
+CONFIG_UNCOMPRESS_INCLUDE="debug/uncompress.h"
+CONFIG_USB=y
+CONFIG_USB_COMMON=y
+CONFIG_USB_EHCI_HCD=y
+CONFIG_USB_EHCI_HCD_ORION=y
+CONFIG_USB_EHCI_HCD_PLATFORM=y
+CONFIG_USB_EHCI_PCI=y
+CONFIG_USB_LEDS_TRIGGER_USBPORT=y
+CONFIG_USB_PHY=y
+CONFIG_USB_STORAGE=y
+CONFIG_USB_SUPPORT=y
+# CONFIG_USB_UHCI_HCD is not set
+CONFIG_USB_XHCI_HCD=y
+CONFIG_USB_XHCI_MVEBU=y
+CONFIG_USB_XHCI_PCI=y
+CONFIG_USB_XHCI_PLATFORM=y
+CONFIG_USE_OF=y
+CONFIG_VECTORS_BASE=0xffff0000
+CONFIG_VFP=y
+CONFIG_VFPv3=y
+CONFIG_WATCHDOG_CORE=y
+CONFIG_XPS=y
+CONFIG_XZ_DEC_ARM=y
+CONFIG_XZ_DEC_BCJ=y
+CONFIG_ZBOOT_ROM_BSS=0x0
+CONFIG_ZBOOT_ROM_TEXT=0x0
+CONFIG_ZLIB_DEFLATE=y
+CONFIG_ZLIB_INFLATE=y
+CONFIG_ZONE_DMA_FLAG=0
--- /dev/null
+--- a/arch/arm/boot/dts/armada-xp-linksys-mamba.dts
++++ b/arch/arm/boot/dts/armada-xp-linksys-mamba.dts
+@@ -86,12 +86,100 @@
+ pcie@2,0 {
+ /* Port 0, Lane 1 */
+ status = "okay";
++
++ mwlwifi {
++ marvell,5ghz = <0>;
++ marvell,chainmask = <4 4>;
++ marvell,powertable {
++ FCC =
++ <1 0 0x17 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0xf 0xf 0xf 0xf 0x0 0x0 0x0 0x0 0 0xf>,
++ <2 0 0x17 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x10 0x10 0x10 0x10 0x0 0x0 0x0 0x0 0 0xf>,
++ <3 0 0x17 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x10 0x10 0x10 0x10 0x0 0x0 0x0 0x0 0 0xf>,
++ <4 0 0x17 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x10 0x10 0x10 0x10 0x0 0x0 0x0 0x0 0 0xf>,
++ <5 0 0x17 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x10 0x10 0x10 0x10 0x0 0x0 0x0 0x0 0 0xf>,
++ <6 0 0x17 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x10 0x10 0x10 0x10 0x0 0x0 0x0 0x0 0 0xf>,
++ <7 0 0x17 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x10 0x10 0x10 0x10 0x0 0x0 0x0 0x0 0 0xf>,
++ <8 0 0x17 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x10 0x10 0x10 0x10 0x0 0x0 0x0 0x0 0 0xf>,
++ <9 0 0x17 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x10 0x10 0x10 0x10 0x0 0x0 0x0 0x0 0 0xf>,
++ <10 0 0x17 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x10 0x10 0x10 0x10 0x0 0x0 0x0 0x0 0 0xf>,
++ <11 0 0x17 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x0 0x0 0x0 0x0 0 0xf>;
++
++ ETSI =
++ <1 0 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0x0 0x0 0x0 0x0 0 0xf>,
++ <2 0 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0x0 0x0 0x0 0x0 0 0xf>,
++ <3 0 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0x0 0x0 0x0 0x0 0 0xf>,
++ <4 0 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0x0 0x0 0x0 0x0 0 0xf>,
++ <5 0 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0x0 0x0 0x0 0x0 0 0xf>,
++ <6 0 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0x0 0x0 0x0 0x0 0 0xf>,
++ <7 0 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0x0 0x0 0x0 0x0 0 0xf>,
++ <8 0 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0x0 0x0 0x0 0x0 0 0xf>,
++ <9 0 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0x0 0x0 0x0 0x0 0 0xf>,
++ <10 0 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0x0 0x0 0x0 0x0 0 0xf>,
++ <11 0 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0x0 0x0 0x0 0x0 0 0xf>,
++ <12 0 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0x0 0x0 0x0 0x0 0 0xf>,
++ <13 0 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0x0 0x0 0x0 0x0 0 0xf>;
++ };
++ };
+ };
+
+ /* Second mini-PCIe port */
+ pcie@3,0 {
+ /* Port 0, Lane 3 */
+ status = "okay";
++
++ mwlwifi {
++ marvell,2ghz = <0>;
++ marvell,chainmask = <4 4>;
++ marvell,powertable {
++ FCC =
++ <36 0 0x8 0x8 0x8 0x8 0x8 0x8 0x8 0x8 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0 0xf>,
++ <40 0 0x8 0x8 0x8 0x8 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0 0xf>,
++ <44 0 0x8 0x8 0x8 0x8 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0 0xf>,
++ <48 0 0x8 0x8 0x8 0x8 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0 0xf>,
++ <52 0 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0x12 0x12 0x12 0x12 0x12 0x12 0x12 0x12 0 0xf>,
++ <56 0 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0x12 0x12 0x12 0x12 0x12 0x12 0x12 0x12 0 0xf>,
++ <60 0 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0x12 0x12 0x12 0x12 0x12 0x12 0x12 0x12 0 0xf>,
++ <64 0 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0x12 0x12 0x12 0x12 0x12 0x12 0x12 0x12 0 0xf>,
++ <100 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>,
++ <104 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>,
++ <108 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>,
++ <112 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>,
++ <116 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>,
++ <120 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>,
++ <124 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>,
++ <128 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>,
++ <132 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>,
++ <136 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>,
++ <140 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>,
++ <149 0 0x16 0x16 0x16 0x16 0x14 0x14 0x14 0x14 0x15 0x15 0x15 0x15 0x14 0x14 0x14 0x14 0 0xf>,
++ <153 0 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x14 0x14 0x14 0x14 0 0xf>,
++ <157 0 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x14 0x14 0x14 0x14 0 0xf>,
++ <161 0 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x14 0x14 0x14 0x14 0 0xf>,
++ <165 0 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x14 0x14 0x14 0x14 0 0xf>;
++
++ ETSI =
++ <36 0 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xd 0xd 0xd 0xd 0xc 0xc 0xc 0xc 0 0xf>,
++ <40 0 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xd 0xd 0xd 0xd 0xc 0xc 0xc 0xc 0 0xf>,
++ <44 0 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xd 0xd 0xd 0xd 0xc 0xc 0xc 0xc 0 0xf>,
++ <48 0 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xd 0xd 0xd 0xd 0xc 0xc 0xc 0xc 0 0xf>,
++ <52 0 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xd 0xd 0xd 0xd 0xc 0xc 0xc 0xc 0 0xf>,
++ <56 0 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xd 0xd 0xd 0xd 0xc 0xc 0xc 0xc 0 0xf>,
++ <60 0 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xd 0xd 0xd 0xd 0xc 0xc 0xc 0xc 0 0xf>,
++ <64 0 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xd 0xd 0xd 0xd 0xc 0xc 0xc 0xc 0 0xf>,
++ <100 0 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xd 0xd 0xd 0xd 0xc 0xc 0xc 0xc 0 0xf>,
++ <104 0 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xd 0xd 0xd 0xd 0xc 0xc 0xc 0xc 0 0xf>,
++ <108 0 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xd 0xd 0xd 0xd 0xc 0xc 0xc 0xc 0 0xf>,
++ <112 0 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xd 0xd 0xd 0xd 0xc 0xc 0xc 0xc 0 0xf>,
++ <116 0 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xd 0xd 0xd 0xd 0xc 0xc 0xc 0xc 0 0xf>,
++ <120 0 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xd 0xd 0xd 0xd 0xc 0xc 0xc 0xc 0 0xf>,
++ <124 0 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xd 0xd 0xd 0xd 0xc 0xc 0xc 0xc 0 0xf>,
++ <128 0 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xd 0xd 0xd 0xd 0xc 0xc 0xc 0xc 0 0xf>,
++ <132 0 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xd 0xd 0xd 0xd 0xc 0xc 0xc 0xc 0 0xf>,
++ <136 0 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xd 0xd 0xd 0xd 0xc 0xc 0xc 0xc 0 0xf>,
++ <140 0 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xd 0xd 0xd 0xd 0xc 0xc 0xc 0xc 0 0xf>,
++ <149 0 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xd 0xd 0xd 0xd 0xc 0xc 0xc 0xc 0 0xf>;
++ };
++ };
+ };
+ };
+
+--- a/arch/arm/boot/dts/armada-385-linksys-cobra.dts
++++ b/arch/arm/boot/dts/armada-385-linksys-cobra.dts
+@@ -100,6 +100,212 @@
+ };
+ };
+ };
++
++ pcie-controller {
++ pcie@1,0 {
++ mwlwifi {
++ marvell,2ghz = <0>;
++ marvell,chainmask = <4 4>;
++ marvell,powertable {
++ AU =
++ <36 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <40 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <44 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <48 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <52 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <56 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <60 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <64 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <100 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>,
++ <104 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>,
++ <108 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>,
++ <112 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>,
++ <116 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>,
++ <120 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>,
++ <124 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>,
++ <128 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>,
++ <132 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>,
++ <136 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>,
++ <140 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>,
++ <149 0 0x19 0x19 0x19 0x17 0x19 0x19 0x16 0x15 0x19 0x19 0x16 0x15 0x19 0x19 0x16 0x15 0 0xf>,
++ <153 0 0x19 0x19 0x19 0x17 0x19 0x19 0x16 0x15 0x19 0x19 0x16 0x15 0x19 0x19 0x16 0x15 0 0xf>,
++ <157 0 0x19 0x19 0x19 0x17 0x19 0x19 0x16 0x15 0x19 0x19 0x16 0x15 0x19 0x19 0x16 0x15 0 0xf>,
++ <161 0 0x19 0x19 0x19 0x17 0x19 0x19 0x16 0x15 0x19 0x19 0x16 0x15 0x19 0x19 0x16 0x15 0 0xf>,
++ <165 0 0x19 0x19 0x19 0x17 0x19 0x19 0x16 0x15 0x19 0x19 0x16 0x15 0x19 0x19 0x16 0x15 0 0xf>;
++ CA =
++ <36 0 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0 0xf>,
++ <40 0 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0 0xf>,
++ <44 0 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0 0xf>,
++ <48 0 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0 0xf>,
++ <52 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>,
++ <56 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>,
++ <60 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>,
++ <64 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>,
++ <100 0 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>,
++ <104 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>,
++ <108 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <112 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <116 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <120 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <124 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <128 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <132 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <136 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <140 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <149 0 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0 0xf>,
++ <153 0 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0 0xf>,
++ <157 0 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0 0xf>,
++ <161 0 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0 0xf>,
++ <165 0 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0 0xf>;
++ CN =
++ <36 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <40 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <44 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <48 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <52 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <56 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <60 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <64 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <100 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <104 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <108 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <112 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <116 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <120 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <124 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <128 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <132 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <136 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <140 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <149 0 0x14 0x14 0x14 0x14 0x13 0x13 0x13 0x13 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <153 0 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x14 0x14 0x14 0x14 0x10 0x10 0x10 0x10 0 0xf>,
++ <157 0 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x14 0x14 0x14 0x14 0x10 0x10 0x10 0x10 0 0xf>,
++ <161 0 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x14 0x14 0x14 0x14 0x10 0x10 0x10 0x10 0 0xf>,
++ <165 0 0x13 0x13 0x13 0x13 0x13 0x13 0x13 0x13 0x14 0x14 0x14 0x14 0x10 0x10 0x10 0x10 0 0xf>;
++ ETSI =
++ <36 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <40 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <44 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <48 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <52 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <56 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <60 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <64 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <100 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <104 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <108 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <112 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <116 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <120 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <124 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <128 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <132 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <136 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <140 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <149 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>;
++ FCC =
++ <36 0 0x12 0x12 0x12 0x12 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0xf 0xf 0xf 0xf 0 0xf>,
++ <40 0 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0xf 0xf 0xf 0xf 0 0xf>,
++ <44 0 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0xf 0xf 0xf 0xf 0 0xf>,
++ <48 0 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0xf 0xf 0xf 0xf 0 0xf>,
++ <52 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>,
++ <56 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>,
++ <60 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>,
++ <64 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>,
++ <100 0 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>,
++ <104 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>,
++ <108 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <112 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <116 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <120 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <124 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <128 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <132 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <136 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <140 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <149 0 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0 0xf>,
++ <153 0 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0 0xf>,
++ <157 0 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0 0xf>,
++ <161 0 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0 0xf>,
++ <165 0 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0 0xf>;
++ };
++ };
++ };
++
++ pcie@2,0 {
++ mwlwifi {
++ marvell,5ghz = <0>;
++ marvell,chainmask = <4 4>;
++ marvell,powertable {
++ AU =
++ <1 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <2 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <3 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <4 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <5 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <6 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <7 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <8 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <9 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <10 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <11 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>;
++ CA =
++ <1 0 0x17 0x10 0x10 0x10 0xf 0xf 0xf 0xf 0xe 0xe 0xe 0xe 0x0 0x0 0x0 0x0 0 0xf>,
++ <2 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>,
++ <3 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>,
++ <4 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>,
++ <5 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>,
++ <6 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>,
++ <7 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>,
++ <8 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>,
++ <9 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>,
++ <10 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>,
++ <11 0 0x17 0x12 0x12 0x12 0x13 0x13 0x13 0x13 0xf 0xf 0xf 0xf 0x0 0x0 0x0 0x0 0 0xf>;
++ CN =
++ <1 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <2 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <3 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <4 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <5 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <6 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <7 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <8 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <9 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <10 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <11 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <12 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <13 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <14 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>;
++ ETSI =
++ <1 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <2 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <3 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <4 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <5 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <6 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <7 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <8 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <9 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <10 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <11 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <12 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <13 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>;
++ FCC =
++ <1 0 0x17 0x10 0x10 0x10 0xf 0xf 0xf 0xf 0xe 0xe 0xe 0xe 0x0 0x0 0x0 0x0 0 0xf>,
++ <2 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>,
++ <3 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>,
++ <4 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>,
++ <5 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>,
++ <6 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>,
++ <7 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>,
++ <8 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>,
++ <9 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>,
++ <10 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>,
++ <11 0 0x17 0x12 0x12 0x12 0x13 0x13 0x13 0x13 0xf 0xf 0xf 0xf 0x0 0x0 0x0 0x0 0 0xf>;
++ };
++ };
++ };
++ };
+ };
+
+ gpio-leds {
+--- a/arch/arm/boot/dts/armada-385-linksys-caiman.dts
++++ b/arch/arm/boot/dts/armada-385-linksys-caiman.dts
+@@ -100,6 +100,212 @@
+ };
+ };
+ };
++
++ pcie-controller {
++ pcie@1,0 {
++ mwlwifi {
++ marvell,2ghz = <0>;
++ marvell,chainmask = <2 2>;
++ marvell,powertable {
++ AU =
++ <36 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <40 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <44 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <48 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <52 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <56 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <60 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <64 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <100 0 0x17 0x17 0x17 0x17 0x17 0x17 0x17 0x15 0x17 0x17 0x17 0x14 0x17 0x17 0x17 0x14 0 0xf>,
++ <104 0 0x17 0x17 0x17 0x17 0x17 0x17 0x17 0x15 0x17 0x17 0x17 0x14 0x17 0x17 0x17 0x14 0 0xf>,
++ <108 0 0x17 0x17 0x17 0x17 0x17 0x17 0x17 0x15 0x17 0x17 0x17 0x14 0x17 0x17 0x17 0x14 0 0xf>,
++ <112 0 0x17 0x17 0x17 0x17 0x17 0x17 0x17 0x15 0x17 0x17 0x17 0x14 0x17 0x17 0x17 0x14 0 0xf>,
++ <116 0 0x17 0x17 0x17 0x17 0x17 0x17 0x17 0x15 0x17 0x17 0x17 0x14 0x17 0x17 0x17 0x14 0 0xf>,
++ <120 0 0x17 0x17 0x17 0x17 0x17 0x17 0x17 0x15 0x17 0x17 0x17 0x14 0x17 0x17 0x17 0x14 0 0xf>,
++ <124 0 0x17 0x17 0x17 0x17 0x17 0x17 0x17 0x15 0x17 0x17 0x17 0x14 0x17 0x17 0x17 0x14 0 0xf>,
++ <128 0 0x17 0x17 0x17 0x17 0x17 0x17 0x17 0x15 0x17 0x17 0x17 0x14 0x17 0x17 0x17 0x14 0 0xf>,
++ <132 0 0x17 0x17 0x17 0x17 0x17 0x17 0x17 0x15 0x17 0x17 0x17 0x14 0x17 0x17 0x17 0x14 0 0xf>,
++ <136 0 0x17 0x17 0x17 0x17 0x17 0x17 0x17 0x15 0x17 0x17 0x17 0x14 0x17 0x17 0x17 0x14 0 0xf>,
++ <140 0 0x17 0x17 0x17 0x17 0x17 0x17 0x17 0x15 0x17 0x17 0x17 0x14 0x17 0x17 0x17 0x14 0 0xf>,
++ <149 0 0x1a 0x1a 0x18 0x17 0x1a 0x1a 0x17 0x15 0x1a 0x1a 0x17 0x14 0x1a 0x1a 0x17 0x14 0 0xf>,
++ <153 0 0x1a 0x1a 0x18 0x17 0x1a 0x1a 0x17 0x15 0x1a 0x1a 0x17 0x14 0x1a 0x1a 0x17 0x14 0 0xf>,
++ <157 0 0x1a 0x1a 0x18 0x17 0x1a 0x1a 0x17 0x15 0x1a 0x1a 0x17 0x14 0x1a 0x1a 0x17 0x14 0 0xf>,
++ <161 0 0x1a 0x1a 0x18 0x17 0x1a 0x1a 0x17 0x15 0x1a 0x1a 0x17 0x14 0x1a 0x1a 0x17 0x14 0 0xf>,
++ <165 0 0x1a 0x1a 0x18 0x17 0x1a 0x1a 0x17 0x15 0x1a 0x1a 0x17 0x14 0x1a 0x1a 0x17 0x14 0 0xf>;
++ CA =
++ <36 0 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0 0xf>,
++ <40 0 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0 0xf>,
++ <44 0 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0 0xf>,
++ <48 0 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0 0xf>,
++ <52 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>,
++ <56 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>,
++ <60 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>,
++ <64 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>,
++ <100 0 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>,
++ <104 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>,
++ <108 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <112 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <116 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <120 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <124 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <128 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <132 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <136 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <140 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <149 0 0x1a 0x1a 0x18 0x17 0x19 0x19 0x17 0x15 0x18 0x18 0x17 0x14 0x15 0x15 0x15 0x14 0 0xf>,
++ <153 0 0x1a 0x1a 0x18 0x17 0x1a 0x1a 0x17 0x15 0x1a 0x1a 0x17 0x14 0x15 0x15 0x15 0x14 0 0xf>,
++ <157 0 0x1a 0x1a 0x18 0x17 0x1a 0x1a 0x17 0x15 0x1a 0x1a 0x17 0x14 0x15 0x15 0x15 0x14 0 0xf>,
++ <161 0 0x1a 0x1a 0x18 0x17 0x1a 0x1a 0x17 0x15 0x1a 0x1a 0x17 0x14 0x15 0x15 0x15 0x14 0 0xf>,
++ <165 0 0x1a 0x1a 0x18 0x17 0x1a 0x1a 0x17 0x15 0x1a 0x1a 0x17 0x14 0x15 0x15 0x15 0x14 0 0xf>;
++ CN =
++ <36 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <40 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <44 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <48 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <52 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <56 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <60 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <64 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <100 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <104 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <108 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <112 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <116 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <120 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <124 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <128 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <132 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <136 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <140 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <149 0 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x14 0x14 0x14 0x14 0x11 0x11 0x11 0x11 0 0xf>,
++ <153 0 0x1a 0x1a 0x18 0x17 0x1a 0x1a 0x17 0x15 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0 0xf>,
++ <157 0 0x1a 0x1a 0x18 0x17 0x1a 0x1a 0x17 0x15 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0 0xf>,
++ <161 0 0x1a 0x1a 0x18 0x17 0x1a 0x1a 0x17 0x15 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0 0xf>,
++ <165 0 0x15 0x15 0x15 0x15 0x16 0x16 0x16 0x15 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0 0xf>;
++ ETSI =
++ <36 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <40 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <44 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <48 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <52 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <56 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <60 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <64 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <100 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <104 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <108 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <112 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <116 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <120 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <124 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <128 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <132 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <136 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <140 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>,
++ <149 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>;
++ FCC =
++ <36 0 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>,
++ <40 0 0x19 0x19 0x18 0x17 0x19 0x19 0x17 0x15 0x17 0x17 0x17 0x14 0x10 0x10 0x10 0x10 0 0xf>,
++ <44 0 0x19 0x19 0x18 0x17 0x19 0x19 0x17 0x15 0x17 0x17 0x17 0x14 0x10 0x10 0x10 0x10 0 0xf>,
++ <48 0 0x1a 0x1a 0x18 0x17 0x1a 0x1a 0x17 0x15 0x17 0x17 0x17 0x14 0x10 0x10 0x10 0x10 0 0xf>,
++ <52 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>,
++ <56 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>,
++ <60 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>,
++ <64 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>,
++ <100 0 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>,
++ <104 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>,
++ <108 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <112 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <116 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <120 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <124 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <128 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <132 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <136 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <140 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <149 0 0x1a 0x1a 0x18 0x17 0x19 0x19 0x17 0x15 0x18 0x18 0x17 0x14 0x15 0x15 0x15 0x14 0 0xf>,
++ <153 0 0x1a 0x1a 0x18 0x17 0x1a 0x1a 0x17 0x15 0x1a 0x1a 0x17 0x14 0x15 0x15 0x15 0x14 0 0xf>,
++ <157 0 0x1a 0x1a 0x18 0x17 0x1a 0x1a 0x17 0x15 0x1a 0x1a 0x17 0x14 0x15 0x15 0x15 0x14 0 0xf>,
++ <161 0 0x1a 0x1a 0x18 0x17 0x1a 0x1a 0x17 0x15 0x1a 0x1a 0x17 0x14 0x15 0x15 0x15 0x14 0 0xf>,
++ <165 0 0x1a 0x1a 0x18 0x17 0x1a 0x1a 0x17 0x15 0x1a 0x1a 0x17 0x14 0x15 0x15 0x15 0x14 0 0xf>;
++ };
++ };
++ };
++
++ pcie@2,0 {
++ mwlwifi {
++ marvell,5ghz = <0>;
++ marvell,chainmask = <2 2>;
++ marvell,powertable {
++ AU =
++ <1 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>,
++ <2 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>,
++ <3 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>,
++ <4 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>,
++ <5 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>,
++ <6 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>,
++ <7 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>,
++ <8 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>,
++ <9 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>,
++ <10 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>,
++ <11 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>;
++ CA =
++ <1 0 0x19 0x14 0x14 0x14 0x13 0x13 0x13 0x13 0x10 0x10 0x10 0x10 0x00 0x00 0x00 0x00 0 0xf>,
++ <2 0 0x1a 0x19 0x18 0x17 0x19 0x19 0x17 0x16 0x14 0x14 0x14 0x14 0x00 0x00 0x00 0x00 0 0xf>,
++ <3 0 0x1a 0x19 0x18 0x17 0x19 0x19 0x17 0x16 0x14 0x14 0x14 0x14 0x00 0x00 0x00 0x00 0 0xf>,
++ <4 0 0x1a 0x19 0x18 0x17 0x19 0x19 0x17 0x16 0x14 0x14 0x14 0x14 0x00 0x00 0x00 0x00 0 0xf>,
++ <5 0 0x1a 0x19 0x18 0x17 0x19 0x19 0x17 0x16 0x14 0x14 0x14 0x14 0x00 0x00 0x00 0x00 0 0xf>,
++ <6 0 0x1a 0x19 0x18 0x17 0x19 0x19 0x17 0x16 0x14 0x14 0x14 0x14 0x00 0x00 0x00 0x00 0 0xf>,
++ <7 0 0x1a 0x19 0x18 0x17 0x19 0x19 0x17 0x16 0x14 0x14 0x14 0x14 0x00 0x00 0x00 0x00 0 0xf>,
++ <8 0 0x1a 0x19 0x18 0x17 0x19 0x19 0x17 0x16 0x14 0x14 0x14 0x14 0x00 0x00 0x00 0x00 0 0xf>,
++ <9 0 0x1a 0x19 0x18 0x17 0x19 0x19 0x17 0x16 0x14 0x14 0x14 0x14 0x00 0x00 0x00 0x00 0 0xf>,
++ <10 0 0x1a 0x19 0x18 0x17 0x19 0x19 0x17 0x16 0x14 0x14 0x14 0x14 0x00 0x00 0x00 0x00 0 0xf>,
++ <11 0 0x19 0x15 0x15 0x15 0x14 0x14 0x14 0x14 0x13 0x13 0x13 0x13 0x00 0x00 0x00 0x00 0 0xf>;
++ CN =
++ <1 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>,
++ <2 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>,
++ <3 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>,
++ <4 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>,
++ <5 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>,
++ <6 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>,
++ <7 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>,
++ <8 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>,
++ <9 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>,
++ <10 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>,
++ <11 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>,
++ <12 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>,
++ <13 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>,
++ <14 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>;
++ ETSI =
++ <1 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>,
++ <2 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>,
++ <3 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>,
++ <4 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>,
++ <5 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>,
++ <6 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>,
++ <7 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>,
++ <8 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>,
++ <9 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>,
++ <10 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>,
++ <11 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>,
++ <12 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>,
++ <13 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>;
++ FCC =
++ <1 0 0x19 0x14 0x14 0x14 0x13 0x13 0x13 0x13 0x10 0x10 0x10 0x10 0x0 0x0 0x0 0x0 0 0xf>,
++ <2 0 0x1a 0x19 0x18 0x17 0x19 0x19 0x17 0x16 0x14 0x14 0x14 0x14 0x0 0x0 0x0 0x0 0 0xf>,
++ <3 0 0x1a 0x19 0x18 0x17 0x19 0x19 0x17 0x16 0x14 0x14 0x14 0x14 0x0 0x0 0x0 0x0 0 0xf>,
++ <4 0 0x1a 0x19 0x18 0x17 0x19 0x19 0x17 0x16 0x14 0x14 0x14 0x14 0x0 0x0 0x0 0x0 0 0xf>,
++ <5 0 0x1a 0x19 0x18 0x17 0x19 0x19 0x17 0x16 0x14 0x14 0x14 0x14 0x0 0x0 0x0 0x0 0 0xf>,
++ <6 0 0x1a 0x19 0x18 0x17 0x19 0x19 0x17 0x16 0x14 0x14 0x14 0x14 0x0 0x0 0x0 0x0 0 0xf>,
++ <7 0 0x1a 0x19 0x18 0x17 0x19 0x19 0x17 0x16 0x14 0x14 0x14 0x14 0x0 0x0 0x0 0x0 0 0xf>,
++ <8 0 0x1a 0x19 0x18 0x17 0x19 0x19 0x17 0x16 0x14 0x14 0x14 0x14 0x0 0x0 0x0 0x0 0 0xf>,
++ <9 0 0x1a 0x19 0x18 0x17 0x19 0x19 0x17 0x16 0x14 0x14 0x14 0x14 0x0 0x0 0x0 0x0 0 0xf>,
++ <10 0 0x1a 0x19 0x18 0x17 0x19 0x19 0x17 0x16 0x14 0x14 0x14 0x14 0x0 0x0 0x0 0x0 0 0xf>,
++ <11 0 0x19 0x15 0x15 0x15 0x14 0x14 0x14 0x14 0x13 0x13 0x13 0x13 0x0 0x0 0x0 0x0 0 0xf>;
++ };
++ };
++ };
++ };
+ };
+
+ gpio-leds {
+--- a/arch/arm/boot/dts/armada-385-linksys-shelby.dts
++++ b/arch/arm/boot/dts/armada-385-linksys-shelby.dts
+@@ -100,6 +100,212 @@
+ };
+ };
+ };
++
++ pcie-controller {
++ pcie@1,0 {
++ mwlwifi {
++ marvell,2ghz = <0>;
++ marvell,chainmask = <4 4>;
++ marvell,powertable {
++ AU =
++ <36 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <40 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <44 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <48 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <52 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <56 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <60 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <64 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <100 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>,
++ <104 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>,
++ <108 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>,
++ <112 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>,
++ <116 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>,
++ <120 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>,
++ <124 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>,
++ <128 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>,
++ <132 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>,
++ <136 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>,
++ <140 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>,
++ <149 0 0x19 0x19 0x19 0x17 0x19 0x19 0x16 0x15 0x19 0x19 0x16 0x15 0x19 0x19 0x16 0x15 0 0xf>,
++ <153 0 0x19 0x19 0x19 0x17 0x19 0x19 0x16 0x15 0x19 0x19 0x16 0x15 0x19 0x19 0x16 0x15 0 0xf>,
++ <157 0 0x19 0x19 0x19 0x17 0x19 0x19 0x16 0x15 0x19 0x19 0x16 0x15 0x19 0x19 0x16 0x15 0 0xf>,
++ <161 0 0x19 0x19 0x19 0x17 0x19 0x19 0x16 0x15 0x19 0x19 0x16 0x15 0x19 0x19 0x16 0x15 0 0xf>,
++ <165 0 0x19 0x19 0x19 0x17 0x19 0x19 0x16 0x15 0x19 0x19 0x16 0x15 0x19 0x19 0x16 0x15 0 0xf>;
++ CA =
++ <36 0 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0 0xf>,
++ <40 0 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0 0xf>,
++ <44 0 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0 0xf>,
++ <48 0 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0 0xf>,
++ <52 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>,
++ <56 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>,
++ <60 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>,
++ <64 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>,
++ <100 0 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>,
++ <104 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>,
++ <108 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <112 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <116 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <120 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <124 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <128 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <132 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <136 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <140 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <149 0 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0 0xf>,
++ <153 0 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0 0xf>,
++ <157 0 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0 0xf>,
++ <161 0 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0 0xf>,
++ <165 0 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0 0xf>;
++ CN =
++ <36 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <40 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <44 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <48 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <52 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <56 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <60 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <64 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <100 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <104 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <108 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <112 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <116 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <120 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <124 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <128 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <132 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <136 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <140 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <149 0 0x14 0x14 0x14 0x14 0x13 0x13 0x13 0x13 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <153 0 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x14 0x14 0x14 0x14 0x10 0x10 0x10 0x10 0 0xf>,
++ <157 0 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x14 0x14 0x14 0x14 0x10 0x10 0x10 0x10 0 0xf>,
++ <161 0 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x14 0x14 0x14 0x14 0x10 0x10 0x10 0x10 0 0xf>,
++ <165 0 0x13 0x13 0x13 0x13 0x13 0x13 0x13 0x13 0x14 0x14 0x14 0x14 0x10 0x10 0x10 0x10 0 0xf>;
++ ETSI =
++ <36 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <40 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <44 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <48 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <52 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <56 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <60 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <64 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <100 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <104 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <108 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <112 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <116 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <120 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <124 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <128 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <132 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <136 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <140 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>,
++ <149 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>;
++ FCC =
++ <36 0 0x12 0x12 0x12 0x12 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0xf 0xf 0xf 0xf 0 0xf>,
++ <40 0 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0xf 0xf 0xf 0xf 0 0xf>,
++ <44 0 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0xf 0xf 0xf 0xf 0 0xf>,
++ <48 0 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0xf 0xf 0xf 0xf 0 0xf>,
++ <52 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>,
++ <56 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>,
++ <60 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>,
++ <64 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>,
++ <100 0 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>,
++ <104 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>,
++ <108 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <112 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <116 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <120 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <124 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <128 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <132 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <136 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <140 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>,
++ <149 0 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0 0xf>,
++ <153 0 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0 0xf>,
++ <157 0 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0 0xf>,
++ <161 0 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0 0xf>,
++ <165 0 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0 0xf>;
++ };
++ };
++ };
++
++ pcie@2,0 {
++ mwlwifi {
++ marvell,5ghz = <0>;
++ marvell,chainmask = <4 4>;
++ marvell,powertable {
++ AU =
++ <1 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <2 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <3 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <4 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <5 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <6 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <7 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <8 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <9 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <10 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <11 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>;
++ CA =
++ <1 0 0x17 0x10 0x10 0x10 0xf 0xf 0xf 0xf 0xe 0xe 0xe 0xe 0x0 0x0 0x0 0x0 0 0xf>,
++ <2 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>,
++ <3 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>,
++ <4 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>,
++ <5 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>,
++ <6 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>,
++ <7 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>,
++ <8 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>,
++ <9 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>,
++ <10 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>,
++ <11 0 0x17 0x12 0x12 0x12 0x13 0x13 0x13 0x13 0xf 0xf 0xf 0xf 0x0 0x0 0x0 0x0 0 0xf>;
++ CN =
++ <1 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <2 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <3 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <4 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <5 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <6 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <7 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <8 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <9 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <10 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <11 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <12 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <13 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <14 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>;
++ ETSI =
++ <1 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <2 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <3 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <4 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <5 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <6 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <7 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <8 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <9 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <10 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <11 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <12 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>,
++ <13 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>;
++ FCC =
++ <1 0 0x17 0x10 0x10 0x10 0xf 0xf 0xf 0xf 0xe 0xe 0xe 0xe 0x0 0x0 0x0 0x0 0 0xf>,
++ <2 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>,
++ <3 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>,
++ <4 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>,
++ <5 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>,
++ <6 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>,
++ <7 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>,
++ <8 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>,
++ <9 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>,
++ <10 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>,
++ <11 0 0x17 0x12 0x12 0x12 0x13 0x13 0x13 0x13 0xf 0xf 0xf 0xf 0x0 0x0 0x0 0x0 0 0xf>;
++ };
++ };
++ };
++ };
+ };
+
+ gpio-leds {
--- /dev/null
+--- a/arch/arm/boot/dts/armada-xp-linksys-mamba.dts
++++ b/arch/arm/boot/dts/armada-xp-linksys-mamba.dts
+@@ -467,6 +467,16 @@
+ };
+ };
+ };
++
++ mvsw61xx {
++ compatible = "marvell,88e6172";
++ status = "okay";
++ reg = <0x10>;
++
++ mii-bus = <&mdio>;
++ cpu-port-0 = <5>;
++ cpu-port-1 = <6>;
++ };
+ };
+
+ &pinctrl {
+--- a/arch/arm/boot/dts/armada-385-linksys.dtsi
++++ b/arch/arm/boot/dts/armada-385-linksys.dtsi
+@@ -309,6 +309,18 @@
+ };
+ };
+ };
++
++ mvsw61xx {
++ #address-cells = <1>;
++ #size-cells = <0>;
++ compatible = "marvell,88e6176";
++ status = "okay";
++ reg = <0x10>;
++
++ mii-bus = <&mdio>;
++ cpu-port-0 = <5>;
++ cpu-port-1 = <6>;
++ };
+ };
+
+ &pinctrl {
--- /dev/null
+--- a/arch/arm/boot/dts/Makefile
++++ b/arch/arm/boot/dts/Makefile
+@@ -748,6 +748,8 @@ dtb-$(CONFIG_MACH_ARMADA_38X) += \
+ armada-385-db-ap.dtb \
+ armada-385-linksys-caiman.dtb \
+ armada-385-linksys-cobra.dtb \
++ armada-385-linksys-rango.dtb \
++ armada-385-linksys-shelby.dtb \
+ armada-388-db.dtb \
+ armada-388-gp.dtb \
+ armada-388-rd.dtb
--- /dev/null
+From: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
+Date: Wed, 10 Feb 2016 14:54:21 +0100
+Subject: [PATCH] mtd: nand: pxa3xx_nand: add support for partial chunks
+
+This commit is needed to properly support the 8-bits ECC configuration
+with 4KB pages.
+
+When pages larger than 2 KB are used on platforms using the PXA3xx
+NAND controller, the reading/programming operations need to be split
+in chunks of 2 KBs or less because the controller FIFO is limited to
+about 2 KB (i.e a bit more than 2 KB to accommodate OOB data). Due to
+this requirement, the data layout on NAND is a bit strange, with ECC
+interleaved with data, at the end of each chunk.
+
+When a 4-bits ECC configuration is used with 4 KB pages, the physical
+data layout on the NAND looks like this:
+
+| 2048 data | 32 spare | 30 ECC | 2048 data | 32 spare | 30 ECC |
+
+So the data chunks have an equal size, 2080 bytes for each chunk,
+which the driver supports properly.
+
+When a 8-bits ECC configuration is used with 4KB pages, the physical
+data layout on the NAND looks like this:
+
+| 1024 data | 30 ECC | 1024 data | 30 ECC | 1024 data | 30 ECC | 1024 data | 30 ECC | 64 spare | 30 ECC |
+
+So, the spare area is stored in its own chunk, which has a different
+size than the other chunks. Since OOB is not used by UBIFS, the initial
+implementation of the driver has chosen to not support reading this
+additional "spare" chunk of data.
+
+Unfortunately, Marvell has chosen to store the BBT signature in the
+OOB area. Therefore, if the driver doesn't read this spare area, Linux
+has no way of finding the BBT. It thinks there is no BBT, and rewrites
+one, which U-Boot does not recognize, causing compatibility problems
+between the bootloader and the kernel in terms of NAND usage.
+
+To fix this, this commit implements the support for reading a partial
+last chunk. This support is currently only useful for the case of 8
+bits ECC with 4 KB pages, but it will be useful in the future to
+enable other configurations such as 12 bits and 16 bits ECC with 4 KB
+pages, or 8 bits ECC with 8 KB pages, etc. All those configurations
+have a "last" chunk that doesn't have the same size as the other
+chunks.
+
+In order to implement reading of the last chunk, this commit:
+
+ - Adds a number of new fields to the pxa3xx_nand_info to describe how
+ many full chunks and how many chunks we have, the size of full
+ chunks and partial chunks, both in terms of data area and spare
+ area.
+
+ - Fills in the step_chunk_size and step_spare_size variables to
+ describe how much data and spare should be read/written for the
+ current read/program step.
+
+ - Reworks the state machine to accommodate doing the additional read
+ or program step when a last partial chunk is used.
+
+This commit has been tested on a Marvell Armada 398 DB board, with a
+4KB page NAND, tested in both 4 bits ECC and 8 bits ECC
+configurations. Robert Jarzmik has tested on some PXA platforms.
+
+Signed-off-by: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
+Tested-by: Robert Jarzmik <robert.jarzmik@free.fr>
+Acked-by: Ezequiel Garcia <ezequiel@vanguardiasur.com.ar>
+Signed-off-by: Brian Norris <computersforpeace@gmail.com>
+---
+
+--- a/drivers/mtd/nand/pxa3xx_nand.c
++++ b/drivers/mtd/nand/pxa3xx_nand.c
+@@ -228,15 +228,44 @@ struct pxa3xx_nand_info {
+ int use_spare; /* use spare ? */
+ int need_wait;
+
+- unsigned int data_size; /* data to be read from FIFO */
+- unsigned int chunk_size; /* split commands chunk size */
+- unsigned int oob_size;
++ /* Amount of real data per full chunk */
++ unsigned int chunk_size;
++
++ /* Amount of spare data per full chunk */
+ unsigned int spare_size;
++
++ /* Number of full chunks (i.e chunk_size + spare_size) */
++ unsigned int nfullchunks;
++
++ /*
++ * Total number of chunks. If equal to nfullchunks, then there
++ * are only full chunks. Otherwise, there is one last chunk of
++ * size (last_chunk_size + last_spare_size)
++ */
++ unsigned int ntotalchunks;
++
++ /* Amount of real data in the last chunk */
++ unsigned int last_chunk_size;
++
++ /* Amount of spare data in the last chunk */
++ unsigned int last_spare_size;
++
+ unsigned int ecc_size;
+ unsigned int ecc_err_cnt;
+ unsigned int max_bitflips;
+ int retcode;
+
++ /*
++ * Variables only valid during command
++ * execution. step_chunk_size and step_spare_size is the
++ * amount of real data and spare data in the current
++ * chunk. cur_chunk is the current chunk being
++ * read/programmed.
++ */
++ unsigned int step_chunk_size;
++ unsigned int step_spare_size;
++ unsigned int cur_chunk;
++
+ /* cached register value */
+ uint32_t reg_ndcr;
+ uint32_t ndtr0cs0;
+@@ -531,25 +560,6 @@ static int pxa3xx_nand_init(struct pxa3x
+ return 0;
+ }
+
+-/*
+- * Set the data and OOB size, depending on the selected
+- * spare and ECC configuration.
+- * Only applicable to READ0, READOOB and PAGEPROG commands.
+- */
+-static void pxa3xx_set_datasize(struct pxa3xx_nand_info *info,
+- struct mtd_info *mtd)
+-{
+- int oob_enable = info->reg_ndcr & NDCR_SPARE_EN;
+-
+- info->data_size = mtd->writesize;
+- if (!oob_enable)
+- return;
+-
+- info->oob_size = info->spare_size;
+- if (!info->use_ecc)
+- info->oob_size += info->ecc_size;
+-}
+-
+ /**
+ * NOTE: it is a must to set ND_RUN firstly, then write
+ * command buffer, otherwise, it does not work.
+@@ -665,28 +675,28 @@ static void drain_fifo(struct pxa3xx_nan
+
+ static void handle_data_pio(struct pxa3xx_nand_info *info)
+ {
+- unsigned int do_bytes = min(info->data_size, info->chunk_size);
+-
+ switch (info->state) {
+ case STATE_PIO_WRITING:
+- writesl(info->mmio_base + NDDB,
+- info->data_buff + info->data_buff_pos,
+- DIV_ROUND_UP(do_bytes, 4));
++ if (info->step_chunk_size)
++ writesl(info->mmio_base + NDDB,
++ info->data_buff + info->data_buff_pos,
++ DIV_ROUND_UP(info->step_chunk_size, 4));
+
+- if (info->oob_size > 0)
++ if (info->step_spare_size)
+ writesl(info->mmio_base + NDDB,
+ info->oob_buff + info->oob_buff_pos,
+- DIV_ROUND_UP(info->oob_size, 4));
++ DIV_ROUND_UP(info->step_spare_size, 4));
+ break;
+ case STATE_PIO_READING:
+- drain_fifo(info,
+- info->data_buff + info->data_buff_pos,
+- DIV_ROUND_UP(do_bytes, 4));
++ if (info->step_chunk_size)
++ drain_fifo(info,
++ info->data_buff + info->data_buff_pos,
++ DIV_ROUND_UP(info->step_chunk_size, 4));
+
+- if (info->oob_size > 0)
++ if (info->step_spare_size)
+ drain_fifo(info,
+ info->oob_buff + info->oob_buff_pos,
+- DIV_ROUND_UP(info->oob_size, 4));
++ DIV_ROUND_UP(info->step_spare_size, 4));
+ break;
+ default:
+ dev_err(&info->pdev->dev, "%s: invalid state %d\n", __func__,
+@@ -695,9 +705,8 @@ static void handle_data_pio(struct pxa3x
+ }
+
+ /* Update buffer pointers for multi-page read/write */
+- info->data_buff_pos += do_bytes;
+- info->oob_buff_pos += info->oob_size;
+- info->data_size -= do_bytes;
++ info->data_buff_pos += info->step_chunk_size;
++ info->oob_buff_pos += info->step_spare_size;
+ }
+
+ static void pxa3xx_nand_data_dma_irq(void *data)
+@@ -738,8 +747,9 @@ static void start_data_dma(struct pxa3xx
+ info->state);
+ BUG();
+ }
+- info->sg.length = info->data_size +
+- (info->oob_size ? info->spare_size + info->ecc_size : 0);
++ info->sg.length = info->chunk_size;
++ if (info->use_spare)
++ info->sg.length += info->spare_size + info->ecc_size;
+ dma_map_sg(info->dma_chan->device->dev, &info->sg, 1, info->dma_dir);
+
+ tx = dmaengine_prep_slave_sg(info->dma_chan, &info->sg, 1, direction,
+@@ -900,9 +910,11 @@ static void prepare_start_command(struct
+ /* reset data and oob column point to handle data */
+ info->buf_start = 0;
+ info->buf_count = 0;
+- info->oob_size = 0;
+ info->data_buff_pos = 0;
+ info->oob_buff_pos = 0;
++ info->step_chunk_size = 0;
++ info->step_spare_size = 0;
++ info->cur_chunk = 0;
+ info->use_ecc = 0;
+ info->use_spare = 1;
+ info->retcode = ERR_NONE;
+@@ -914,8 +926,6 @@ static void prepare_start_command(struct
+ case NAND_CMD_READ0:
+ case NAND_CMD_PAGEPROG:
+ info->use_ecc = 1;
+- case NAND_CMD_READOOB:
+- pxa3xx_set_datasize(info, mtd);
+ break;
+ case NAND_CMD_PARAM:
+ info->use_spare = 0;
+@@ -974,6 +984,14 @@ static int prepare_set_command(struct px
+ if (command == NAND_CMD_READOOB)
+ info->buf_start += mtd->writesize;
+
++ if (info->cur_chunk < info->nfullchunks) {
++ info->step_chunk_size = info->chunk_size;
++ info->step_spare_size = info->spare_size;
++ } else {
++ info->step_chunk_size = info->last_chunk_size;
++ info->step_spare_size = info->last_spare_size;
++ }
++
+ /*
+ * Multiple page read needs an 'extended command type' field,
+ * which is either naked-read or last-read according to the
+@@ -985,8 +1003,8 @@ static int prepare_set_command(struct px
+ info->ndcb0 |= NDCB0_DBC | (NAND_CMD_READSTART << 8)
+ | NDCB0_LEN_OVRD
+ | NDCB0_EXT_CMD_TYPE(ext_cmd_type);
+- info->ndcb3 = info->chunk_size +
+- info->oob_size;
++ info->ndcb3 = info->step_chunk_size +
++ info->step_spare_size;
+ }
+
+ set_command_address(info, mtd->writesize, column, page_addr);
+@@ -1006,8 +1024,6 @@ static int prepare_set_command(struct px
+ | NDCB0_EXT_CMD_TYPE(ext_cmd_type)
+ | addr_cycle
+ | command;
+- /* No data transfer in this case */
+- info->data_size = 0;
+ exec_cmd = 1;
+ }
+ break;
+@@ -1019,6 +1035,14 @@ static int prepare_set_command(struct px
+ break;
+ }
+
++ if (info->cur_chunk < info->nfullchunks) {
++ info->step_chunk_size = info->chunk_size;
++ info->step_spare_size = info->spare_size;
++ } else {
++ info->step_chunk_size = info->last_chunk_size;
++ info->step_spare_size = info->last_spare_size;
++ }
++
+ /* Second command setting for large pages */
+ if (mtd->writesize > PAGE_CHUNK_SIZE) {
+ /*
+@@ -1029,14 +1053,14 @@ static int prepare_set_command(struct px
+ info->ndcb0 |= NDCB0_CMD_TYPE(0x1)
+ | NDCB0_LEN_OVRD
+ | NDCB0_EXT_CMD_TYPE(ext_cmd_type);
+- info->ndcb3 = info->chunk_size +
+- info->oob_size;
++ info->ndcb3 = info->step_chunk_size +
++ info->step_spare_size;
+
+ /*
+ * This is the command dispatch that completes a chunked
+ * page program operation.
+ */
+- if (info->data_size == 0) {
++ if (info->cur_chunk == info->ntotalchunks) {
+ info->ndcb0 = NDCB0_CMD_TYPE(0x1)
+ | NDCB0_EXT_CMD_TYPE(ext_cmd_type)
+ | command;
+@@ -1063,7 +1087,7 @@ static int prepare_set_command(struct px
+ | command;
+ info->ndcb1 = (column & 0xFF);
+ info->ndcb3 = INIT_BUFFER_SIZE;
+- info->data_size = INIT_BUFFER_SIZE;
++ info->step_chunk_size = INIT_BUFFER_SIZE;
+ break;
+
+ case NAND_CMD_READID:
+@@ -1073,7 +1097,7 @@ static int prepare_set_command(struct px
+ | command;
+ info->ndcb1 = (column & 0xFF);
+
+- info->data_size = 8;
++ info->step_chunk_size = 8;
+ break;
+ case NAND_CMD_STATUS:
+ info->buf_count = 1;
+@@ -1081,7 +1105,7 @@ static int prepare_set_command(struct px
+ | NDCB0_ADDR_CYC(1)
+ | command;
+
+- info->data_size = 8;
++ info->step_chunk_size = 8;
+ break;
+
+ case NAND_CMD_ERASE1:
+@@ -1220,6 +1244,7 @@ static void nand_cmdfunc_extended(struct
+ init_completion(&info->dev_ready);
+ do {
+ info->state = STATE_PREPARED;
++
+ exec_cmd = prepare_set_command(info, command, ext_cmd_type,
+ column, page_addr);
+ if (!exec_cmd) {
+@@ -1239,22 +1264,30 @@ static void nand_cmdfunc_extended(struct
+ break;
+ }
+
++ /* Only a few commands need several steps */
++ if (command != NAND_CMD_PAGEPROG &&
++ command != NAND_CMD_READ0 &&
++ command != NAND_CMD_READOOB)
++ break;
++
++ info->cur_chunk++;
++
+ /* Check if the sequence is complete */
+- if (info->data_size == 0 && command != NAND_CMD_PAGEPROG)
++ if (info->cur_chunk == info->ntotalchunks && command != NAND_CMD_PAGEPROG)
+ break;
+
+ /*
+ * After a splitted program command sequence has issued
+ * the command dispatch, the command sequence is complete.
+ */
+- if (info->data_size == 0 &&
++ if (info->cur_chunk == (info->ntotalchunks + 1) &&
+ command == NAND_CMD_PAGEPROG &&
+ ext_cmd_type == EXT_CMD_TYPE_DISPATCH)
+ break;
+
+ if (command == NAND_CMD_READ0 || command == NAND_CMD_READOOB) {
+ /* Last read: issue a 'last naked read' */
+- if (info->data_size == info->chunk_size)
++ if (info->cur_chunk == info->ntotalchunks - 1)
+ ext_cmd_type = EXT_CMD_TYPE_LAST_RW;
+ else
+ ext_cmd_type = EXT_CMD_TYPE_NAKED_RW;
+@@ -1264,7 +1297,7 @@ static void nand_cmdfunc_extended(struct
+ * the command dispatch must be issued to complete.
+ */
+ } else if (command == NAND_CMD_PAGEPROG &&
+- info->data_size == 0) {
++ info->cur_chunk == info->ntotalchunks) {
+ ext_cmd_type = EXT_CMD_TYPE_DISPATCH;
+ }
+ } while (1);
+@@ -1514,6 +1547,8 @@ static int pxa_ecc_init(struct pxa3xx_na
+ int strength, int ecc_stepsize, int page_size)
+ {
+ if (strength == 1 && ecc_stepsize == 512 && page_size == 2048) {
++ info->nfullchunks = 1;
++ info->ntotalchunks = 1;
+ info->chunk_size = 2048;
+ info->spare_size = 40;
+ info->ecc_size = 24;
+@@ -1522,6 +1557,8 @@ static int pxa_ecc_init(struct pxa3xx_na
+ ecc->strength = 1;
+
+ } else if (strength == 1 && ecc_stepsize == 512 && page_size == 512) {
++ info->nfullchunks = 1;
++ info->ntotalchunks = 1;
+ info->chunk_size = 512;
+ info->spare_size = 8;
+ info->ecc_size = 8;
+@@ -1535,6 +1572,8 @@ static int pxa_ecc_init(struct pxa3xx_na
+ */
+ } else if (strength == 4 && ecc_stepsize == 512 && page_size == 2048) {
+ info->ecc_bch = 1;
++ info->nfullchunks = 1;
++ info->ntotalchunks = 1;
+ info->chunk_size = 2048;
+ info->spare_size = 32;
+ info->ecc_size = 32;
+@@ -1545,6 +1584,8 @@ static int pxa_ecc_init(struct pxa3xx_na
+
+ } else if (strength == 4 && ecc_stepsize == 512 && page_size == 4096) {
+ info->ecc_bch = 1;
++ info->nfullchunks = 2;
++ info->ntotalchunks = 2;
+ info->chunk_size = 2048;
+ info->spare_size = 32;
+ info->ecc_size = 32;
+@@ -1559,8 +1600,12 @@ static int pxa_ecc_init(struct pxa3xx_na
+ */
+ } else if (strength == 8 && ecc_stepsize == 512 && page_size == 4096) {
+ info->ecc_bch = 1;
++ info->nfullchunks = 4;
++ info->ntotalchunks = 5;
+ info->chunk_size = 1024;
+ info->spare_size = 0;
++ info->last_chunk_size = 0;
++ info->last_spare_size = 64;
+ info->ecc_size = 32;
+ ecc->mode = NAND_ECC_HW;
+ ecc->size = info->chunk_size;
--- /dev/null
+From: =?UTF-8?q?Ezequiel=20Garc=C3=ADa?= <ezequiel@vanguardiasur.com.ar>
+Date: Wed, 4 Nov 2015 13:13:41 -0300
+Subject: [PATCH] mtd: pxa3xx_nand: Increase the initial chunk size
+
+The chunk size represents the size of the data chunks, which
+is used by the controllers that allow to split transfered data.
+
+However, the initial chunk size is used in a non-splitted way,
+during device identification. Therefore, it must be large enough
+for all the NAND commands issued during device identification.
+This includes NAND_CMD_PARAM which was recently changed to
+transfer up to 2048 bytes (for the redundant parameter pages).
+
+Thus, the initial chunk size should be 2048 as well.
+
+On Armada 370/XP platforms (NFCv2) booted without the keep-config
+devicetree property, this commit fixes a timeout on the NAND_CMD_PARAM
+command:
+
+ [..]
+ pxa3xx-nand f10d0000.nand: This platform can't do DMA on this device
+ pxa3xx-nand f10d0000.nand: Wait time out!!!
+ nand: device found, Manufacturer ID: 0x2c, Chip ID: 0x38
+ nand: Micron MT29F8G08ABABAWP
+ nand: 1024 MiB, SLC, erase size: 512 KiB, page size: 4096, OOB size: 224
+
+Signed-off-by: Ezequiel Garcia <ezequiel@vanguardiasur.com.ar>
+Acked-by: Robert Jarzmik <robert.jarzmik@free.fr>
+Signed-off-by: Brian Norris <computersforpeace@gmail.com>
+---
+
+--- a/drivers/mtd/nand/pxa3xx_nand.c
++++ b/drivers/mtd/nand/pxa3xx_nand.c
+@@ -1637,7 +1637,7 @@ static int pxa3xx_nand_scan(struct mtd_i
+ goto KEEP_CONFIG;
+
+ /* Set a default chunk size */
+- info->chunk_size = 512;
++ info->chunk_size = PAGE_CHUNK_SIZE;
+
+ ret = pxa3xx_nand_config_flash(info);
+ if (ret)
--- /dev/null
+From: =?UTF-8?q?Ezequiel=20Garc=C3=ADa?= <ezequiel@vanguardiasur.com.ar>
+Date: Wed, 4 Nov 2015 13:13:42 -0300
+Subject: [PATCH] mtd: pxa3xx_nand: Fix initial controller configuration
+
+The Data Flash Control Register (NDCR) contains two types
+of parameters: those that are needed for device identification,
+and those that can only be set after device identification.
+
+Therefore, the driver can't set them all at once and instead
+needs to configure the first group before nand_scan_ident()
+and the second group later.
+
+Let's split pxa3xx_nand_config in two halves, and set the
+parameters that depend on the device geometry once this is known.
+
+Signed-off-by: Ezequiel Garcia <ezequiel@vanguardiasur.com.ar>
+Signed-off-by: Brian Norris <computersforpeace@gmail.com>
+---
+
+--- a/drivers/mtd/nand/pxa3xx_nand.c
++++ b/drivers/mtd/nand/pxa3xx_nand.c
+@@ -1420,34 +1420,43 @@ static int pxa3xx_nand_waitfunc(struct m
+ return NAND_STATUS_READY;
+ }
+
+-static int pxa3xx_nand_config_flash(struct pxa3xx_nand_info *info)
++static int pxa3xx_nand_config_ident(struct pxa3xx_nand_info *info)
+ {
+ struct platform_device *pdev = info->pdev;
+ struct pxa3xx_nand_platform_data *pdata = dev_get_platdata(&pdev->dev);
+- struct pxa3xx_nand_host *host = info->host[info->cs];
+- struct mtd_info *mtd = host->mtd;
+- struct nand_chip *chip = mtd->priv;
+
+- /* configure default flash values */
++ /* Configure default flash values */
++ info->chunk_size = PAGE_CHUNK_SIZE;
+ info->reg_ndcr = 0x0; /* enable all interrupts */
+ info->reg_ndcr |= (pdata->enable_arbiter) ? NDCR_ND_ARB_EN : 0;
+ info->reg_ndcr |= NDCR_RD_ID_CNT(READ_ID_BYTES);
+- info->reg_ndcr |= NDCR_SPARE_EN; /* enable spare by default */
++ info->reg_ndcr |= NDCR_SPARE_EN;
++
++ return 0;
++}
++
++static void pxa3xx_nand_config_tail(struct pxa3xx_nand_info *info)
++{
++ struct pxa3xx_nand_host *host = info->host[info->cs];
++ struct mtd_info *mtd = host->mtd;
++ struct nand_chip *chip = mtd->priv;
++
+ info->reg_ndcr |= (host->col_addr_cycles == 2) ? NDCR_RA_START : 0;
+ info->reg_ndcr |= (chip->page_shift == 6) ? NDCR_PG_PER_BLK : 0;
+ info->reg_ndcr |= (mtd->writesize == 2048) ? NDCR_PAGE_SZ : 0;
+-
+- return 0;
+ }
+
+ static int pxa3xx_nand_detect_config(struct pxa3xx_nand_info *info)
+ {
++ struct platform_device *pdev = info->pdev;
++ struct pxa3xx_nand_platform_data *pdata = dev_get_platdata(&pdev->dev);
+ uint32_t ndcr = nand_readl(info, NDCR);
+
+ /* Set an initial chunk size */
+ info->chunk_size = ndcr & NDCR_PAGE_SZ ? 2048 : 512;
+ info->reg_ndcr = ndcr &
+ ~(NDCR_INT_MASK | NDCR_ND_ARB_EN | NFCV1_NDCR_ARB_CNTL);
++ info->reg_ndcr |= (pdata->enable_arbiter) ? NDCR_ND_ARB_EN : 0;
+ info->ndtr0cs0 = nand_readl(info, NDTR0CS0);
+ info->ndtr1cs0 = nand_readl(info, NDTR1CS0);
+ return 0;
+@@ -1636,10 +1645,7 @@ static int pxa3xx_nand_scan(struct mtd_i
+ if (pdata->keep_config && !pxa3xx_nand_detect_config(info))
+ goto KEEP_CONFIG;
+
+- /* Set a default chunk size */
+- info->chunk_size = PAGE_CHUNK_SIZE;
+-
+- ret = pxa3xx_nand_config_flash(info);
++ ret = pxa3xx_nand_config_ident(info);
+ if (ret)
+ return ret;
+
+@@ -1652,7 +1658,6 @@ static int pxa3xx_nand_scan(struct mtd_i
+ }
+
+ KEEP_CONFIG:
+- info->reg_ndcr |= (pdata->enable_arbiter) ? NDCR_ND_ARB_EN : 0;
+ if (info->reg_ndcr & NDCR_DWIDTH_M)
+ chip->options |= NAND_BUSWIDTH_16;
+
+@@ -1737,6 +1742,10 @@ KEEP_CONFIG:
+ host->row_addr_cycles = 3;
+ else
+ host->row_addr_cycles = 2;
++
++ if (!pdata->keep_config)
++ pxa3xx_nand_config_tail(info);
++
+ return nand_scan_tail(mtd);
+ }
+
--- /dev/null
+From: Marcin Wojtas <mw@semihalf.com>
+Date: Mon, 14 Mar 2016 09:39:02 +0100
+Subject: [PATCH] bus: mvebu-mbus: provide api for obtaining IO and DRAM window
+ information
+
+This commit enables finding appropriate mbus window and obtaining its
+target id and attribute for given physical address in two separate
+routines, both for IO and DRAM windows. This functionality
+is needed for Armada XP/38x Network Controller's Buffer Manager and
+PnC configuration.
+
+[gregory.clement@free-electrons.com: Fix size test for
+mvebu_mbus_get_dram_win_info]
+
+Signed-off-by: Marcin Wojtas <mw@semihalf.com>
+[DRAM window information reference in LKv3.10]
+Signed-off-by: Evan Wang <xswang@marvell.com>
+Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+
+--- a/drivers/bus/mvebu-mbus.c
++++ b/drivers/bus/mvebu-mbus.c
+@@ -948,6 +948,58 @@ void mvebu_mbus_get_pcie_io_aperture(str
+ *res = mbus_state.pcie_io_aperture;
+ }
+
++int mvebu_mbus_get_dram_win_info(phys_addr_t phyaddr, u8 *target, u8 *attr)
++{
++ const struct mbus_dram_target_info *dram;
++ int i;
++
++ /* Get dram info */
++ dram = mv_mbus_dram_info();
++ if (!dram) {
++ pr_err("missing DRAM information\n");
++ return -ENODEV;
++ }
++
++ /* Try to find matching DRAM window for phyaddr */
++ for (i = 0; i < dram->num_cs; i++) {
++ const struct mbus_dram_window *cs = dram->cs + i;
++
++ if (cs->base <= phyaddr &&
++ phyaddr <= (cs->base + cs->size - 1)) {
++ *target = dram->mbus_dram_target_id;
++ *attr = cs->mbus_attr;
++ return 0;
++ }
++ }
++
++ pr_err("invalid dram address 0x%x\n", phyaddr);
++ return -EINVAL;
++}
++EXPORT_SYMBOL_GPL(mvebu_mbus_get_dram_win_info);
++
++int mvebu_mbus_get_io_win_info(phys_addr_t phyaddr, u32 *size, u8 *target,
++ u8 *attr)
++{
++ int win;
++
++ for (win = 0; win < mbus_state.soc->num_wins; win++) {
++ u64 wbase;
++ int enabled;
++
++ mvebu_mbus_read_window(&mbus_state, win, &enabled, &wbase,
++ size, target, attr, NULL);
++
++ if (!enabled)
++ continue;
++
++ if (wbase <= phyaddr && phyaddr <= wbase + *size)
++ return win;
++ }
++
++ return -EINVAL;
++}
++EXPORT_SYMBOL_GPL(mvebu_mbus_get_io_win_info);
++
+ static __init int mvebu_mbus_debugfs_init(void)
+ {
+ struct mvebu_mbus_state *s = &mbus_state;
+--- a/include/linux/mbus.h
++++ b/include/linux/mbus.h
+@@ -69,6 +69,9 @@ static inline const struct mbus_dram_tar
+ int mvebu_mbus_save_cpu_target(u32 *store_addr);
+ void mvebu_mbus_get_pcie_mem_aperture(struct resource *res);
+ void mvebu_mbus_get_pcie_io_aperture(struct resource *res);
++int mvebu_mbus_get_dram_win_info(phys_addr_t phyaddr, u8 *target, u8 *attr);
++int mvebu_mbus_get_io_win_info(phys_addr_t phyaddr, u32 *size, u8 *target,
++ u8 *attr);
+ int mvebu_mbus_add_window_remap_by_id(unsigned int target,
+ unsigned int attribute,
+ phys_addr_t base, size_t size,
--- /dev/null
+From: Stas Sergeev <stsp@list.ru>
+Date: Wed, 2 Dec 2015 20:33:56 +0300
+Subject: [PATCH] mvneta: consolidate autoneg enabling
+
+This moves autoneg-related bit manipulations to the single place.
+
+CC: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
+CC: netdev@vger.kernel.org
+CC: linux-kernel@vger.kernel.org
+
+Signed-off-by: Stas Sergeev <stsp@users.sourceforge.net>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+
+--- a/drivers/net/ethernet/marvell/mvneta.c
++++ b/drivers/net/ethernet/marvell/mvneta.c
+@@ -1067,15 +1067,28 @@ static void mvneta_defaults_set(struct m
+ MVNETA_GMAC_AN_SPEED_EN |
+ MVNETA_GMAC_AN_DUPLEX_EN;
+ mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val);
++
+ val = mvreg_read(pp, MVNETA_GMAC_CLOCK_DIVIDER);
+ val |= MVNETA_GMAC_1MS_CLOCK_ENABLE;
+ mvreg_write(pp, MVNETA_GMAC_CLOCK_DIVIDER, val);
++
++ val = mvreg_read(pp, MVNETA_GMAC_CTRL_2);
++ val |= MVNETA_GMAC2_INBAND_AN_ENABLE;
++ mvreg_write(pp, MVNETA_GMAC_CTRL_2, val);
+ } else {
+ val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG);
+ val &= ~(MVNETA_GMAC_INBAND_AN_ENABLE |
+ MVNETA_GMAC_AN_SPEED_EN |
+ MVNETA_GMAC_AN_DUPLEX_EN);
+ mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val);
++
++ val = mvreg_read(pp, MVNETA_GMAC_CLOCK_DIVIDER);
++ val &= ~MVNETA_GMAC_1MS_CLOCK_ENABLE;
++ mvreg_write(pp, MVNETA_GMAC_CLOCK_DIVIDER, val);
++
++ val = mvreg_read(pp, MVNETA_GMAC_CTRL_2);
++ val &= ~MVNETA_GMAC2_INBAND_AN_ENABLE;
++ mvreg_write(pp, MVNETA_GMAC_CTRL_2, val);
+ }
+
+ mvneta_set_ucast_table(pp, -1);
+@@ -3230,9 +3243,6 @@ static int mvneta_port_power_up(struct m
+ return -EINVAL;
+ }
+
+- if (pp->use_inband_status)
+- ctrl |= MVNETA_GMAC2_INBAND_AN_ENABLE;
+-
+ /* Cancel Port Reset */
+ ctrl &= ~MVNETA_GMAC2_PORT_RESET;
+ mvreg_write(pp, MVNETA_GMAC_CTRL_2, ctrl);
--- /dev/null
+From: Stas Sergeev <stsp@list.ru>
+Date: Wed, 2 Dec 2015 20:35:11 +0300
+Subject: [PATCH] mvneta: implement ethtool autonegotiation control
+
+This patch allows to do
+ethtool -s eth0 autoneg off
+ethtool -s eth0 autoneg on
+to disable or enable autonegotiation at run-time.
+Without that functionality, the only way to control the autonegotiation
+is to modify the device tree.
+
+This is needed if you plan to use the same kernel with
+different ethernet switches, the ones that support the in-band
+status and the ones that not.
+
+CC: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
+CC: netdev@vger.kernel.org
+CC: linux-kernel@vger.kernel.org
+
+Signed-off-by: Stas Sergeev <stsp@users.sourceforge.net>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+
+--- a/drivers/net/ethernet/marvell/mvneta.c
++++ b/drivers/net/ethernet/marvell/mvneta.c
+@@ -371,7 +371,7 @@ struct mvneta_port {
+ unsigned int duplex;
+ unsigned int speed;
+ unsigned int tx_csum_limit;
+- int use_inband_status:1;
++ unsigned int use_inband_status:1;
+
+ u64 ethtool_stats[ARRAY_SIZE(mvneta_statistics)];
+ };
+@@ -973,6 +973,44 @@ static void mvneta_set_other_mcast_table
+ mvreg_write(pp, MVNETA_DA_FILT_OTH_MCAST + offset, val);
+ }
+
++static void mvneta_set_autoneg(struct mvneta_port *pp, int enable)
++{
++ u32 val;
++
++ if (enable) {
++ val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG);
++ val &= ~(MVNETA_GMAC_FORCE_LINK_PASS |
++ MVNETA_GMAC_FORCE_LINK_DOWN |
++ MVNETA_GMAC_AN_FLOW_CTRL_EN);
++ val |= MVNETA_GMAC_INBAND_AN_ENABLE |
++ MVNETA_GMAC_AN_SPEED_EN |
++ MVNETA_GMAC_AN_DUPLEX_EN;
++ mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val);
++
++ val = mvreg_read(pp, MVNETA_GMAC_CLOCK_DIVIDER);
++ val |= MVNETA_GMAC_1MS_CLOCK_ENABLE;
++ mvreg_write(pp, MVNETA_GMAC_CLOCK_DIVIDER, val);
++
++ val = mvreg_read(pp, MVNETA_GMAC_CTRL_2);
++ val |= MVNETA_GMAC2_INBAND_AN_ENABLE;
++ mvreg_write(pp, MVNETA_GMAC_CTRL_2, val);
++ } else {
++ val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG);
++ val &= ~(MVNETA_GMAC_INBAND_AN_ENABLE |
++ MVNETA_GMAC_AN_SPEED_EN |
++ MVNETA_GMAC_AN_DUPLEX_EN);
++ mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val);
++
++ val = mvreg_read(pp, MVNETA_GMAC_CLOCK_DIVIDER);
++ val &= ~MVNETA_GMAC_1MS_CLOCK_ENABLE;
++ mvreg_write(pp, MVNETA_GMAC_CLOCK_DIVIDER, val);
++
++ val = mvreg_read(pp, MVNETA_GMAC_CTRL_2);
++ val &= ~MVNETA_GMAC2_INBAND_AN_ENABLE;
++ mvreg_write(pp, MVNETA_GMAC_CTRL_2, val);
++ }
++}
++
+ /* This method sets defaults to the NETA port:
+ * Clears interrupt Cause and Mask registers.
+ * Clears all MAC tables.
+@@ -1058,39 +1096,7 @@ static void mvneta_defaults_set(struct m
+ val &= ~MVNETA_PHY_POLLING_ENABLE;
+ mvreg_write(pp, MVNETA_UNIT_CONTROL, val);
+
+- if (pp->use_inband_status) {
+- val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG);
+- val &= ~(MVNETA_GMAC_FORCE_LINK_PASS |
+- MVNETA_GMAC_FORCE_LINK_DOWN |
+- MVNETA_GMAC_AN_FLOW_CTRL_EN);
+- val |= MVNETA_GMAC_INBAND_AN_ENABLE |
+- MVNETA_GMAC_AN_SPEED_EN |
+- MVNETA_GMAC_AN_DUPLEX_EN;
+- mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val);
+-
+- val = mvreg_read(pp, MVNETA_GMAC_CLOCK_DIVIDER);
+- val |= MVNETA_GMAC_1MS_CLOCK_ENABLE;
+- mvreg_write(pp, MVNETA_GMAC_CLOCK_DIVIDER, val);
+-
+- val = mvreg_read(pp, MVNETA_GMAC_CTRL_2);
+- val |= MVNETA_GMAC2_INBAND_AN_ENABLE;
+- mvreg_write(pp, MVNETA_GMAC_CTRL_2, val);
+- } else {
+- val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG);
+- val &= ~(MVNETA_GMAC_INBAND_AN_ENABLE |
+- MVNETA_GMAC_AN_SPEED_EN |
+- MVNETA_GMAC_AN_DUPLEX_EN);
+- mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val);
+-
+- val = mvreg_read(pp, MVNETA_GMAC_CLOCK_DIVIDER);
+- val &= ~MVNETA_GMAC_1MS_CLOCK_ENABLE;
+- mvreg_write(pp, MVNETA_GMAC_CLOCK_DIVIDER, val);
+-
+- val = mvreg_read(pp, MVNETA_GMAC_CTRL_2);
+- val &= ~MVNETA_GMAC2_INBAND_AN_ENABLE;
+- mvreg_write(pp, MVNETA_GMAC_CTRL_2, val);
+- }
+-
++ mvneta_set_autoneg(pp, pp->use_inband_status);
+ mvneta_set_ucast_table(pp, -1);
+ mvneta_set_special_mcast_table(pp, -1);
+ mvneta_set_other_mcast_table(pp, -1);
+@@ -2956,10 +2962,43 @@ int mvneta_ethtool_get_settings(struct n
+ int mvneta_ethtool_set_settings(struct net_device *dev, struct ethtool_cmd *cmd)
+ {
+ struct mvneta_port *pp = netdev_priv(dev);
++ struct phy_device *phydev = pp->phy_dev;
+
+- if (!pp->phy_dev)
++ if (!phydev)
+ return -ENODEV;
+
++ if ((cmd->autoneg == AUTONEG_ENABLE) != pp->use_inband_status) {
++ u32 val;
++
++ mvneta_set_autoneg(pp, cmd->autoneg == AUTONEG_ENABLE);
++
++ if (cmd->autoneg == AUTONEG_DISABLE) {
++ val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG);
++ val &= ~(MVNETA_GMAC_CONFIG_MII_SPEED |
++ MVNETA_GMAC_CONFIG_GMII_SPEED |
++ MVNETA_GMAC_CONFIG_FULL_DUPLEX);
++
++ if (phydev->duplex)
++ val |= MVNETA_GMAC_CONFIG_FULL_DUPLEX;
++
++ if (phydev->speed == SPEED_1000)
++ val |= MVNETA_GMAC_CONFIG_GMII_SPEED;
++ else if (phydev->speed == SPEED_100)
++ val |= MVNETA_GMAC_CONFIG_MII_SPEED;
++
++ mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val);
++ }
++
++ pp->use_inband_status = (cmd->autoneg == AUTONEG_ENABLE);
++ netdev_info(pp->dev, "autoneg status set to %i\n",
++ pp->use_inband_status);
++
++ if (netif_running(dev)) {
++ mvneta_port_down(pp);
++ mvneta_port_up(pp);
++ }
++ }
++
+ return phy_ethtool_sset(pp->phy_dev, cmd);
+ }
+
--- /dev/null
+From: Gregory CLEMENT <gregory.clement@free-electrons.com>
+Date: Wed, 9 Dec 2015 18:23:48 +0100
+Subject: [PATCH] net: mvneta: Make the default queue related for each port
+
+Instead of using the same default queue for all the port. Move it in the
+port struct. It will allow have a different default queue for each port.
+
+Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+
+--- a/drivers/net/ethernet/marvell/mvneta.c
++++ b/drivers/net/ethernet/marvell/mvneta.c
+@@ -356,6 +356,7 @@ struct mvneta_port {
+ struct mvneta_tx_queue *txqs;
+ struct net_device *dev;
+ struct notifier_block cpu_notifier;
++ int rxq_def;
+
+ /* Core clock */
+ struct clk *clk;
+@@ -819,7 +820,7 @@ static void mvneta_port_up(struct mvneta
+ mvreg_write(pp, MVNETA_TXQ_CMD, q_map);
+
+ /* Enable all initialized RXQs. */
+- mvreg_write(pp, MVNETA_RXQ_CMD, BIT(rxq_def));
++ mvreg_write(pp, MVNETA_RXQ_CMD, BIT(pp->rxq_def));
+ }
+
+ /* Stop the Ethernet port activity */
+@@ -1067,7 +1068,7 @@ static void mvneta_defaults_set(struct m
+ mvreg_write(pp, MVNETA_ACC_MODE, val);
+
+ /* Update val of portCfg register accordingly with all RxQueue types */
+- val = MVNETA_PORT_CONFIG_DEFL_VALUE(rxq_def);
++ val = MVNETA_PORT_CONFIG_DEFL_VALUE(pp->rxq_def);
+ mvreg_write(pp, MVNETA_PORT_CONFIG, val);
+
+ val = 0;
+@@ -2101,19 +2102,19 @@ static void mvneta_set_rx_mode(struct ne
+ if (dev->flags & IFF_PROMISC) {
+ /* Accept all: Multicast + Unicast */
+ mvneta_rx_unicast_promisc_set(pp, 1);
+- mvneta_set_ucast_table(pp, rxq_def);
+- mvneta_set_special_mcast_table(pp, rxq_def);
+- mvneta_set_other_mcast_table(pp, rxq_def);
++ mvneta_set_ucast_table(pp, pp->rxq_def);
++ mvneta_set_special_mcast_table(pp, pp->rxq_def);
++ mvneta_set_other_mcast_table(pp, pp->rxq_def);
+ } else {
+ /* Accept single Unicast */
+ mvneta_rx_unicast_promisc_set(pp, 0);
+ mvneta_set_ucast_table(pp, -1);
+- mvneta_mac_addr_set(pp, dev->dev_addr, rxq_def);
++ mvneta_mac_addr_set(pp, dev->dev_addr, pp->rxq_def);
+
+ if (dev->flags & IFF_ALLMULTI) {
+ /* Accept all multicast */
+- mvneta_set_special_mcast_table(pp, rxq_def);
+- mvneta_set_other_mcast_table(pp, rxq_def);
++ mvneta_set_special_mcast_table(pp, pp->rxq_def);
++ mvneta_set_other_mcast_table(pp, pp->rxq_def);
+ } else {
+ /* Accept only initialized multicast */
+ mvneta_set_special_mcast_table(pp, -1);
+@@ -2122,7 +2123,7 @@ static void mvneta_set_rx_mode(struct ne
+ if (!netdev_mc_empty(dev)) {
+ netdev_for_each_mc_addr(ha, dev) {
+ mvneta_mcast_addr_set(pp, ha->addr,
+- rxq_def);
++ pp->rxq_def);
+ }
+ }
+ }
+@@ -2205,7 +2206,7 @@ static int mvneta_poll(struct napi_struc
+ * RX packets
+ */
+ cause_rx_tx |= port->cause_rx_tx;
+- rx_done = mvneta_rx(pp, budget, &pp->rxqs[rxq_def]);
++ rx_done = mvneta_rx(pp, budget, &pp->rxqs[pp->rxq_def]);
+ budget -= rx_done;
+
+ if (budget > 0) {
+@@ -2418,17 +2419,17 @@ static void mvneta_cleanup_txqs(struct m
+ /* Cleanup all Rx queues */
+ static void mvneta_cleanup_rxqs(struct mvneta_port *pp)
+ {
+- mvneta_rxq_deinit(pp, &pp->rxqs[rxq_def]);
++ mvneta_rxq_deinit(pp, &pp->rxqs[pp->rxq_def]);
+ }
+
+
+ /* Init all Rx queues */
+ static int mvneta_setup_rxqs(struct mvneta_port *pp)
+ {
+- int err = mvneta_rxq_init(pp, &pp->rxqs[rxq_def]);
++ int err = mvneta_rxq_init(pp, &pp->rxqs[pp->rxq_def]);
+ if (err) {
+ netdev_err(pp->dev, "%s: can't create rxq=%d\n",
+- __func__, rxq_def);
++ __func__, pp->rxq_def);
+ mvneta_cleanup_rxqs(pp);
+ return err;
+ }
+@@ -2634,7 +2635,7 @@ static int mvneta_set_mac_addr(struct ne
+ mvneta_mac_addr_set(pp, dev->dev_addr, -1);
+
+ /* Set new addr in hw */
+- mvneta_mac_addr_set(pp, sockaddr->sa_data, rxq_def);
++ mvneta_mac_addr_set(pp, sockaddr->sa_data, pp->rxq_def);
+
+ eth_commit_mac_addr_change(dev, addr);
+ return 0;
+@@ -2753,7 +2754,7 @@ static void mvneta_percpu_elect(struct m
+ {
+ int online_cpu_idx, cpu, i = 0;
+
+- online_cpu_idx = rxq_def % num_online_cpus();
++ online_cpu_idx = pp->rxq_def % num_online_cpus();
+
+ for_each_online_cpu(cpu) {
+ if (i == online_cpu_idx)
+@@ -3363,6 +3364,8 @@ static int mvneta_probe(struct platform_
+ strcmp(managed, "in-band-status") == 0);
+ pp->cpu_notifier.notifier_call = mvneta_percpu_notifier;
+
++ pp->rxq_def = rxq_def;
++
+ pp->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(pp->clk)) {
+ err = PTR_ERR(pp->clk);
--- /dev/null
+From: Gregory CLEMENT <gregory.clement@free-electrons.com>
+Date: Wed, 9 Dec 2015 18:23:49 +0100
+Subject: [PATCH] net: mvneta: Associate RX queues with each CPU
+
+We enable the percpu interrupt for all the CPU and we just associate a
+CPU to a few queue at the neta level. The mapping between the CPUs and
+the queues is static. The queues are associated to the CPU module the
+number of CPUs. However currently we only use on RX queue for a given
+Ethernet port.
+
+Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+
+--- a/drivers/net/ethernet/marvell/mvneta.c
++++ b/drivers/net/ethernet/marvell/mvneta.c
+@@ -110,9 +110,16 @@
+ #define MVNETA_CPU_MAP(cpu) (0x2540 + ((cpu) << 2))
+ #define MVNETA_CPU_RXQ_ACCESS_ALL_MASK 0x000000ff
+ #define MVNETA_CPU_TXQ_ACCESS_ALL_MASK 0x0000ff00
++#define MVNETA_CPU_RXQ_ACCESS(rxq) BIT(rxq)
+ #define MVNETA_RXQ_TIME_COAL_REG(q) (0x2580 + ((q) << 2))
+
+-/* Exception Interrupt Port/Queue Cause register */
++/* Exception Interrupt Port/Queue Cause register
++ *
++ * Their behavior depend of the mapping done using the PCPX2Q
++ * registers. For a given CPU if the bit associated to a queue is not
++ * set, then for the register a read from this CPU will always return
++ * 0 and a write won't do anything
++ */
+
+ #define MVNETA_INTR_NEW_CAUSE 0x25a0
+ #define MVNETA_INTR_NEW_MASK 0x25a4
+@@ -820,7 +827,13 @@ static void mvneta_port_up(struct mvneta
+ mvreg_write(pp, MVNETA_TXQ_CMD, q_map);
+
+ /* Enable all initialized RXQs. */
+- mvreg_write(pp, MVNETA_RXQ_CMD, BIT(pp->rxq_def));
++ for (queue = 0; queue < rxq_number; queue++) {
++ struct mvneta_rx_queue *rxq = &pp->rxqs[queue];
++
++ if (rxq->descs != NULL)
++ q_map |= (1 << queue);
++ }
++ mvreg_write(pp, MVNETA_RXQ_CMD, q_map);
+ }
+
+ /* Stop the Ethernet port activity */
+@@ -1026,6 +1039,7 @@ static void mvneta_defaults_set(struct m
+ int cpu;
+ int queue;
+ u32 val;
++ int max_cpu = num_present_cpus();
+
+ /* Clear all Cause registers */
+ mvreg_write(pp, MVNETA_INTR_NEW_CAUSE, 0);
+@@ -1041,13 +1055,23 @@ static void mvneta_defaults_set(struct m
+ /* Enable MBUS Retry bit16 */
+ mvreg_write(pp, MVNETA_MBUS_RETRY, 0x20);
+
+- /* Set CPU queue access map - all CPUs have access to all RX
+- * queues and to all TX queues
++ /* Set CPU queue access map. CPUs are assigned to the RX
++ * queues modulo their number and all the TX queues are
++ * assigned to the CPU associated to the default RX queue.
+ */
+- for_each_present_cpu(cpu)
+- mvreg_write(pp, MVNETA_CPU_MAP(cpu),
+- (MVNETA_CPU_RXQ_ACCESS_ALL_MASK |
+- MVNETA_CPU_TXQ_ACCESS_ALL_MASK));
++ for_each_present_cpu(cpu) {
++ int rxq_map = 0, txq_map = 0;
++ int rxq;
++
++ for (rxq = 0; rxq < rxq_number; rxq++)
++ if ((rxq % max_cpu) == cpu)
++ rxq_map |= MVNETA_CPU_RXQ_ACCESS(rxq);
++
++ if (cpu == rxq_def)
++ txq_map = MVNETA_CPU_TXQ_ACCESS_ALL_MASK;
++
++ mvreg_write(pp, MVNETA_CPU_MAP(cpu), rxq_map | txq_map);
++ }
+
+ /* Reset RX and TX DMAs */
+ mvreg_write(pp, MVNETA_PORT_RX_RESET, MVNETA_PORT_RX_DMA_RESET);
+@@ -2174,6 +2198,7 @@ static int mvneta_poll(struct napi_struc
+ {
+ int rx_done = 0;
+ u32 cause_rx_tx;
++ int rx_queue;
+ struct mvneta_port *pp = netdev_priv(napi->dev);
+ struct mvneta_pcpu_port *port = this_cpu_ptr(pp->ports);
+
+@@ -2205,8 +2230,15 @@ static int mvneta_poll(struct napi_struc
+ /* For the case where the last mvneta_poll did not process all
+ * RX packets
+ */
++ rx_queue = fls(((cause_rx_tx >> 8) & 0xff));
++
+ cause_rx_tx |= port->cause_rx_tx;
+- rx_done = mvneta_rx(pp, budget, &pp->rxqs[pp->rxq_def]);
++
++ if (rx_queue) {
++ rx_queue = rx_queue - 1;
++ rx_done = mvneta_rx(pp, budget, &pp->rxqs[rx_queue]);
++ }
++
+ budget -= rx_done;
+
+ if (budget > 0) {
+@@ -2419,19 +2451,27 @@ static void mvneta_cleanup_txqs(struct m
+ /* Cleanup all Rx queues */
+ static void mvneta_cleanup_rxqs(struct mvneta_port *pp)
+ {
+- mvneta_rxq_deinit(pp, &pp->rxqs[pp->rxq_def]);
++ int queue;
++
++ for (queue = 0; queue < txq_number; queue++)
++ mvneta_rxq_deinit(pp, &pp->rxqs[queue]);
+ }
+
+
+ /* Init all Rx queues */
+ static int mvneta_setup_rxqs(struct mvneta_port *pp)
+ {
+- int err = mvneta_rxq_init(pp, &pp->rxqs[pp->rxq_def]);
+- if (err) {
+- netdev_err(pp->dev, "%s: can't create rxq=%d\n",
+- __func__, pp->rxq_def);
+- mvneta_cleanup_rxqs(pp);
+- return err;
++ int queue;
++
++ for (queue = 0; queue < rxq_number; queue++) {
++ int err = mvneta_rxq_init(pp, &pp->rxqs[queue]);
++
++ if (err) {
++ netdev_err(pp->dev, "%s: can't create rxq=%d\n",
++ __func__, queue);
++ mvneta_cleanup_rxqs(pp);
++ return err;
++ }
+ }
+
+ return 0;
+@@ -2455,6 +2495,19 @@ static int mvneta_setup_txqs(struct mvne
+ return 0;
+ }
+
++static void mvneta_percpu_unmask_interrupt(void *arg)
++{
++ struct mvneta_port *pp = arg;
++
++ /* All the queue are unmasked, but actually only the ones
++ * maped to this CPU will be unmasked
++ */
++ mvreg_write(pp, MVNETA_INTR_NEW_MASK,
++ MVNETA_RX_INTR_MASK_ALL |
++ MVNETA_TX_INTR_MASK_ALL |
++ MVNETA_MISCINTR_INTR_MASK);
++}
++
+ static void mvneta_start_dev(struct mvneta_port *pp)
+ {
+ unsigned int cpu;
+@@ -2472,11 +2525,10 @@ static void mvneta_start_dev(struct mvne
+ napi_enable(&port->napi);
+ }
+
+- /* Unmask interrupts */
+- mvreg_write(pp, MVNETA_INTR_NEW_MASK,
+- MVNETA_RX_INTR_MASK(rxq_number) |
+- MVNETA_TX_INTR_MASK(txq_number) |
+- MVNETA_MISCINTR_INTR_MASK);
++ /* Unmask interrupts. It has to be done from each CPU */
++ for_each_online_cpu(cpu)
++ smp_call_function_single(cpu, mvneta_percpu_unmask_interrupt,
++ pp, true);
+ mvreg_write(pp, MVNETA_INTR_MISC_MASK,
+ MVNETA_CAUSE_PHY_STATUS_CHANGE |
+ MVNETA_CAUSE_LINK_CHANGE |
+@@ -2752,22 +2804,35 @@ static void mvneta_percpu_disable(void *
+
+ static void mvneta_percpu_elect(struct mvneta_port *pp)
+ {
+- int online_cpu_idx, cpu, i = 0;
++ int online_cpu_idx, max_cpu, cpu, i = 0;
+
+ online_cpu_idx = pp->rxq_def % num_online_cpus();
++ max_cpu = num_present_cpus();
+
+ for_each_online_cpu(cpu) {
+- if (i == online_cpu_idx)
+- /* Enable per-CPU interrupt on the one CPU we
+- * just elected
++ int rxq_map = 0, txq_map = 0;
++ int rxq;
++
++ for (rxq = 0; rxq < rxq_number; rxq++)
++ if ((rxq % max_cpu) == cpu)
++ rxq_map |= MVNETA_CPU_RXQ_ACCESS(rxq);
++
++ if (i == online_cpu_idx) {
++ /* Map the default receive queue and transmit
++ * queue to the elected CPU
+ */
+- smp_call_function_single(cpu, mvneta_percpu_enable,
+- pp, true);
+- else
+- /* Disable per-CPU interrupt on all the other CPU */
+- smp_call_function_single(cpu, mvneta_percpu_disable,
+- pp, true);
++ rxq_map |= MVNETA_CPU_RXQ_ACCESS(pp->rxq_def);
++ txq_map = MVNETA_CPU_TXQ_ACCESS_ALL_MASK;
++ }
++ mvreg_write(pp, MVNETA_CPU_MAP(cpu), rxq_map | txq_map);
++
++ /* Update the interrupt mask on each CPU according the
++ * new mapping
++ */
++ smp_call_function_single(cpu, mvneta_percpu_unmask_interrupt,
++ pp, true);
+ i++;
++
+ }
+ };
+
+@@ -2802,12 +2867,22 @@ static int mvneta_percpu_notifier(struct
+ mvreg_write(pp, MVNETA_INTR_MISC_MASK, 0);
+ napi_enable(&port->napi);
+
++
++ /* Enable per-CPU interrupts on the CPU that is
++ * brought up.
++ */
++ smp_call_function_single(cpu, mvneta_percpu_enable,
++ pp, true);
++
+ /* Enable per-CPU interrupt on the one CPU we care
+ * about.
+ */
+ mvneta_percpu_elect(pp);
+
+- /* Unmask all ethernet port interrupts */
++ /* Unmask all ethernet port interrupts, as this
++ * notifier is called for each CPU then the CPU to
++ * Queue mapping is applied
++ */
+ mvreg_write(pp, MVNETA_INTR_NEW_MASK,
+ MVNETA_RX_INTR_MASK(rxq_number) |
+ MVNETA_TX_INTR_MASK(txq_number) |
+@@ -2858,7 +2933,7 @@ static int mvneta_percpu_notifier(struct
+ static int mvneta_open(struct net_device *dev)
+ {
+ struct mvneta_port *pp = netdev_priv(dev);
+- int ret;
++ int ret, cpu;
+
+ pp->pkt_size = MVNETA_RX_PKT_SIZE(pp->dev->mtu);
+ pp->frag_size = SKB_DATA_ALIGN(MVNETA_RX_BUF_SIZE(pp->pkt_size)) +
+@@ -2888,8 +2963,13 @@ static int mvneta_open(struct net_device
+ */
+ mvneta_percpu_disable(pp);
+
+- /* Elect a CPU to handle our RX queue interrupt */
+- mvneta_percpu_elect(pp);
++ /* Enable per-CPU interrupt on all the CPU to handle our RX
++ * queue interrupts
++ */
++ for_each_online_cpu(cpu)
++ smp_call_function_single(cpu, mvneta_percpu_enable,
++ pp, true);
++
+
+ /* Register a CPU notifier to handle the case where our CPU
+ * might be taken offline.
--- /dev/null
+From: Gregory CLEMENT <gregory.clement@free-electrons.com>
+Date: Wed, 9 Dec 2015 18:23:50 +0100
+Subject: [PATCH] net: mvneta: Add naive RSS support
+
+This patch adds the support for the RSS related ethtool
+function. Currently it only uses one entry in the indirection table which
+allows associating an mvneta interface to a given CPU.
+
+Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
+Tested-by: Marcin Wojtas <mw@semihalf.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+
+--- a/drivers/net/ethernet/marvell/mvneta.c
++++ b/drivers/net/ethernet/marvell/mvneta.c
+@@ -261,6 +261,11 @@
+
+ #define MVNETA_TX_MTU_MAX 0x3ffff
+
++/* The RSS lookup table actually has 256 entries but we do not use
++ * them yet
++ */
++#define MVNETA_RSS_LU_TABLE_SIZE 1
++
+ /* TSO header size */
+ #define TSO_HEADER_SIZE 128
+
+@@ -382,6 +387,8 @@ struct mvneta_port {
+ unsigned int use_inband_status:1;
+
+ u64 ethtool_stats[ARRAY_SIZE(mvneta_statistics)];
++
++ u32 indir[MVNETA_RSS_LU_TABLE_SIZE];
+ };
+
+ /* The mvneta_tx_desc and mvneta_rx_desc structures describe the
+@@ -1067,7 +1074,7 @@ static void mvneta_defaults_set(struct m
+ if ((rxq % max_cpu) == cpu)
+ rxq_map |= MVNETA_CPU_RXQ_ACCESS(rxq);
+
+- if (cpu == rxq_def)
++ if (cpu == pp->rxq_def)
+ txq_map = MVNETA_CPU_TXQ_ACCESS_ALL_MASK;
+
+ mvreg_write(pp, MVNETA_CPU_MAP(cpu), rxq_map | txq_map);
+@@ -2508,6 +2515,18 @@ static void mvneta_percpu_unmask_interru
+ MVNETA_MISCINTR_INTR_MASK);
+ }
+
++static void mvneta_percpu_mask_interrupt(void *arg)
++{
++ struct mvneta_port *pp = arg;
++
++ /* All the queue are masked, but actually only the ones
++ * maped to this CPU will be masked
++ */
++ mvreg_write(pp, MVNETA_INTR_NEW_MASK, 0);
++ mvreg_write(pp, MVNETA_INTR_OLD_MASK, 0);
++ mvreg_write(pp, MVNETA_INTR_MISC_MASK, 0);
++}
++
+ static void mvneta_start_dev(struct mvneta_port *pp)
+ {
+ unsigned int cpu;
+@@ -3231,6 +3250,106 @@ static int mvneta_ethtool_get_sset_count
+ return -EOPNOTSUPP;
+ }
+
++static u32 mvneta_ethtool_get_rxfh_indir_size(struct net_device *dev)
++{
++ return MVNETA_RSS_LU_TABLE_SIZE;
++}
++
++static int mvneta_ethtool_get_rxnfc(struct net_device *dev,
++ struct ethtool_rxnfc *info,
++ u32 *rules __always_unused)
++{
++ switch (info->cmd) {
++ case ETHTOOL_GRXRINGS:
++ info->data = rxq_number;
++ return 0;
++ case ETHTOOL_GRXFH:
++ return -EOPNOTSUPP;
++ default:
++ return -EOPNOTSUPP;
++ }
++}
++
++static int mvneta_config_rss(struct mvneta_port *pp)
++{
++ int cpu;
++ u32 val;
++
++ netif_tx_stop_all_queues(pp->dev);
++
++ for_each_online_cpu(cpu)
++ smp_call_function_single(cpu, mvneta_percpu_mask_interrupt,
++ pp, true);
++
++ /* We have to synchronise on the napi of each CPU */
++ for_each_online_cpu(cpu) {
++ struct mvneta_pcpu_port *pcpu_port =
++ per_cpu_ptr(pp->ports, cpu);
++
++ napi_synchronize(&pcpu_port->napi);
++ napi_disable(&pcpu_port->napi);
++ }
++
++ pp->rxq_def = pp->indir[0];
++
++ /* Update unicast mapping */
++ mvneta_set_rx_mode(pp->dev);
++
++ /* Update val of portCfg register accordingly with all RxQueue types */
++ val = MVNETA_PORT_CONFIG_DEFL_VALUE(pp->rxq_def);
++ mvreg_write(pp, MVNETA_PORT_CONFIG, val);
++
++ /* Update the elected CPU matching the new rxq_def */
++ mvneta_percpu_elect(pp);
++
++ /* We have to synchronise on the napi of each CPU */
++ for_each_online_cpu(cpu) {
++ struct mvneta_pcpu_port *pcpu_port =
++ per_cpu_ptr(pp->ports, cpu);
++
++ napi_enable(&pcpu_port->napi);
++ }
++
++ netif_tx_start_all_queues(pp->dev);
++
++ return 0;
++}
++
++static int mvneta_ethtool_set_rxfh(struct net_device *dev, const u32 *indir,
++ const u8 *key, const u8 hfunc)
++{
++ struct mvneta_port *pp = netdev_priv(dev);
++ /* We require at least one supported parameter to be changed
++ * and no change in any of the unsupported parameters
++ */
++ if (key ||
++ (hfunc != ETH_RSS_HASH_NO_CHANGE && hfunc != ETH_RSS_HASH_TOP))
++ return -EOPNOTSUPP;
++
++ if (!indir)
++ return 0;
++
++ memcpy(pp->indir, indir, MVNETA_RSS_LU_TABLE_SIZE);
++
++ return mvneta_config_rss(pp);
++}
++
++static int mvneta_ethtool_get_rxfh(struct net_device *dev, u32 *indir, u8 *key,
++ u8 *hfunc)
++{
++ struct mvneta_port *pp = netdev_priv(dev);
++
++ if (hfunc)
++ *hfunc = ETH_RSS_HASH_TOP;
++
++ if (!indir)
++ return 0;
++
++ memcpy(indir, pp->indir, MVNETA_RSS_LU_TABLE_SIZE);
++
++ return 0;
++}
++
+ static const struct net_device_ops mvneta_netdev_ops = {
+ .ndo_open = mvneta_open,
+ .ndo_stop = mvneta_stop,
+@@ -3255,6 +3374,10 @@ const struct ethtool_ops mvneta_eth_tool
+ .get_strings = mvneta_ethtool_get_strings,
+ .get_ethtool_stats = mvneta_ethtool_get_stats,
+ .get_sset_count = mvneta_ethtool_get_sset_count,
++ .get_rxfh_indir_size = mvneta_ethtool_get_rxfh_indir_size,
++ .get_rxnfc = mvneta_ethtool_get_rxnfc,
++ .get_rxfh = mvneta_ethtool_get_rxfh,
++ .set_rxfh = mvneta_ethtool_set_rxfh,
+ };
+
+ /* Initialize hw */
+@@ -3446,6 +3569,8 @@ static int mvneta_probe(struct platform_
+
+ pp->rxq_def = rxq_def;
+
++ pp->indir[0] = rxq_def;
++
+ pp->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(pp->clk)) {
+ err = PTR_ERR(pp->clk);
--- /dev/null
+From: Gregory CLEMENT <gregory.clement@free-electrons.com>
+Date: Wed, 9 Dec 2015 18:23:51 +0100
+Subject: [PATCH] net: mvneta: Configure XPS support
+
+With this patch each CPU is associated with its own set of TX queues.
+
+It also setup the XPS with an initial configuration which set the
+affinity matching the hardware configuration.
+
+Suggested-by: Arnd Bergmann <arnd@arndb.de>
+Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+
+--- a/drivers/net/ethernet/marvell/mvneta.c
++++ b/drivers/net/ethernet/marvell/mvneta.c
+@@ -111,6 +111,7 @@
+ #define MVNETA_CPU_RXQ_ACCESS_ALL_MASK 0x000000ff
+ #define MVNETA_CPU_TXQ_ACCESS_ALL_MASK 0x0000ff00
+ #define MVNETA_CPU_RXQ_ACCESS(rxq) BIT(rxq)
++#define MVNETA_CPU_TXQ_ACCESS(txq) BIT(txq + 8)
+ #define MVNETA_RXQ_TIME_COAL_REG(q) (0x2580 + ((q) << 2))
+
+ /* Exception Interrupt Port/Queue Cause register
+@@ -514,6 +515,9 @@ struct mvneta_tx_queue {
+
+ /* DMA address of TSO headers */
+ dma_addr_t tso_hdrs_phys;
++
++ /* Affinity mask for CPUs*/
++ cpumask_t affinity_mask;
+ };
+
+ struct mvneta_rx_queue {
+@@ -1062,20 +1066,30 @@ static void mvneta_defaults_set(struct m
+ /* Enable MBUS Retry bit16 */
+ mvreg_write(pp, MVNETA_MBUS_RETRY, 0x20);
+
+- /* Set CPU queue access map. CPUs are assigned to the RX
+- * queues modulo their number and all the TX queues are
+- * assigned to the CPU associated to the default RX queue.
++ /* Set CPU queue access map. CPUs are assigned to the RX and
++ * TX queues modulo their number. If there is only one TX
++ * queue then it is assigned to the CPU associated to the
++ * default RX queue.
+ */
+ for_each_present_cpu(cpu) {
+ int rxq_map = 0, txq_map = 0;
+- int rxq;
++ int rxq, txq;
+
+ for (rxq = 0; rxq < rxq_number; rxq++)
+ if ((rxq % max_cpu) == cpu)
+ rxq_map |= MVNETA_CPU_RXQ_ACCESS(rxq);
+
+- if (cpu == pp->rxq_def)
+- txq_map = MVNETA_CPU_TXQ_ACCESS_ALL_MASK;
++ for (txq = 0; txq < txq_number; txq++)
++ if ((txq % max_cpu) == cpu)
++ txq_map |= MVNETA_CPU_TXQ_ACCESS(txq);
++
++ /* With only one TX queue we configure a special case
++ * which will allow to get all the irq on a single
++ * CPU
++ */
++ if (txq_number == 1)
++ txq_map = (cpu == pp->rxq_def) ?
++ MVNETA_CPU_TXQ_ACCESS(1) : 0;
+
+ mvreg_write(pp, MVNETA_CPU_MAP(cpu), rxq_map | txq_map);
+ }
+@@ -2362,6 +2376,8 @@ static void mvneta_rxq_deinit(struct mvn
+ static int mvneta_txq_init(struct mvneta_port *pp,
+ struct mvneta_tx_queue *txq)
+ {
++ int cpu;
++
+ txq->size = pp->tx_ring_size;
+
+ /* A queue must always have room for at least one skb.
+@@ -2414,6 +2430,14 @@ static int mvneta_txq_init(struct mvneta
+ }
+ mvneta_tx_done_pkts_coal_set(pp, txq, txq->done_pkts_coal);
+
++ /* Setup XPS mapping */
++ if (txq_number > 1)
++ cpu = txq->id % num_present_cpus();
++ else
++ cpu = pp->rxq_def % num_present_cpus();
++ cpumask_set_cpu(cpu, &txq->affinity_mask);
++ netif_set_xps_queue(pp->dev, &txq->affinity_mask, txq->id);
++
+ return 0;
+ }
+
+@@ -2836,13 +2860,23 @@ static void mvneta_percpu_elect(struct m
+ if ((rxq % max_cpu) == cpu)
+ rxq_map |= MVNETA_CPU_RXQ_ACCESS(rxq);
+
+- if (i == online_cpu_idx) {
+- /* Map the default receive queue and transmit
+- * queue to the elected CPU
++ if (i == online_cpu_idx)
++ /* Map the default receive queue queue to the
++ * elected CPU
+ */
+ rxq_map |= MVNETA_CPU_RXQ_ACCESS(pp->rxq_def);
+- txq_map = MVNETA_CPU_TXQ_ACCESS_ALL_MASK;
+- }
++
++ /* We update the TX queue map only if we have one
++ * queue. In this case we associate the TX queue to
++ * the CPU bound to the default RX queue
++ */
++ if (txq_number == 1)
++ txq_map = (i == online_cpu_idx) ?
++ MVNETA_CPU_TXQ_ACCESS(1) : 0;
++ else
++ txq_map = mvreg_read(pp, MVNETA_CPU_MAP(cpu)) &
++ MVNETA_CPU_TXQ_ACCESS_ALL_MASK;
++
+ mvreg_write(pp, MVNETA_CPU_MAP(cpu), rxq_map | txq_map);
+
+ /* Update the interrupt mask on each CPU according the
--- /dev/null
+From: Jisheng Zhang <jszhang@marvell.com>
+Date: Wed, 20 Jan 2016 16:36:25 +0800
+Subject: [PATCH] net: mvneta: fix trivial cut-off issue in
+ mvneta_ethtool_update_stats
+
+When s->type is T_REG_64, the high 32bits are lost in val. This patch
+fixes this trivial issue.
+
+Signed-off-by: Jisheng Zhang <jszhang@marvell.com>
+Fixes: 9b0cdefa4cd5 ("net: mvneta: add ethtool statistics")
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+
+--- a/drivers/net/ethernet/marvell/mvneta.c
++++ b/drivers/net/ethernet/marvell/mvneta.c
+@@ -3242,26 +3242,25 @@ static void mvneta_ethtool_update_stats(
+ const struct mvneta_statistic *s;
+ void __iomem *base = pp->base;
+ u32 high, low, val;
++ u64 val64;
+ int i;
+
+ for (i = 0, s = mvneta_statistics;
+ s < mvneta_statistics + ARRAY_SIZE(mvneta_statistics);
+ s++, i++) {
+- val = 0;
+-
+ switch (s->type) {
+ case T_REG_32:
+ val = readl_relaxed(base + s->offset);
++ pp->ethtool_stats[i] += val;
+ break;
+ case T_REG_64:
+ /* Docs say to read low 32-bit then high */
+ low = readl_relaxed(base + s->offset);
+ high = readl_relaxed(base + s->offset + 4);
+- val = (u64)high << 32 | low;
++ val64 = (u64)high << 32 | low;
++ pp->ethtool_stats[i] += val64;
+ break;
+ }
+-
+- pp->ethtool_stats[i] += val;
+ }
+ }
+
--- /dev/null
+From: Gregory CLEMENT <gregory.clement@free-electrons.com>
+Date: Thu, 4 Feb 2016 22:09:23 +0100
+Subject: [PATCH] net: mvneta: Fix for_each_present_cpu usage
+
+This patch convert the for_each_present in on_each_cpu, instead of
+applying on the present cpus it will be applied only on the online cpus.
+This fix a bug reported on
+http://thread.gmane.org/gmane.linux.ports.arm.kernel/468173.
+
+Using the macro on_each_cpu (instead of a for_each_* loop) also ensures
+that all the calls will be done all at once.
+
+Fixes: f86428854480 ("net: mvneta: Statically assign queues to CPUs")
+Reported-by: Stefan Roese <stefan.roese@gmail.com>
+Suggested-by: Jisheng Zhang <jszhang@marvell.com>
+Suggested-by: Russell King <rmk+kernel@arm.linux.org.uk>
+Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+
+--- a/drivers/net/ethernet/marvell/mvneta.c
++++ b/drivers/net/ethernet/marvell/mvneta.c
+@@ -2562,7 +2562,7 @@ static void mvneta_start_dev(struct mvne
+ mvneta_port_enable(pp);
+
+ /* Enable polling on the port */
+- for_each_present_cpu(cpu) {
++ for_each_online_cpu(cpu) {
+ struct mvneta_pcpu_port *port = per_cpu_ptr(pp->ports, cpu);
+
+ napi_enable(&port->napi);
+@@ -2587,7 +2587,7 @@ static void mvneta_stop_dev(struct mvnet
+
+ phy_stop(pp->phy_dev);
+
+- for_each_present_cpu(cpu) {
++ for_each_online_cpu(cpu) {
+ struct mvneta_pcpu_port *port = per_cpu_ptr(pp->ports, cpu);
+
+ napi_disable(&port->napi);
+@@ -3055,13 +3055,11 @@ err_cleanup_rxqs:
+ static int mvneta_stop(struct net_device *dev)
+ {
+ struct mvneta_port *pp = netdev_priv(dev);
+- int cpu;
+
+ mvneta_stop_dev(pp);
+ mvneta_mdio_remove(pp);
+ unregister_cpu_notifier(&pp->cpu_notifier);
+- for_each_present_cpu(cpu)
+- smp_call_function_single(cpu, mvneta_percpu_disable, pp, true);
++ on_each_cpu(mvneta_percpu_disable, pp, true);
+ free_percpu_irq(dev->irq, pp->ports);
+ mvneta_cleanup_rxqs(pp);
+ mvneta_cleanup_txqs(pp);
--- /dev/null
+From: Gregory CLEMENT <gregory.clement@free-electrons.com>
+Date: Thu, 4 Feb 2016 22:09:24 +0100
+Subject: [PATCH] net: mvneta: Fix the CPU choice in mvneta_percpu_elect
+
+When passing to the management of multiple RX queue, the
+mvneta_percpu_elect function was broken. The use of the modulo can lead
+to elect the wrong cpu. For example with rxq_def=2, if the CPU 2 goes
+offline and then online, we ended with the third RX queue activated in
+the same time on CPU 0 and CPU2, which lead to a kernel crash.
+
+With this fix, we don't try to get "the closer" CPU if the default CPU is
+gone, now we just use CPU 0 which always be there. Thanks to this, the
+code becomes more readable, easier to maintain and more predicable.
+
+Cc: stable@vger.kernel.org
+Fixes: 2dcf75e2793c ("net: mvneta: Associate RX queues with each CPU")
+Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+
+--- a/drivers/net/ethernet/marvell/mvneta.c
++++ b/drivers/net/ethernet/marvell/mvneta.c
+@@ -2847,9 +2847,14 @@ static void mvneta_percpu_disable(void *
+
+ static void mvneta_percpu_elect(struct mvneta_port *pp)
+ {
+- int online_cpu_idx, max_cpu, cpu, i = 0;
++ int elected_cpu = 0, max_cpu, cpu, i = 0;
++
++ /* Use the cpu associated to the rxq when it is online, in all
++ * the other cases, use the cpu 0 which can't be offline.
++ */
++ if (cpu_online(pp->rxq_def))
++ elected_cpu = pp->rxq_def;
+
+- online_cpu_idx = pp->rxq_def % num_online_cpus();
+ max_cpu = num_present_cpus();
+
+ for_each_online_cpu(cpu) {
+@@ -2860,7 +2865,7 @@ static void mvneta_percpu_elect(struct m
+ if ((rxq % max_cpu) == cpu)
+ rxq_map |= MVNETA_CPU_RXQ_ACCESS(rxq);
+
+- if (i == online_cpu_idx)
++ if (cpu == elected_cpu)
+ /* Map the default receive queue queue to the
+ * elected CPU
+ */
+@@ -2871,7 +2876,7 @@ static void mvneta_percpu_elect(struct m
+ * the CPU bound to the default RX queue
+ */
+ if (txq_number == 1)
+- txq_map = (i == online_cpu_idx) ?
++ txq_map = (cpu == elected_cpu) ?
+ MVNETA_CPU_TXQ_ACCESS(1) : 0;
+ else
+ txq_map = mvreg_read(pp, MVNETA_CPU_MAP(cpu)) &
--- /dev/null
+From: Gregory CLEMENT <gregory.clement@free-electrons.com>
+Date: Thu, 4 Feb 2016 22:09:25 +0100
+Subject: [PATCH] net: mvneta: Use on_each_cpu when possible
+
+Instead of using a for_each_* loop in which we just call the
+smp_call_function_single macro, it is more simple to directly use the
+on_each_cpu macro. Moreover, this macro ensures that the calls will be
+done all at once.
+
+Suggested-by: Russell King <rmk+kernel@arm.linux.org.uk>
+Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+
+--- a/drivers/net/ethernet/marvell/mvneta.c
++++ b/drivers/net/ethernet/marvell/mvneta.c
+@@ -2553,7 +2553,7 @@ static void mvneta_percpu_mask_interrupt
+
+ static void mvneta_start_dev(struct mvneta_port *pp)
+ {
+- unsigned int cpu;
++ int cpu;
+
+ mvneta_max_rx_size_set(pp, pp->pkt_size);
+ mvneta_txq_max_tx_size_set(pp, pp->pkt_size);
+@@ -2569,9 +2569,8 @@ static void mvneta_start_dev(struct mvne
+ }
+
+ /* Unmask interrupts. It has to be done from each CPU */
+- for_each_online_cpu(cpu)
+- smp_call_function_single(cpu, mvneta_percpu_unmask_interrupt,
+- pp, true);
++ on_each_cpu(mvneta_percpu_unmask_interrupt, pp, true);
++
+ mvreg_write(pp, MVNETA_INTR_MISC_MASK,
+ MVNETA_CAUSE_PHY_STATUS_CHANGE |
+ MVNETA_CAUSE_LINK_CHANGE |
+@@ -2991,7 +2990,7 @@ static int mvneta_percpu_notifier(struct
+ static int mvneta_open(struct net_device *dev)
+ {
+ struct mvneta_port *pp = netdev_priv(dev);
+- int ret, cpu;
++ int ret;
+
+ pp->pkt_size = MVNETA_RX_PKT_SIZE(pp->dev->mtu);
+ pp->frag_size = SKB_DATA_ALIGN(MVNETA_RX_BUF_SIZE(pp->pkt_size)) +
+@@ -3024,9 +3023,7 @@ static int mvneta_open(struct net_device
+ /* Enable per-CPU interrupt on all the CPU to handle our RX
+ * queue interrupts
+ */
+- for_each_online_cpu(cpu)
+- smp_call_function_single(cpu, mvneta_percpu_enable,
+- pp, true);
++ on_each_cpu(mvneta_percpu_enable, pp, true);
+
+
+ /* Register a CPU notifier to handle the case where our CPU
+@@ -3313,9 +3310,7 @@ static int mvneta_config_rss(struct mvn
+
+ netif_tx_stop_all_queues(pp->dev);
+
+- for_each_online_cpu(cpu)
+- smp_call_function_single(cpu, mvneta_percpu_mask_interrupt,
+- pp, true);
++ on_each_cpu(mvneta_percpu_mask_interrupt, pp, true);
+
+ /* We have to synchronise on the napi of each CPU */
+ for_each_online_cpu(cpu) {
--- /dev/null
+From: Gregory CLEMENT <gregory.clement@free-electrons.com>
+Date: Thu, 4 Feb 2016 22:09:27 +0100
+Subject: [PATCH] net: mvneta: Modify the queue related fields from each cpu
+
+In the MVNETA_INTR_* registers, the queues related fields are per cpu,
+according to the datasheet (comment in [] are added by me):
+"In a multi-CPU system, bits of RX[or TX] queues for which the access by
+the reading[or writing] CPU is disabled are read as 0, and cannot be
+cleared[or written]."
+
+That means that each time we want to manipulate these bits we had to do
+it on each cpu and not only on the current cpu.
+
+Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+
+--- a/drivers/net/ethernet/marvell/mvneta.c
++++ b/drivers/net/ethernet/marvell/mvneta.c
+@@ -1036,6 +1036,43 @@ static void mvneta_set_autoneg(struct mv
+ }
+ }
+
++static void mvneta_percpu_unmask_interrupt(void *arg)
++{
++ struct mvneta_port *pp = arg;
++
++ /* All the queue are unmasked, but actually only the ones
++ * mapped to this CPU will be unmasked
++ */
++ mvreg_write(pp, MVNETA_INTR_NEW_MASK,
++ MVNETA_RX_INTR_MASK_ALL |
++ MVNETA_TX_INTR_MASK_ALL |
++ MVNETA_MISCINTR_INTR_MASK);
++}
++
++static void mvneta_percpu_mask_interrupt(void *arg)
++{
++ struct mvneta_port *pp = arg;
++
++ /* All the queue are masked, but actually only the ones
++ * mapped to this CPU will be masked
++ */
++ mvreg_write(pp, MVNETA_INTR_NEW_MASK, 0);
++ mvreg_write(pp, MVNETA_INTR_OLD_MASK, 0);
++ mvreg_write(pp, MVNETA_INTR_MISC_MASK, 0);
++}
++
++static void mvneta_percpu_clear_intr_cause(void *arg)
++{
++ struct mvneta_port *pp = arg;
++
++ /* All the queue are cleared, but actually only the ones
++ * mapped to this CPU will be cleared
++ */
++ mvreg_write(pp, MVNETA_INTR_NEW_CAUSE, 0);
++ mvreg_write(pp, MVNETA_INTR_MISC_CAUSE, 0);
++ mvreg_write(pp, MVNETA_INTR_OLD_CAUSE, 0);
++}
++
+ /* This method sets defaults to the NETA port:
+ * Clears interrupt Cause and Mask registers.
+ * Clears all MAC tables.
+@@ -1053,14 +1090,10 @@ static void mvneta_defaults_set(struct m
+ int max_cpu = num_present_cpus();
+
+ /* Clear all Cause registers */
+- mvreg_write(pp, MVNETA_INTR_NEW_CAUSE, 0);
+- mvreg_write(pp, MVNETA_INTR_OLD_CAUSE, 0);
+- mvreg_write(pp, MVNETA_INTR_MISC_CAUSE, 0);
++ on_each_cpu(mvneta_percpu_clear_intr_cause, pp, true);
+
+ /* Mask all interrupts */
+- mvreg_write(pp, MVNETA_INTR_NEW_MASK, 0);
+- mvreg_write(pp, MVNETA_INTR_OLD_MASK, 0);
+- mvreg_write(pp, MVNETA_INTR_MISC_MASK, 0);
++ on_each_cpu(mvneta_percpu_mask_interrupt, pp, true);
+ mvreg_write(pp, MVNETA_INTR_ENABLE, 0);
+
+ /* Enable MBUS Retry bit16 */
+@@ -2526,31 +2559,6 @@ static int mvneta_setup_txqs(struct mvne
+ return 0;
+ }
+
+-static void mvneta_percpu_unmask_interrupt(void *arg)
+-{
+- struct mvneta_port *pp = arg;
+-
+- /* All the queue are unmasked, but actually only the ones
+- * maped to this CPU will be unmasked
+- */
+- mvreg_write(pp, MVNETA_INTR_NEW_MASK,
+- MVNETA_RX_INTR_MASK_ALL |
+- MVNETA_TX_INTR_MASK_ALL |
+- MVNETA_MISCINTR_INTR_MASK);
+-}
+-
+-static void mvneta_percpu_mask_interrupt(void *arg)
+-{
+- struct mvneta_port *pp = arg;
+-
+- /* All the queue are masked, but actually only the ones
+- * maped to this CPU will be masked
+- */
+- mvreg_write(pp, MVNETA_INTR_NEW_MASK, 0);
+- mvreg_write(pp, MVNETA_INTR_OLD_MASK, 0);
+- mvreg_write(pp, MVNETA_INTR_MISC_MASK, 0);
+-}
+-
+ static void mvneta_start_dev(struct mvneta_port *pp)
+ {
+ int cpu;
+@@ -2601,13 +2609,10 @@ static void mvneta_stop_dev(struct mvnet
+ mvneta_port_disable(pp);
+
+ /* Clear all ethernet port interrupts */
+- mvreg_write(pp, MVNETA_INTR_MISC_CAUSE, 0);
+- mvreg_write(pp, MVNETA_INTR_OLD_CAUSE, 0);
++ on_each_cpu(mvneta_percpu_clear_intr_cause, pp, true);
+
+ /* Mask all ethernet port interrupts */
+- mvreg_write(pp, MVNETA_INTR_NEW_MASK, 0);
+- mvreg_write(pp, MVNETA_INTR_OLD_MASK, 0);
+- mvreg_write(pp, MVNETA_INTR_MISC_MASK, 0);
++ on_each_cpu(mvneta_percpu_mask_interrupt, pp, true);
+
+ mvneta_tx_reset(pp);
+ mvneta_rx_reset(pp);
+@@ -2919,9 +2924,7 @@ static int mvneta_percpu_notifier(struct
+ }
+
+ /* Mask all ethernet port interrupts */
+- mvreg_write(pp, MVNETA_INTR_NEW_MASK, 0);
+- mvreg_write(pp, MVNETA_INTR_OLD_MASK, 0);
+- mvreg_write(pp, MVNETA_INTR_MISC_MASK, 0);
++ on_each_cpu(mvneta_percpu_mask_interrupt, pp, true);
+ napi_enable(&port->napi);
+
+
+@@ -2936,14 +2939,8 @@ static int mvneta_percpu_notifier(struct
+ */
+ mvneta_percpu_elect(pp);
+
+- /* Unmask all ethernet port interrupts, as this
+- * notifier is called for each CPU then the CPU to
+- * Queue mapping is applied
+- */
+- mvreg_write(pp, MVNETA_INTR_NEW_MASK,
+- MVNETA_RX_INTR_MASK(rxq_number) |
+- MVNETA_TX_INTR_MASK(txq_number) |
+- MVNETA_MISCINTR_INTR_MASK);
++ /* Unmask all ethernet port interrupts */
++ on_each_cpu(mvneta_percpu_unmask_interrupt, pp, true);
+ mvreg_write(pp, MVNETA_INTR_MISC_MASK,
+ MVNETA_CAUSE_PHY_STATUS_CHANGE |
+ MVNETA_CAUSE_LINK_CHANGE |
+@@ -2954,9 +2951,7 @@ static int mvneta_percpu_notifier(struct
+ case CPU_DOWN_PREPARE_FROZEN:
+ netif_tx_stop_all_queues(pp->dev);
+ /* Mask all ethernet port interrupts */
+- mvreg_write(pp, MVNETA_INTR_NEW_MASK, 0);
+- mvreg_write(pp, MVNETA_INTR_OLD_MASK, 0);
+- mvreg_write(pp, MVNETA_INTR_MISC_MASK, 0);
++ on_each_cpu(mvneta_percpu_mask_interrupt, pp, true);
+
+ napi_synchronize(&port->napi);
+ napi_disable(&port->napi);
+@@ -2972,10 +2967,7 @@ static int mvneta_percpu_notifier(struct
+ /* Check if a new CPU must be elected now this on is down */
+ mvneta_percpu_elect(pp);
+ /* Unmask all ethernet port interrupts */
+- mvreg_write(pp, MVNETA_INTR_NEW_MASK,
+- MVNETA_RX_INTR_MASK(rxq_number) |
+- MVNETA_TX_INTR_MASK(txq_number) |
+- MVNETA_MISCINTR_INTR_MASK);
++ on_each_cpu(mvneta_percpu_unmask_interrupt, pp, true);
+ mvreg_write(pp, MVNETA_INTR_MISC_MASK,
+ MVNETA_CAUSE_PHY_STATUS_CHANGE |
+ MVNETA_CAUSE_LINK_CHANGE |
--- /dev/null
+From: Gregory CLEMENT <gregory.clement@free-electrons.com>
+Date: Thu, 4 Feb 2016 22:09:28 +0100
+Subject: [PATCH] net: mvneta: The mvneta_percpu_elect function should be
+ atomic
+
+Electing a CPU must be done in an atomic way: it should be done after or
+before the removal/insertion of a CPU and this function is not reentrant.
+
+During the loop of mvneta_percpu_elect we associates the queues to the
+CPUs, if there is a topology change during this loop, then the mapping
+between the CPUs and the queues could be wrong. During this loop the
+interrupt mask is also updating for each CPUs, It should not be changed
+in the same time by other part of the driver.
+
+This patch adds spinlock to create the needed critical sections.
+
+Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+
+--- a/drivers/net/ethernet/marvell/mvneta.c
++++ b/drivers/net/ethernet/marvell/mvneta.c
+@@ -370,6 +370,10 @@ struct mvneta_port {
+ struct net_device *dev;
+ struct notifier_block cpu_notifier;
+ int rxq_def;
++ /* Protect the access to the percpu interrupt registers,
++ * ensuring that the configuration remains coherent.
++ */
++ spinlock_t lock;
+
+ /* Core clock */
+ struct clk *clk;
+@@ -2853,6 +2857,12 @@ static void mvneta_percpu_elect(struct m
+ {
+ int elected_cpu = 0, max_cpu, cpu, i = 0;
+
++ /* Electing a CPU must be done in an atomic way: it should be
++ * done after or before the removal/insertion of a CPU and
++ * this function is not reentrant.
++ */
++ spin_lock(&pp->lock);
++
+ /* Use the cpu associated to the rxq when it is online, in all
+ * the other cases, use the cpu 0 which can't be offline.
+ */
+@@ -2896,6 +2906,7 @@ static void mvneta_percpu_elect(struct m
+ i++;
+
+ }
++ spin_unlock(&pp->lock);
+ };
+
+ static int mvneta_percpu_notifier(struct notifier_block *nfb,
+@@ -2950,8 +2961,13 @@ static int mvneta_percpu_notifier(struct
+ case CPU_DOWN_PREPARE:
+ case CPU_DOWN_PREPARE_FROZEN:
+ netif_tx_stop_all_queues(pp->dev);
++ /* Thanks to this lock we are sure that any pending
++ * cpu election is done
++ */
++ spin_lock(&pp->lock);
+ /* Mask all ethernet port interrupts */
+ on_each_cpu(mvneta_percpu_mask_interrupt, pp, true);
++ spin_unlock(&pp->lock);
+
+ napi_synchronize(&port->napi);
+ napi_disable(&port->napi);
--- /dev/null
+From: Gregory CLEMENT <gregory.clement@free-electrons.com>
+Date: Thu, 4 Feb 2016 22:09:29 +0100
+Subject: [PATCH] net: mvneta: Fix race condition during stopping
+
+When stopping the port, the CPU notifier are still there whereas the
+mvneta_stop_dev function calls mvneta_percpu_disable() on each CPUs.
+It was possible to have a new CPU coming at this point which could be
+racy.
+
+This patch adds a flag preventing executing the code notifier for a new
+CPU when the port is stopping. It also uses the spinlock introduces
+previously. To avoid the deadlock, the lock has been moved outside the
+mvneta_percpu_elect function.
+
+Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+
+--- a/drivers/net/ethernet/marvell/mvneta.c
++++ b/drivers/net/ethernet/marvell/mvneta.c
+@@ -374,6 +374,7 @@ struct mvneta_port {
+ * ensuring that the configuration remains coherent.
+ */
+ spinlock_t lock;
++ bool is_stopped;
+
+ /* Core clock */
+ struct clk *clk;
+@@ -2853,16 +2854,14 @@ static void mvneta_percpu_disable(void *
+ disable_percpu_irq(pp->dev->irq);
+ }
+
++/* Electing a CPU must be done in an atomic way: it should be done
++ * after or before the removal/insertion of a CPU and this function is
++ * not reentrant.
++ */
+ static void mvneta_percpu_elect(struct mvneta_port *pp)
+ {
+ int elected_cpu = 0, max_cpu, cpu, i = 0;
+
+- /* Electing a CPU must be done in an atomic way: it should be
+- * done after or before the removal/insertion of a CPU and
+- * this function is not reentrant.
+- */
+- spin_lock(&pp->lock);
+-
+ /* Use the cpu associated to the rxq when it is online, in all
+ * the other cases, use the cpu 0 which can't be offline.
+ */
+@@ -2906,7 +2905,6 @@ static void mvneta_percpu_elect(struct m
+ i++;
+
+ }
+- spin_unlock(&pp->lock);
+ };
+
+ static int mvneta_percpu_notifier(struct notifier_block *nfb,
+@@ -2920,6 +2918,14 @@ static int mvneta_percpu_notifier(struct
+ switch (action) {
+ case CPU_ONLINE:
+ case CPU_ONLINE_FROZEN:
++ spin_lock(&pp->lock);
++ /* Configuring the driver for a new CPU while the
++ * driver is stopping is racy, so just avoid it.
++ */
++ if (pp->is_stopped) {
++ spin_unlock(&pp->lock);
++ break;
++ }
+ netif_tx_stop_all_queues(pp->dev);
+
+ /* We have to synchronise on tha napi of each CPU
+@@ -2957,6 +2963,7 @@ static int mvneta_percpu_notifier(struct
+ MVNETA_CAUSE_LINK_CHANGE |
+ MVNETA_CAUSE_PSC_SYNC_CHANGE);
+ netif_tx_start_all_queues(pp->dev);
++ spin_unlock(&pp->lock);
+ break;
+ case CPU_DOWN_PREPARE:
+ case CPU_DOWN_PREPARE_FROZEN:
+@@ -2981,7 +2988,9 @@ static int mvneta_percpu_notifier(struct
+ case CPU_DEAD:
+ case CPU_DEAD_FROZEN:
+ /* Check if a new CPU must be elected now this on is down */
++ spin_lock(&pp->lock);
+ mvneta_percpu_elect(pp);
++ spin_unlock(&pp->lock);
+ /* Unmask all ethernet port interrupts */
+ on_each_cpu(mvneta_percpu_unmask_interrupt, pp, true);
+ mvreg_write(pp, MVNETA_INTR_MISC_MASK,
+@@ -3033,7 +3042,7 @@ static int mvneta_open(struct net_device
+ */
+ on_each_cpu(mvneta_percpu_enable, pp, true);
+
+-
++ pp->is_stopped = false;
+ /* Register a CPU notifier to handle the case where our CPU
+ * might be taken offline.
+ */
+@@ -3066,9 +3075,18 @@ static int mvneta_stop(struct net_device
+ {
+ struct mvneta_port *pp = netdev_priv(dev);
+
++ /* Inform that we are stopping so we don't want to setup the
++ * driver for new CPUs in the notifiers
++ */
++ spin_lock(&pp->lock);
++ pp->is_stopped = true;
+ mvneta_stop_dev(pp);
+ mvneta_mdio_remove(pp);
+ unregister_cpu_notifier(&pp->cpu_notifier);
++ /* Now that the notifier are unregistered, we can release le
++ * lock
++ */
++ spin_unlock(&pp->lock);
+ on_each_cpu(mvneta_percpu_disable, pp, true);
+ free_percpu_irq(dev->irq, pp->ports);
+ mvneta_cleanup_rxqs(pp);
+@@ -3339,7 +3357,9 @@ static int mvneta_config_rss(struct mvn
+ mvreg_write(pp, MVNETA_PORT_CONFIG, val);
+
+ /* Update the elected CPU matching the new rxq_def */
++ spin_lock(&pp->lock);
+ mvneta_percpu_elect(pp);
++ spin_unlock(&pp->lock);
+
+ /* We have to synchronise on the napi of each CPU */
+ for_each_online_cpu(cpu) {
--- /dev/null
+From: Jisheng Zhang <jszhang@marvell.com>
+Date: Wed, 20 Jan 2016 19:27:22 +0800
+Subject: [PATCH] net: mvneta: sort the headers in alphabetic order
+
+Sorting the headers in alphabetic order will help to reduce the conflict
+when adding new headers in the future.
+
+Signed-off-by: Jisheng Zhang <jszhang@marvell.com>
+Acked-by: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+
+--- a/drivers/net/ethernet/marvell/mvneta.c
++++ b/drivers/net/ethernet/marvell/mvneta.c
+@@ -11,28 +11,28 @@
+ * warranty of any kind, whether express or implied.
+ */
+
+-#include <linux/kernel.h>
+-#include <linux/netdevice.h>
++#include <linux/clk.h>
++#include <linux/cpu.h>
+ #include <linux/etherdevice.h>
+-#include <linux/platform_device.h>
+-#include <linux/skbuff.h>
++#include <linux/if_vlan.h>
+ #include <linux/inetdevice.h>
+-#include <linux/mbus.h>
+-#include <linux/module.h>
+ #include <linux/interrupt.h>
+-#include <linux/if_vlan.h>
+-#include <net/ip.h>
+-#include <net/ipv6.h>
+ #include <linux/io.h>
+-#include <net/tso.h>
++#include <linux/kernel.h>
++#include <linux/mbus.h>
++#include <linux/module.h>
++#include <linux/netdevice.h>
+ #include <linux/of.h>
++#include <linux/of_address.h>
+ #include <linux/of_irq.h>
+ #include <linux/of_mdio.h>
+ #include <linux/of_net.h>
+-#include <linux/of_address.h>
+ #include <linux/phy.h>
+-#include <linux/clk.h>
+-#include <linux/cpu.h>
++#include <linux/platform_device.h>
++#include <linux/skbuff.h>
++#include <net/ip.h>
++#include <net/ipv6.h>
++#include <net/tso.h>
+
+ /* Registers */
+ #define MVNETA_RXQ_CONFIG_REG(q) (0x1400 + ((q) << 2))
--- /dev/null
+From: Gregory CLEMENT <gregory.clement@free-electrons.com>
+Date: Mon, 14 Mar 2016 09:39:04 +0100
+Subject: [PATCH] net: add a hardware buffer management helper API
+
+This basic implementation allows to share code between driver using
+hardware buffer management. As the code is hardware agnostic, there is
+few helpers, most of the optimization brought by the an HW BM has to be
+done at driver level.
+
+Tested-by: Sebastian Careba <nitroshift@yahoo.com>
+Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ create mode 100644 include/net/hwbm.h
+ create mode 100644 net/core/hwbm.c
+
+--- /dev/null
++++ b/include/net/hwbm.h
+@@ -0,0 +1,28 @@
++#ifndef _HWBM_H
++#define _HWBM_H
++
++struct hwbm_pool {
++ /* Capacity of the pool */
++ int size;
++ /* Size of the buffers managed */
++ int frag_size;
++ /* Number of buffers currently used by this pool */
++ int buf_num;
++ /* constructor called during alocation */
++ int (*construct)(struct hwbm_pool *bm_pool, void *buf);
++ /* protect acces to the buffer counter*/
++ spinlock_t lock;
++ /* private data */
++ void *priv;
++};
++#ifdef CONFIG_HWBM
++void hwbm_buf_free(struct hwbm_pool *bm_pool, void *buf);
++int hwbm_pool_refill(struct hwbm_pool *bm_pool, gfp_t gfp);
++int hwbm_pool_add(struct hwbm_pool *bm_pool, unsigned int buf_num, gfp_t gfp);
++#else
++void hwbm_buf_free(struct hwbm_pool *bm_pool, void *buf) {}
++int hwbm_pool_refill(struct hwbm_pool *bm_pool, gfp_t gfp) { return 0; }
++int hwbm_pool_add(struct hwbm_pool *bm_pool, unsigned int buf_num, gfp_t gfp)
++{ return 0; }
++#endif /* CONFIG_HWBM */
++#endif /* _HWBM_H */
+--- a/net/Kconfig
++++ b/net/Kconfig
+@@ -259,6 +259,9 @@ config XPS
+ depends on SMP
+ default y
+
++config HWBM
++ bool
++
+ config CGROUP_NET_PRIO
+ bool "Network priority cgroup"
+ depends on CGROUPS
+--- a/net/core/Makefile
++++ b/net/core/Makefile
+@@ -14,6 +14,7 @@ obj-y += dev.o ethtool.o dev_addr_
+ obj-$(CONFIG_SOCK_DIAG) += sock_diag.o
+ obj-$(CONFIG_XFRM) += flow.o
+ obj-y += net-sysfs.o
++obj-$(CONFIG_HWBM) += hwbm.o
+ obj-$(CONFIG_PROC_FS) += net-procfs.o
+ obj-$(CONFIG_NET_PKTGEN) += pktgen.o
+ obj-$(CONFIG_NETPOLL) += netpoll.o
+--- /dev/null
++++ b/net/core/hwbm.c
+@@ -0,0 +1,87 @@
++/* Support for hardware buffer manager.
++ *
++ * Copyright (C) 2016 Marvell
++ *
++ * Gregory CLEMENT <gregory.clement@free-electrons.com>
++ *
++ * 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.
++ */
++#include <linux/kernel.h>
++#include <linux/printk.h>
++#include <linux/skbuff.h>
++#include <net/hwbm.h>
++
++void hwbm_buf_free(struct hwbm_pool *bm_pool, void *buf)
++{
++ if (likely(bm_pool->frag_size <= PAGE_SIZE))
++ skb_free_frag(buf);
++ else
++ kfree(buf);
++}
++EXPORT_SYMBOL_GPL(hwbm_buf_free);
++
++/* Refill processing for HW buffer management */
++int hwbm_pool_refill(struct hwbm_pool *bm_pool, gfp_t gfp)
++{
++ int frag_size = bm_pool->frag_size;
++ void *buf;
++
++ if (likely(frag_size <= PAGE_SIZE))
++ buf = netdev_alloc_frag(frag_size);
++ else
++ buf = kmalloc(frag_size, gfp);
++
++ if (!buf)
++ return -ENOMEM;
++
++ if (bm_pool->construct)
++ if (bm_pool->construct(bm_pool, buf)) {
++ hwbm_buf_free(bm_pool, buf);
++ return -ENOMEM;
++ }
++
++ return 0;
++}
++EXPORT_SYMBOL_GPL(hwbm_pool_refill);
++
++int hwbm_pool_add(struct hwbm_pool *bm_pool, unsigned int buf_num, gfp_t gfp)
++{
++ int err, i;
++ unsigned long flags;
++
++ spin_lock_irqsave(&bm_pool->lock, flags);
++ if (bm_pool->buf_num == bm_pool->size) {
++ pr_warn("pool already filled\n");
++ return bm_pool->buf_num;
++ }
++
++ if (buf_num + bm_pool->buf_num > bm_pool->size) {
++ pr_warn("cannot allocate %d buffers for pool\n",
++ buf_num);
++ return 0;
++ }
++
++ if ((buf_num + bm_pool->buf_num) < bm_pool->buf_num) {
++ pr_warn("Adding %d buffers to the %d current buffers will overflow\n",
++ buf_num, bm_pool->buf_num);
++ return 0;
++ }
++
++ for (i = 0; i < buf_num; i++) {
++ err = hwbm_pool_refill(bm_pool, gfp);
++ if (err < 0)
++ break;
++ }
++
++ /* Update BM driver with number of buffers added to pool */
++ bm_pool->buf_num += i;
++
++ pr_debug("hwpm pool: %d of %d buffers added\n", i, buf_num);
++ spin_unlock_irqrestore(&bm_pool->lock, flags);
++
++ return i;
++}
++EXPORT_SYMBOL_GPL(hwbm_pool_add);
--- /dev/null
+From: Marcin Wojtas <mw@semihalf.com>
+Date: Mon, 14 Mar 2016 09:39:03 +0100
+Subject: [PATCH] net: mvneta: bm: add support for hardware buffer management
+
+Buffer manager (BM) is a dedicated hardware unit that can be used by all
+ethernet ports of Armada XP and 38x SoC's. It allows to offload CPU on RX
+path by sparing DRAM access on refilling buffer pool, hardware-based
+filling of descriptor ring data and better memory utilization due to HW
+arbitration for using 'short' pools for small packets.
+
+Tests performed with A388 SoC working as a network bridge between two
+packet generators showed increase of maximum processed 64B packets by
+~20k (~555k packets with BM enabled vs ~535 packets without BM). Also
+when pushing 1500B-packets with a line rate achieved, CPU load decreased
+from around 25% without BM to 20% with BM.
+
+BM comprise up to 4 buffer pointers' (BP) rings kept in DRAM, which
+are called external BP pools - BPPE. Allocating and releasing buffer
+pointers (BP) to/from BPPE is performed indirectly by write/read access
+to a dedicated internal SRAM, where internal BP pools (BPPI) are placed.
+BM hardware controls status of BPPE automatically, as well as assigning
+proper buffers to RX descriptors. For more details please refer to
+Functional Specification of Armada XP or 38x SoC.
+
+In order to enable support for a separate hardware block, common for all
+ports, a new driver has to be implemented ('mvneta_bm'). It provides
+initialization sequence of address space, clocks, registers, SRAM,
+empty pools' structures and also obtaining optional configuration
+from DT (please refer to device tree binding documentation). mvneta_bm
+exposes also a necessary API to mvneta driver, as well as a dedicated
+structure with BM information (bm_priv), whose presence is used as a
+flag notifying of BM usage by port. It has to be ensured that mvneta_bm
+probe is executed prior to the ones in ports' driver. In case BM is not
+used or its probe fails, mvneta falls back to use software buffer
+management.
+
+A sequence executed in mvneta_probe function is modified in order to have
+an access to needed resources before possible port's BM initialization is
+done. According to port-pools mapping provided by DT appropriate registers
+are configured and the buffer pools are filled. RX path is modified
+accordingly. Becaues the hardware allows a wide variety of configuration
+options, following assumptions are made:
+* using BM mechanisms can be selectively disabled/enabled basing
+ on DT configuration among the ports
+* 'long' pool's single buffer size is tied to port's MTU
+* using 'long' pool by port is obligatory and it cannot be shared
+* using 'short' pool for smaller packets is optional
+* one 'short' pool can be shared among all ports
+
+This commit enables hardware buffer management operation cooperating with
+existing mvneta driver. New device tree binding documentation is added and
+the one of mvneta is updated accordingly.
+
+[gregory.clement@free-electrons.com: removed the suspend/resume part]
+
+Signed-off-by: Marcin Wojtas <mw@semihalf.com>
+Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ create mode 100644 Documentation/devicetree/bindings/net/marvell-neta-bm.txt
+ create mode 100644 drivers/net/ethernet/marvell/mvneta_bm.c
+ create mode 100644 drivers/net/ethernet/marvell/mvneta_bm.h
+
+--- a/Documentation/devicetree/bindings/net/marvell-armada-370-neta.txt
++++ b/Documentation/devicetree/bindings/net/marvell-armada-370-neta.txt
+@@ -13,15 +13,30 @@ Optional properties:
+ Value is presented in bytes. If not used, by default 1600B is set for
+ "marvell,armada-370-neta" and 9800B for others.
+
++Optional properties (valid only for Armada XP/38x):
++
++- buffer-manager: a phandle to a buffer manager node. Please refer to
++ Documentation/devicetree/bindings/net/marvell-neta-bm.txt
++- bm,pool-long: ID of a pool, that will accept all packets of a size
++ higher than 'short' pool's threshold (if set) and up to MTU value.
++ Obligatory, when the port is supposed to use hardware
++ buffer management.
++- bm,pool-short: ID of a pool, that will be used for accepting
++ packets of a size lower than given threshold. If not set, the port
++ will use a single 'long' pool for all packets, as defined above.
++
+ Example:
+
+-ethernet@d0070000 {
++ethernet@70000 {
+ compatible = "marvell,armada-370-neta";
+- reg = <0xd0070000 0x2500>;
++ reg = <0x70000 0x2500>;
+ interrupts = <8>;
+ clocks = <&gate_clk 4>;
+ tx-csum-limit = <9800>
+ status = "okay";
+ phy = <&phy0>;
+ phy-mode = "rgmii-id";
++ buffer-manager = <&bm>;
++ bm,pool-long = <0>;
++ bm,pool-short = <1>;
+ };
+--- /dev/null
++++ b/Documentation/devicetree/bindings/net/marvell-neta-bm.txt
+@@ -0,0 +1,49 @@
++* Marvell Armada 380/XP Buffer Manager driver (BM)
++
++Required properties:
++
++- compatible: should be "marvell,armada-380-neta-bm".
++- reg: address and length of the register set for the device.
++- clocks: a pointer to the reference clock for this device.
++- internal-mem: a phandle to BM internal SRAM definition.
++
++Optional properties (port):
++
++- pool<0 : 3>,capacity: size of external buffer pointers' ring maintained
++ in DRAM. Can be set for each pool (id 0 : 3) separately. The value has
++ to be chosen between 128 and 16352 and it also has to be aligned to 32.
++ Otherwise the driver would adjust a given number or choose default if
++ not set.
++- pool<0 : 3>,pkt-size: maximum size of a packet accepted by a given buffer
++ pointers' pool (id 0 : 3). It will be taken into consideration only when pool
++ type is 'short'. For 'long' ones it would be overridden by port's MTU.
++ If not set a driver will choose a default value.
++
++In order to see how to hook the BM to a given ethernet port, please
++refer to Documentation/devicetree/bindings/net/marvell-armada-370-neta.txt.
++
++Example:
++
++- main node:
++
++bm: bm@c8000 {
++ compatible = "marvell,armada-380-neta-bm";
++ reg = <0xc8000 0xac>;
++ clocks = <&gateclk 13>;
++ internal-mem = <&bm_bppi>;
++ status = "okay";
++ pool2,capacity = <4096>;
++ pool1,pkt-size = <512>;
++};
++
++- internal SRAM node:
++
++bm_bppi: bm-bppi {
++ compatible = "mmio-sram";
++ reg = <MBUS_ID(0x0c, 0x04) 0 0x100000>;
++ ranges = <0 MBUS_ID(0x0c, 0x04) 0 0x100000>;
++ #address-cells = <1>;
++ #size-cells = <1>;
++ clocks = <&gateclk 13>;
++ status = "okay";
++};
+--- a/drivers/net/ethernet/marvell/Kconfig
++++ b/drivers/net/ethernet/marvell/Kconfig
+@@ -40,6 +40,19 @@ config MVMDIO
+
+ This driver is used by the MV643XX_ETH and MVNETA drivers.
+
++config MVNETA_BM
++ tristate "Marvell Armada 38x/XP network interface BM support"
++ depends on MVNETA
++ ---help---
++ This driver supports auxiliary block of the network
++ interface units in the Marvell ARMADA XP and ARMADA 38x SoC
++ family, which is called buffer manager.
++
++ This driver, when enabled, strictly cooperates with mvneta
++ driver and is common for all network ports of the devices,
++ even for Armada 370 SoC, which doesn't support hardware
++ buffer management.
++
+ config MVNETA
+ tristate "Marvell Armada 370/38x/XP network interface support"
+ depends on PLAT_ORION
+--- a/drivers/net/ethernet/marvell/Makefile
++++ b/drivers/net/ethernet/marvell/Makefile
+@@ -4,6 +4,7 @@
+
+ obj-$(CONFIG_MVMDIO) += mvmdio.o
+ obj-$(CONFIG_MV643XX_ETH) += mv643xx_eth.o
++obj-$(CONFIG_MVNETA_BM) += mvneta_bm.o
+ obj-$(CONFIG_MVNETA) += mvneta.o
+ obj-$(CONFIG_MVPP2) += mvpp2.o
+ obj-$(CONFIG_PXA168_ETH) += pxa168_eth.o
+--- a/drivers/net/ethernet/marvell/mvneta.c
++++ b/drivers/net/ethernet/marvell/mvneta.c
+@@ -30,6 +30,7 @@
+ #include <linux/phy.h>
+ #include <linux/platform_device.h>
+ #include <linux/skbuff.h>
++#include "mvneta_bm.h"
+ #include <net/ip.h>
+ #include <net/ipv6.h>
+ #include <net/tso.h>
+@@ -37,6 +38,10 @@
+ /* Registers */
+ #define MVNETA_RXQ_CONFIG_REG(q) (0x1400 + ((q) << 2))
+ #define MVNETA_RXQ_HW_BUF_ALLOC BIT(0)
++#define MVNETA_RXQ_SHORT_POOL_ID_SHIFT 4
++#define MVNETA_RXQ_SHORT_POOL_ID_MASK 0x30
++#define MVNETA_RXQ_LONG_POOL_ID_SHIFT 6
++#define MVNETA_RXQ_LONG_POOL_ID_MASK 0xc0
+ #define MVNETA_RXQ_PKT_OFFSET_ALL_MASK (0xf << 8)
+ #define MVNETA_RXQ_PKT_OFFSET_MASK(offs) ((offs) << 8)
+ #define MVNETA_RXQ_THRESHOLD_REG(q) (0x14c0 + ((q) << 2))
+@@ -50,6 +55,9 @@
+ #define MVNETA_RXQ_STATUS_UPDATE_REG(q) (0x1500 + ((q) << 2))
+ #define MVNETA_RXQ_ADD_NON_OCCUPIED_SHIFT 16
+ #define MVNETA_RXQ_ADD_NON_OCCUPIED_MAX 255
++#define MVNETA_PORT_POOL_BUFFER_SZ_REG(pool) (0x1700 + ((pool) << 2))
++#define MVNETA_PORT_POOL_BUFFER_SZ_SHIFT 3
++#define MVNETA_PORT_POOL_BUFFER_SZ_MASK 0xfff8
+ #define MVNETA_PORT_RX_RESET 0x1cc0
+ #define MVNETA_PORT_RX_DMA_RESET BIT(0)
+ #define MVNETA_PHY_ADDR 0x2000
+@@ -107,6 +115,7 @@
+ #define MVNETA_GMAC_CLOCK_DIVIDER 0x24f4
+ #define MVNETA_GMAC_1MS_CLOCK_ENABLE BIT(31)
+ #define MVNETA_ACC_MODE 0x2500
++#define MVNETA_BM_ADDRESS 0x2504
+ #define MVNETA_CPU_MAP(cpu) (0x2540 + ((cpu) << 2))
+ #define MVNETA_CPU_RXQ_ACCESS_ALL_MASK 0x000000ff
+ #define MVNETA_CPU_TXQ_ACCESS_ALL_MASK 0x0000ff00
+@@ -253,7 +262,10 @@
+ #define MVNETA_CPU_D_CACHE_LINE_SIZE 32
+ #define MVNETA_TX_CSUM_DEF_SIZE 1600
+ #define MVNETA_TX_CSUM_MAX_SIZE 9800
+-#define MVNETA_ACC_MODE_EXT 1
++#define MVNETA_ACC_MODE_EXT1 1
++#define MVNETA_ACC_MODE_EXT2 2
++
++#define MVNETA_MAX_DECODE_WIN 6
+
+ /* Timeout constants */
+ #define MVNETA_TX_DISABLE_TIMEOUT_MSEC 1000
+@@ -293,7 +305,8 @@
+ ((addr >= txq->tso_hdrs_phys) && \
+ (addr < txq->tso_hdrs_phys + txq->size * TSO_HEADER_SIZE))
+
+-#define MVNETA_RX_BUF_SIZE(pkt_size) ((pkt_size) + NET_SKB_PAD)
++#define MVNETA_RX_GET_BM_POOL_ID(rxd) \
++ (((rxd)->status & MVNETA_RXD_BM_POOL_MASK) >> MVNETA_RXD_BM_POOL_SHIFT)
+
+ struct mvneta_statistic {
+ unsigned short offset;
+@@ -359,6 +372,7 @@ struct mvneta_pcpu_port {
+ };
+
+ struct mvneta_port {
++ u8 id;
+ struct mvneta_pcpu_port __percpu *ports;
+ struct mvneta_pcpu_stats __percpu *stats;
+
+@@ -392,6 +406,11 @@ struct mvneta_port {
+ unsigned int tx_csum_limit;
+ unsigned int use_inband_status:1;
+
++ struct mvneta_bm *bm_priv;
++ struct mvneta_bm_pool *pool_long;
++ struct mvneta_bm_pool *pool_short;
++ int bm_win_id;
++
+ u64 ethtool_stats[ARRAY_SIZE(mvneta_statistics)];
+
+ u32 indir[MVNETA_RSS_LU_TABLE_SIZE];
+@@ -417,6 +436,8 @@ struct mvneta_port {
+ #define MVNETA_TX_L4_CSUM_NOT BIT(31)
+
+ #define MVNETA_RXD_ERR_CRC 0x0
++#define MVNETA_RXD_BM_POOL_SHIFT 13
++#define MVNETA_RXD_BM_POOL_MASK (BIT(13) | BIT(14))
+ #define MVNETA_RXD_ERR_SUMMARY BIT(16)
+ #define MVNETA_RXD_ERR_OVERRUN BIT(17)
+ #define MVNETA_RXD_ERR_LEN BIT(18)
+@@ -561,6 +582,9 @@ static int rxq_def;
+
+ static int rx_copybreak __read_mostly = 256;
+
++/* HW BM need that each port be identify by a unique ID */
++static int global_port_id;
++
+ #define MVNETA_DRIVER_NAME "mvneta"
+ #define MVNETA_DRIVER_VERSION "1.0"
+
+@@ -827,6 +851,214 @@ static void mvneta_rxq_bm_disable(struct
+ mvreg_write(pp, MVNETA_RXQ_CONFIG_REG(rxq->id), val);
+ }
+
++/* Enable buffer management (BM) */
++static void mvneta_rxq_bm_enable(struct mvneta_port *pp,
++ struct mvneta_rx_queue *rxq)
++{
++ u32 val;
++
++ val = mvreg_read(pp, MVNETA_RXQ_CONFIG_REG(rxq->id));
++ val |= MVNETA_RXQ_HW_BUF_ALLOC;
++ mvreg_write(pp, MVNETA_RXQ_CONFIG_REG(rxq->id), val);
++}
++
++/* Notify HW about port's assignment of pool for bigger packets */
++static void mvneta_rxq_long_pool_set(struct mvneta_port *pp,
++ struct mvneta_rx_queue *rxq)
++{
++ u32 val;
++
++ val = mvreg_read(pp, MVNETA_RXQ_CONFIG_REG(rxq->id));
++ val &= ~MVNETA_RXQ_LONG_POOL_ID_MASK;
++ val |= (pp->pool_long->id << MVNETA_RXQ_LONG_POOL_ID_SHIFT);
++
++ mvreg_write(pp, MVNETA_RXQ_CONFIG_REG(rxq->id), val);
++}
++
++/* Notify HW about port's assignment of pool for smaller packets */
++static void mvneta_rxq_short_pool_set(struct mvneta_port *pp,
++ struct mvneta_rx_queue *rxq)
++{
++ u32 val;
++
++ val = mvreg_read(pp, MVNETA_RXQ_CONFIG_REG(rxq->id));
++ val &= ~MVNETA_RXQ_SHORT_POOL_ID_MASK;
++ val |= (pp->pool_short->id << MVNETA_RXQ_SHORT_POOL_ID_SHIFT);
++
++ mvreg_write(pp, MVNETA_RXQ_CONFIG_REG(rxq->id), val);
++}
++
++/* Set port's receive buffer size for assigned BM pool */
++static inline void mvneta_bm_pool_bufsize_set(struct mvneta_port *pp,
++ int buf_size,
++ u8 pool_id)
++{
++ u32 val;
++
++ if (!IS_ALIGNED(buf_size, 8)) {
++ dev_warn(pp->dev->dev.parent,
++ "illegal buf_size value %d, round to %d\n",
++ buf_size, ALIGN(buf_size, 8));
++ buf_size = ALIGN(buf_size, 8);
++ }
++
++ val = mvreg_read(pp, MVNETA_PORT_POOL_BUFFER_SZ_REG(pool_id));
++ val |= buf_size & MVNETA_PORT_POOL_BUFFER_SZ_MASK;
++ mvreg_write(pp, MVNETA_PORT_POOL_BUFFER_SZ_REG(pool_id), val);
++}
++
++/* Configure MBUS window in order to enable access BM internal SRAM */
++static int mvneta_mbus_io_win_set(struct mvneta_port *pp, u32 base, u32 wsize,
++ u8 target, u8 attr)
++{
++ u32 win_enable, win_protect;
++ int i;
++
++ win_enable = mvreg_read(pp, MVNETA_BASE_ADDR_ENABLE);
++
++ if (pp->bm_win_id < 0) {
++ /* Find first not occupied window */
++ for (i = 0; i < MVNETA_MAX_DECODE_WIN; i++) {
++ if (win_enable & (1 << i)) {
++ pp->bm_win_id = i;
++ break;
++ }
++ }
++ if (i == MVNETA_MAX_DECODE_WIN)
++ return -ENOMEM;
++ } else {
++ i = pp->bm_win_id;
++ }
++
++ mvreg_write(pp, MVNETA_WIN_BASE(i), 0);
++ mvreg_write(pp, MVNETA_WIN_SIZE(i), 0);
++
++ if (i < 4)
++ mvreg_write(pp, MVNETA_WIN_REMAP(i), 0);
++
++ mvreg_write(pp, MVNETA_WIN_BASE(i), (base & 0xffff0000) |
++ (attr << 8) | target);
++
++ mvreg_write(pp, MVNETA_WIN_SIZE(i), (wsize - 1) & 0xffff0000);
++
++ win_protect = mvreg_read(pp, MVNETA_ACCESS_PROTECT_ENABLE);
++ win_protect |= 3 << (2 * i);
++ mvreg_write(pp, MVNETA_ACCESS_PROTECT_ENABLE, win_protect);
++
++ win_enable &= ~(1 << i);
++ mvreg_write(pp, MVNETA_BASE_ADDR_ENABLE, win_enable);
++
++ return 0;
++}
++
++/* Assign and initialize pools for port. In case of fail
++ * buffer manager will remain disabled for current port.
++ */
++static int mvneta_bm_port_init(struct platform_device *pdev,
++ struct mvneta_port *pp)
++{
++ struct device_node *dn = pdev->dev.of_node;
++ u32 long_pool_id, short_pool_id, wsize;
++ u8 target, attr;
++ int err;
++
++ /* Get BM window information */
++ err = mvebu_mbus_get_io_win_info(pp->bm_priv->bppi_phys_addr, &wsize,
++ &target, &attr);
++ if (err < 0)
++ return err;
++
++ pp->bm_win_id = -1;
++
++ /* Open NETA -> BM window */
++ err = mvneta_mbus_io_win_set(pp, pp->bm_priv->bppi_phys_addr, wsize,
++ target, attr);
++ if (err < 0) {
++ netdev_info(pp->dev, "fail to configure mbus window to BM\n");
++ return err;
++ }
++
++ if (of_property_read_u32(dn, "bm,pool-long", &long_pool_id)) {
++ netdev_info(pp->dev, "missing long pool id\n");
++ return -EINVAL;
++ }
++
++ /* Create port's long pool depending on mtu */
++ pp->pool_long = mvneta_bm_pool_use(pp->bm_priv, long_pool_id,
++ MVNETA_BM_LONG, pp->id,
++ MVNETA_RX_PKT_SIZE(pp->dev->mtu));
++ if (!pp->pool_long) {
++ netdev_info(pp->dev, "fail to obtain long pool for port\n");
++ return -ENOMEM;
++ }
++
++ pp->pool_long->port_map |= 1 << pp->id;
++
++ mvneta_bm_pool_bufsize_set(pp, pp->pool_long->buf_size,
++ pp->pool_long->id);
++
++ /* If short pool id is not defined, assume using single pool */
++ if (of_property_read_u32(dn, "bm,pool-short", &short_pool_id))
++ short_pool_id = long_pool_id;
++
++ /* Create port's short pool */
++ pp->pool_short = mvneta_bm_pool_use(pp->bm_priv, short_pool_id,
++ MVNETA_BM_SHORT, pp->id,
++ MVNETA_BM_SHORT_PKT_SIZE);
++ if (!pp->pool_short) {
++ netdev_info(pp->dev, "fail to obtain short pool for port\n");
++ mvneta_bm_pool_destroy(pp->bm_priv, pp->pool_long, 1 << pp->id);
++ return -ENOMEM;
++ }
++
++ if (short_pool_id != long_pool_id) {
++ pp->pool_short->port_map |= 1 << pp->id;
++ mvneta_bm_pool_bufsize_set(pp, pp->pool_short->buf_size,
++ pp->pool_short->id);
++ }
++
++ return 0;
++}
++
++/* Update settings of a pool for bigger packets */
++static void mvneta_bm_update_mtu(struct mvneta_port *pp, int mtu)
++{
++ struct mvneta_bm_pool *bm_pool = pp->pool_long;
++ int num;
++
++ /* Release all buffers from long pool */
++ mvneta_bm_bufs_free(pp->bm_priv, bm_pool, 1 << pp->id);
++ if (bm_pool->buf_num) {
++ WARN(1, "cannot free all buffers in pool %d\n",
++ bm_pool->id);
++ goto bm_mtu_err;
++ }
++
++ bm_pool->pkt_size = MVNETA_RX_PKT_SIZE(mtu);
++ bm_pool->buf_size = MVNETA_RX_BUF_SIZE(bm_pool->pkt_size);
++ bm_pool->frag_size = SKB_DATA_ALIGN(sizeof(struct skb_shared_info)) +
++ SKB_DATA_ALIGN(MVNETA_RX_BUF_SIZE(bm_pool->pkt_size));
++
++ /* Fill entire long pool */
++ num = mvneta_bm_bufs_add(pp->bm_priv, bm_pool, bm_pool->size);
++ if (num != bm_pool->size) {
++ WARN(1, "pool %d: %d of %d allocated\n",
++ bm_pool->id, num, bm_pool->size);
++ goto bm_mtu_err;
++ }
++ mvneta_bm_pool_bufsize_set(pp, bm_pool->buf_size, bm_pool->id);
++
++ return;
++
++bm_mtu_err:
++ mvneta_bm_pool_destroy(pp->bm_priv, pp->pool_long, 1 << pp->id);
++ mvneta_bm_pool_destroy(pp->bm_priv, pp->pool_short, 1 << pp->id);
++
++ pp->bm_priv = NULL;
++ mvreg_write(pp, MVNETA_ACC_MODE, MVNETA_ACC_MODE_EXT1);
++ netdev_info(pp->dev, "fail to update MTU, fall back to software BM\n");
++}
++
+ /* Start the Ethernet port RX and TX activity */
+ static void mvneta_port_up(struct mvneta_port *pp)
+ {
+@@ -1147,9 +1379,17 @@ static void mvneta_defaults_set(struct m
+ mvreg_write(pp, MVNETA_PORT_RX_RESET, 0);
+
+ /* Set Port Acceleration Mode */
+- val = MVNETA_ACC_MODE_EXT;
++ if (pp->bm_priv)
++ /* HW buffer management + legacy parser */
++ val = MVNETA_ACC_MODE_EXT2;
++ else
++ /* SW buffer management + legacy parser */
++ val = MVNETA_ACC_MODE_EXT1;
+ mvreg_write(pp, MVNETA_ACC_MODE, val);
+
++ if (pp->bm_priv)
++ mvreg_write(pp, MVNETA_BM_ADDRESS, pp->bm_priv->bppi_phys_addr);
++
+ /* Update val of portCfg register accordingly with all RxQueue types */
+ val = MVNETA_PORT_CONFIG_DEFL_VALUE(pp->rxq_def);
+ mvreg_write(pp, MVNETA_PORT_CONFIG, val);
+@@ -1516,23 +1756,25 @@ static void mvneta_txq_done(struct mvnet
+ }
+ }
+
+-static void *mvneta_frag_alloc(const struct mvneta_port *pp)
++void *mvneta_frag_alloc(unsigned int frag_size)
+ {
+- if (likely(pp->frag_size <= PAGE_SIZE))
+- return netdev_alloc_frag(pp->frag_size);
++ if (likely(frag_size <= PAGE_SIZE))
++ return netdev_alloc_frag(frag_size);
+ else
+- return kmalloc(pp->frag_size, GFP_ATOMIC);
++ return kmalloc(frag_size, GFP_ATOMIC);
+ }
++EXPORT_SYMBOL_GPL(mvneta_frag_alloc);
+
+-static void mvneta_frag_free(const struct mvneta_port *pp, void *data)
++void mvneta_frag_free(unsigned int frag_size, void *data)
+ {
+- if (likely(pp->frag_size <= PAGE_SIZE))
++ if (likely(frag_size <= PAGE_SIZE))
+ skb_free_frag(data);
+ else
+ kfree(data);
+ }
++EXPORT_SYMBOL_GPL(mvneta_frag_free);
+
+-/* Refill processing */
++/* Refill processing for SW buffer management */
+ static int mvneta_rx_refill(struct mvneta_port *pp,
+ struct mvneta_rx_desc *rx_desc)
+
+@@ -1540,7 +1782,7 @@ static int mvneta_rx_refill(struct mvnet
+ dma_addr_t phys_addr;
+ void *data;
+
+- data = mvneta_frag_alloc(pp);
++ data = mvneta_frag_alloc(pp->frag_size);
+ if (!data)
+ return -ENOMEM;
+
+@@ -1548,7 +1790,7 @@ static int mvneta_rx_refill(struct mvnet
+ MVNETA_RX_BUF_SIZE(pp->pkt_size),
+ DMA_FROM_DEVICE);
+ if (unlikely(dma_mapping_error(pp->dev->dev.parent, phys_addr))) {
+- mvneta_frag_free(pp, data);
++ mvneta_frag_free(pp->frag_size, data);
+ return -ENOMEM;
+ }
+
+@@ -1594,22 +1836,156 @@ static void mvneta_rxq_drop_pkts(struct
+ int rx_done, i;
+
+ rx_done = mvneta_rxq_busy_desc_num_get(pp, rxq);
++ if (rx_done)
++ mvneta_rxq_desc_num_update(pp, rxq, rx_done, rx_done);
++
++ if (pp->bm_priv) {
++ for (i = 0; i < rx_done; i++) {
++ struct mvneta_rx_desc *rx_desc =
++ mvneta_rxq_next_desc_get(rxq);
++ u8 pool_id = MVNETA_RX_GET_BM_POOL_ID(rx_desc);
++ struct mvneta_bm_pool *bm_pool;
++
++ bm_pool = &pp->bm_priv->bm_pools[pool_id];
++ /* Return dropped buffer to the pool */
++ mvneta_bm_pool_put_bp(pp->bm_priv, bm_pool,
++ rx_desc->buf_phys_addr);
++ }
++ return;
++ }
++
+ for (i = 0; i < rxq->size; i++) {
+ struct mvneta_rx_desc *rx_desc = rxq->descs + i;
+ void *data = (void *)rx_desc->buf_cookie;
+
+ dma_unmap_single(pp->dev->dev.parent, rx_desc->buf_phys_addr,
+ MVNETA_RX_BUF_SIZE(pp->pkt_size), DMA_FROM_DEVICE);
+- mvneta_frag_free(pp, data);
++ mvneta_frag_free(pp->frag_size, data);
+ }
++}
+
+- if (rx_done)
+- mvneta_rxq_desc_num_update(pp, rxq, rx_done, rx_done);
++/* Main rx processing when using software buffer management */
++static int mvneta_rx_swbm(struct mvneta_port *pp, int rx_todo,
++ struct mvneta_rx_queue *rxq)
++{
++ struct mvneta_pcpu_port *port = this_cpu_ptr(pp->ports);
++ struct net_device *dev = pp->dev;
++ int rx_done;
++ u32 rcvd_pkts = 0;
++ u32 rcvd_bytes = 0;
++
++ /* Get number of received packets */
++ rx_done = mvneta_rxq_busy_desc_num_get(pp, rxq);
++
++ if (rx_todo > rx_done)
++ rx_todo = rx_done;
++
++ rx_done = 0;
++
++ /* Fairness NAPI loop */
++ while (rx_done < rx_todo) {
++ struct mvneta_rx_desc *rx_desc = mvneta_rxq_next_desc_get(rxq);
++ struct sk_buff *skb;
++ unsigned char *data;
++ dma_addr_t phys_addr;
++ u32 rx_status, frag_size;
++ int rx_bytes, err;
++
++ rx_done++;
++ rx_status = rx_desc->status;
++ rx_bytes = rx_desc->data_size - (ETH_FCS_LEN + MVNETA_MH_SIZE);
++ data = (unsigned char *)rx_desc->buf_cookie;
++ phys_addr = rx_desc->buf_phys_addr;
++
++ if (!mvneta_rxq_desc_is_first_last(rx_status) ||
++ (rx_status & MVNETA_RXD_ERR_SUMMARY)) {
++err_drop_frame:
++ dev->stats.rx_errors++;
++ mvneta_rx_error(pp, rx_desc);
++ /* leave the descriptor untouched */
++ continue;
++ }
++
++ if (rx_bytes <= rx_copybreak) {
++ /* better copy a small frame and not unmap the DMA region */
++ skb = netdev_alloc_skb_ip_align(dev, rx_bytes);
++ if (unlikely(!skb))
++ goto err_drop_frame;
++
++ dma_sync_single_range_for_cpu(dev->dev.parent,
++ rx_desc->buf_phys_addr,
++ MVNETA_MH_SIZE + NET_SKB_PAD,
++ rx_bytes,
++ DMA_FROM_DEVICE);
++ memcpy(skb_put(skb, rx_bytes),
++ data + MVNETA_MH_SIZE + NET_SKB_PAD,
++ rx_bytes);
++
++ skb->protocol = eth_type_trans(skb, dev);
++ mvneta_rx_csum(pp, rx_status, skb);
++ napi_gro_receive(&port->napi, skb);
++
++ rcvd_pkts++;
++ rcvd_bytes += rx_bytes;
++
++ /* leave the descriptor and buffer untouched */
++ continue;
++ }
++
++ /* Refill processing */
++ err = mvneta_rx_refill(pp, rx_desc);
++ if (err) {
++ netdev_err(dev, "Linux processing - Can't refill\n");
++ rxq->missed++;
++ goto err_drop_frame;
++ }
++
++ frag_size = pp->frag_size;
++
++ skb = build_skb(data, frag_size > PAGE_SIZE ? 0 : frag_size);
++
++ /* After refill old buffer has to be unmapped regardless
++ * the skb is successfully built or not.
++ */
++ dma_unmap_single(dev->dev.parent, phys_addr,
++ MVNETA_RX_BUF_SIZE(pp->pkt_size),
++ DMA_FROM_DEVICE);
++
++ if (!skb)
++ goto err_drop_frame;
++
++ rcvd_pkts++;
++ rcvd_bytes += rx_bytes;
++
++ /* Linux processing */
++ skb_reserve(skb, MVNETA_MH_SIZE + NET_SKB_PAD);
++ skb_put(skb, rx_bytes);
++
++ skb->protocol = eth_type_trans(skb, dev);
++
++ mvneta_rx_csum(pp, rx_status, skb);
++
++ napi_gro_receive(&port->napi, skb);
++ }
++
++ if (rcvd_pkts) {
++ struct mvneta_pcpu_stats *stats = this_cpu_ptr(pp->stats);
++
++ u64_stats_update_begin(&stats->syncp);
++ stats->rx_packets += rcvd_pkts;
++ stats->rx_bytes += rcvd_bytes;
++ u64_stats_update_end(&stats->syncp);
++ }
++
++ /* Update rxq management counters */
++ mvneta_rxq_desc_num_update(pp, rxq, rx_done, rx_done);
++
++ return rx_done;
+ }
+
+-/* Main rx processing */
+-static int mvneta_rx(struct mvneta_port *pp, int rx_todo,
+- struct mvneta_rx_queue *rxq)
++/* Main rx processing when using hardware buffer management */
++static int mvneta_rx_hwbm(struct mvneta_port *pp, int rx_todo,
++ struct mvneta_rx_queue *rxq)
+ {
+ struct mvneta_pcpu_port *port = this_cpu_ptr(pp->ports);
+ struct net_device *dev = pp->dev;
+@@ -1628,21 +2004,29 @@ static int mvneta_rx(struct mvneta_port
+ /* Fairness NAPI loop */
+ while (rx_done < rx_todo) {
+ struct mvneta_rx_desc *rx_desc = mvneta_rxq_next_desc_get(rxq);
++ struct mvneta_bm_pool *bm_pool = NULL;
+ struct sk_buff *skb;
+ unsigned char *data;
+ dma_addr_t phys_addr;
+- u32 rx_status;
++ u32 rx_status, frag_size;
+ int rx_bytes, err;
++ u8 pool_id;
+
+ rx_done++;
+ rx_status = rx_desc->status;
+ rx_bytes = rx_desc->data_size - (ETH_FCS_LEN + MVNETA_MH_SIZE);
+ data = (unsigned char *)rx_desc->buf_cookie;
+ phys_addr = rx_desc->buf_phys_addr;
++ pool_id = MVNETA_RX_GET_BM_POOL_ID(rx_desc);
++ bm_pool = &pp->bm_priv->bm_pools[pool_id];
+
+ if (!mvneta_rxq_desc_is_first_last(rx_status) ||
+ (rx_status & MVNETA_RXD_ERR_SUMMARY)) {
+- err_drop_frame:
++err_drop_frame_ret_pool:
++ /* Return the buffer to the pool */
++ mvneta_bm_pool_put_bp(pp->bm_priv, bm_pool,
++ rx_desc->buf_phys_addr);
++err_drop_frame:
+ dev->stats.rx_errors++;
+ mvneta_rx_error(pp, rx_desc);
+ /* leave the descriptor untouched */
+@@ -1653,7 +2037,7 @@ static int mvneta_rx(struct mvneta_port
+ /* better copy a small frame and not unmap the DMA region */
+ skb = netdev_alloc_skb_ip_align(dev, rx_bytes);
+ if (unlikely(!skb))
+- goto err_drop_frame;
++ goto err_drop_frame_ret_pool;
+
+ dma_sync_single_range_for_cpu(dev->dev.parent,
+ rx_desc->buf_phys_addr,
+@@ -1671,26 +2055,31 @@ static int mvneta_rx(struct mvneta_port
+ rcvd_pkts++;
+ rcvd_bytes += rx_bytes;
+
++ /* Return the buffer to the pool */
++ mvneta_bm_pool_put_bp(pp->bm_priv, bm_pool,
++ rx_desc->buf_phys_addr);
++
+ /* leave the descriptor and buffer untouched */
+ continue;
+ }
+
+ /* Refill processing */
+- err = mvneta_rx_refill(pp, rx_desc);
++ err = mvneta_bm_pool_refill(pp->bm_priv, bm_pool);
+ if (err) {
+ netdev_err(dev, "Linux processing - Can't refill\n");
+ rxq->missed++;
+- goto err_drop_frame;
++ goto err_drop_frame_ret_pool;
+ }
+
+- skb = build_skb(data, pp->frag_size > PAGE_SIZE ? 0 : pp->frag_size);
++ frag_size = bm_pool->frag_size;
++
++ skb = build_skb(data, frag_size > PAGE_SIZE ? 0 : frag_size);
+
+ /* After refill old buffer has to be unmapped regardless
+ * the skb is successfully built or not.
+ */
+- dma_unmap_single(dev->dev.parent, phys_addr,
+- MVNETA_RX_BUF_SIZE(pp->pkt_size), DMA_FROM_DEVICE);
+-
++ dma_unmap_single(&pp->bm_priv->pdev->dev, phys_addr,
++ bm_pool->buf_size, DMA_FROM_DEVICE);
+ if (!skb)
+ goto err_drop_frame;
+
+@@ -2295,7 +2684,10 @@ static int mvneta_poll(struct napi_struc
+
+ if (rx_queue) {
+ rx_queue = rx_queue - 1;
+- rx_done = mvneta_rx(pp, budget, &pp->rxqs[rx_queue]);
++ if (pp->bm_priv)
++ rx_done = mvneta_rx_hwbm(pp, budget, &pp->rxqs[rx_queue]);
++ else
++ rx_done = mvneta_rx_swbm(pp, budget, &pp->rxqs[rx_queue]);
+ }
+
+ budget -= rx_done;
+@@ -2384,9 +2776,17 @@ static int mvneta_rxq_init(struct mvneta
+ mvneta_rx_pkts_coal_set(pp, rxq, rxq->pkts_coal);
+ mvneta_rx_time_coal_set(pp, rxq, rxq->time_coal);
+
+- /* Fill RXQ with buffers from RX pool */
+- mvneta_rxq_buf_size_set(pp, rxq, MVNETA_RX_BUF_SIZE(pp->pkt_size));
+- mvneta_rxq_bm_disable(pp, rxq);
++ if (!pp->bm_priv) {
++ /* Fill RXQ with buffers from RX pool */
++ mvneta_rxq_buf_size_set(pp, rxq,
++ MVNETA_RX_BUF_SIZE(pp->pkt_size));
++ mvneta_rxq_bm_disable(pp, rxq);
++ } else {
++ mvneta_rxq_bm_enable(pp, rxq);
++ mvneta_rxq_long_pool_set(pp, rxq);
++ mvneta_rxq_short_pool_set(pp, rxq);
++ }
++
+ mvneta_rxq_fill(pp, rxq, rxq->size);
+
+ return 0;
+@@ -2659,6 +3059,9 @@ static int mvneta_change_mtu(struct net_
+ dev->mtu = mtu;
+
+ if (!netif_running(dev)) {
++ if (pp->bm_priv)
++ mvneta_bm_update_mtu(pp, mtu);
++
+ netdev_update_features(dev);
+ return 0;
+ }
+@@ -2671,6 +3074,9 @@ static int mvneta_change_mtu(struct net_
+ mvneta_cleanup_txqs(pp);
+ mvneta_cleanup_rxqs(pp);
+
++ if (pp->bm_priv)
++ mvneta_bm_update_mtu(pp, mtu);
++
+ pp->pkt_size = MVNETA_RX_PKT_SIZE(dev->mtu);
+ pp->frag_size = SKB_DATA_ALIGN(MVNETA_RX_BUF_SIZE(pp->pkt_size)) +
+ SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
+@@ -3563,6 +3969,7 @@ static int mvneta_probe(struct platform_
+ struct resource *res;
+ struct device_node *dn = pdev->dev.of_node;
+ struct device_node *phy_node;
++ struct device_node *bm_node;
+ struct mvneta_port *pp;
+ struct net_device *dev;
+ const char *dt_mac_addr;
+@@ -3690,26 +4097,39 @@ static int mvneta_probe(struct platform_
+
+ pp->tx_csum_limit = tx_csum_limit;
+
++ dram_target_info = mv_mbus_dram_info();
++ if (dram_target_info)
++ mvneta_conf_mbus_windows(pp, dram_target_info);
++
+ pp->tx_ring_size = MVNETA_MAX_TXD;
+ pp->rx_ring_size = MVNETA_MAX_RXD;
+
+ pp->dev = dev;
+ SET_NETDEV_DEV(dev, &pdev->dev);
+
++ pp->id = global_port_id++;
++
++ /* Obtain access to BM resources if enabled and already initialized */
++ bm_node = of_parse_phandle(dn, "buffer-manager", 0);
++ if (bm_node && bm_node->data) {
++ pp->bm_priv = bm_node->data;
++ err = mvneta_bm_port_init(pdev, pp);
++ if (err < 0) {
++ dev_info(&pdev->dev, "use SW buffer management\n");
++ pp->bm_priv = NULL;
++ }
++ }
++
+ err = mvneta_init(&pdev->dev, pp);
+ if (err < 0)
+- goto err_free_stats;
++ goto err_netdev;
+
+ err = mvneta_port_power_up(pp, phy_mode);
+ if (err < 0) {
+ dev_err(&pdev->dev, "can't power up port\n");
+- goto err_free_stats;
++ goto err_netdev;
+ }
+
+- dram_target_info = mv_mbus_dram_info();
+- if (dram_target_info)
+- mvneta_conf_mbus_windows(pp, dram_target_info);
+-
+ for_each_present_cpu(cpu) {
+ struct mvneta_pcpu_port *port = per_cpu_ptr(pp->ports, cpu);
+
+@@ -3744,6 +4164,13 @@ static int mvneta_probe(struct platform_
+
+ return 0;
+
++err_netdev:
++ unregister_netdev(dev);
++ if (pp->bm_priv) {
++ mvneta_bm_pool_destroy(pp->bm_priv, pp->pool_long, 1 << pp->id);
++ mvneta_bm_pool_destroy(pp->bm_priv, pp->pool_short,
++ 1 << pp->id);
++ }
+ err_free_stats:
+ free_percpu(pp->stats);
+ err_free_ports:
+@@ -3773,6 +4200,12 @@ static int mvneta_remove(struct platform
+ of_node_put(pp->phy_node);
+ free_netdev(dev);
+
++ if (pp->bm_priv) {
++ mvneta_bm_pool_destroy(pp->bm_priv, pp->pool_long, 1 << pp->id);
++ mvneta_bm_pool_destroy(pp->bm_priv, pp->pool_short,
++ 1 << pp->id);
++ }
++
+ return 0;
+ }
+
+--- /dev/null
++++ b/drivers/net/ethernet/marvell/mvneta_bm.c
+@@ -0,0 +1,546 @@
++/*
++ * Driver for Marvell NETA network controller Buffer Manager.
++ *
++ * Copyright (C) 2015 Marvell
++ *
++ * Marcin Wojtas <mw@semihalf.com>
++ *
++ * This file is licensed under the terms of the GNU General Public
++ * License version 2. This program is licensed "as is" without any
++ * warranty of any kind, whether express or implied.
++ */
++
++#include <linux/kernel.h>
++#include <linux/genalloc.h>
++#include <linux/platform_device.h>
++#include <linux/netdevice.h>
++#include <linux/skbuff.h>
++#include <linux/mbus.h>
++#include <linux/module.h>
++#include <linux/io.h>
++#include <linux/of.h>
++#include <linux/clk.h>
++#include "mvneta_bm.h"
++
++#define MVNETA_BM_DRIVER_NAME "mvneta_bm"
++#define MVNETA_BM_DRIVER_VERSION "1.0"
++
++static void mvneta_bm_write(struct mvneta_bm *priv, u32 offset, u32 data)
++{
++ writel(data, priv->reg_base + offset);
++}
++
++static u32 mvneta_bm_read(struct mvneta_bm *priv, u32 offset)
++{
++ return readl(priv->reg_base + offset);
++}
++
++static void mvneta_bm_pool_enable(struct mvneta_bm *priv, int pool_id)
++{
++ u32 val;
++
++ val = mvneta_bm_read(priv, MVNETA_BM_POOL_BASE_REG(pool_id));
++ val |= MVNETA_BM_POOL_ENABLE_MASK;
++ mvneta_bm_write(priv, MVNETA_BM_POOL_BASE_REG(pool_id), val);
++
++ /* Clear BM cause register */
++ mvneta_bm_write(priv, MVNETA_BM_INTR_CAUSE_REG, 0);
++}
++
++static void mvneta_bm_pool_disable(struct mvneta_bm *priv, int pool_id)
++{
++ u32 val;
++
++ val = mvneta_bm_read(priv, MVNETA_BM_POOL_BASE_REG(pool_id));
++ val &= ~MVNETA_BM_POOL_ENABLE_MASK;
++ mvneta_bm_write(priv, MVNETA_BM_POOL_BASE_REG(pool_id), val);
++}
++
++static inline void mvneta_bm_config_set(struct mvneta_bm *priv, u32 mask)
++{
++ u32 val;
++
++ val = mvneta_bm_read(priv, MVNETA_BM_CONFIG_REG);
++ val |= mask;
++ mvneta_bm_write(priv, MVNETA_BM_CONFIG_REG, val);
++}
++
++static inline void mvneta_bm_config_clear(struct mvneta_bm *priv, u32 mask)
++{
++ u32 val;
++
++ val = mvneta_bm_read(priv, MVNETA_BM_CONFIG_REG);
++ val &= ~mask;
++ mvneta_bm_write(priv, MVNETA_BM_CONFIG_REG, val);
++}
++
++static void mvneta_bm_pool_target_set(struct mvneta_bm *priv, int pool_id,
++ u8 target_id, u8 attr)
++{
++ u32 val;
++
++ val = mvneta_bm_read(priv, MVNETA_BM_XBAR_POOL_REG(pool_id));
++ val &= ~MVNETA_BM_TARGET_ID_MASK(pool_id);
++ val &= ~MVNETA_BM_XBAR_ATTR_MASK(pool_id);
++ val |= MVNETA_BM_TARGET_ID_VAL(pool_id, target_id);
++ val |= MVNETA_BM_XBAR_ATTR_VAL(pool_id, attr);
++
++ mvneta_bm_write(priv, MVNETA_BM_XBAR_POOL_REG(pool_id), val);
++}
++
++/* Allocate skb for BM pool */
++void *mvneta_buf_alloc(struct mvneta_bm *priv, struct mvneta_bm_pool *bm_pool,
++ dma_addr_t *buf_phys_addr)
++{
++ void *buf;
++ dma_addr_t phys_addr;
++
++ buf = mvneta_frag_alloc(bm_pool->frag_size);
++ if (!buf)
++ return NULL;
++
++ /* In order to update buf_cookie field of RX descriptor properly,
++ * BM hardware expects buf virtual address to be placed in the
++ * first four bytes of mapped buffer.
++ */
++ *(u32 *)buf = (u32)buf;
++ phys_addr = dma_map_single(&priv->pdev->dev, buf, bm_pool->buf_size,
++ DMA_FROM_DEVICE);
++ if (unlikely(dma_mapping_error(&priv->pdev->dev, phys_addr))) {
++ mvneta_frag_free(bm_pool->frag_size, buf);
++ return NULL;
++ }
++ *buf_phys_addr = phys_addr;
++
++ return buf;
++}
++
++/* Refill processing for HW buffer management */
++int mvneta_bm_pool_refill(struct mvneta_bm *priv,
++ struct mvneta_bm_pool *bm_pool)
++{
++ dma_addr_t buf_phys_addr;
++ void *buf;
++
++ buf = mvneta_buf_alloc(priv, bm_pool, &buf_phys_addr);
++ if (!buf)
++ return -ENOMEM;
++
++ mvneta_bm_pool_put_bp(priv, bm_pool, buf_phys_addr);
++
++ return 0;
++}
++EXPORT_SYMBOL_GPL(mvneta_bm_pool_refill);
++
++/* Allocate buffers for the pool */
++int mvneta_bm_bufs_add(struct mvneta_bm *priv, struct mvneta_bm_pool *bm_pool,
++ int buf_num)
++{
++ int err, i;
++
++ if (bm_pool->buf_num == bm_pool->size) {
++ dev_dbg(&priv->pdev->dev, "pool %d already filled\n",
++ bm_pool->id);
++ return bm_pool->buf_num;
++ }
++
++ if (buf_num < 0 ||
++ (buf_num + bm_pool->buf_num > bm_pool->size)) {
++ dev_err(&priv->pdev->dev,
++ "cannot allocate %d buffers for pool %d\n",
++ buf_num, bm_pool->id);
++ return 0;
++ }
++
++ for (i = 0; i < buf_num; i++) {
++ err = mvneta_bm_pool_refill(priv, bm_pool);
++ if (err < 0)
++ break;
++ }
++
++ /* Update BM driver with number of buffers added to pool */
++ bm_pool->buf_num += i;
++
++ dev_dbg(&priv->pdev->dev,
++ "%s pool %d: pkt_size=%4d, buf_size=%4d, frag_size=%4d\n",
++ bm_pool->type == MVNETA_BM_SHORT ? "short" : "long",
++ bm_pool->id, bm_pool->pkt_size, bm_pool->buf_size,
++ bm_pool->frag_size);
++
++ dev_dbg(&priv->pdev->dev,
++ "%s pool %d: %d of %d buffers added\n",
++ bm_pool->type == MVNETA_BM_SHORT ? "short" : "long",
++ bm_pool->id, i, buf_num);
++
++ return i;
++}
++EXPORT_SYMBOL_GPL(mvneta_bm_bufs_add);
++
++/* Create pool */
++static int mvneta_bm_pool_create(struct mvneta_bm *priv,
++ struct mvneta_bm_pool *bm_pool)
++{
++ struct platform_device *pdev = priv->pdev;
++ u8 target_id, attr;
++ int size_bytes, err;
++
++ size_bytes = sizeof(u32) * bm_pool->size;
++ bm_pool->virt_addr = dma_alloc_coherent(&pdev->dev, size_bytes,
++ &bm_pool->phys_addr,
++ GFP_KERNEL);
++ if (!bm_pool->virt_addr)
++ return -ENOMEM;
++
++ if (!IS_ALIGNED((u32)bm_pool->virt_addr, MVNETA_BM_POOL_PTR_ALIGN)) {
++ dma_free_coherent(&pdev->dev, size_bytes, bm_pool->virt_addr,
++ bm_pool->phys_addr);
++ dev_err(&pdev->dev, "BM pool %d is not %d bytes aligned\n",
++ bm_pool->id, MVNETA_BM_POOL_PTR_ALIGN);
++ return -ENOMEM;
++ }
++
++ err = mvebu_mbus_get_dram_win_info(bm_pool->phys_addr, &target_id,
++ &attr);
++ if (err < 0) {
++ dma_free_coherent(&pdev->dev, size_bytes, bm_pool->virt_addr,
++ bm_pool->phys_addr);
++ return err;
++ }
++
++ /* Set pool address */
++ mvneta_bm_write(priv, MVNETA_BM_POOL_BASE_REG(bm_pool->id),
++ bm_pool->phys_addr);
++
++ mvneta_bm_pool_target_set(priv, bm_pool->id, target_id, attr);
++ mvneta_bm_pool_enable(priv, bm_pool->id);
++
++ return 0;
++}
++
++/* Notify the driver that BM pool is being used as specific type and return the
++ * pool pointer on success
++ */
++struct mvneta_bm_pool *mvneta_bm_pool_use(struct mvneta_bm *priv, u8 pool_id,
++ enum mvneta_bm_type type, u8 port_id,
++ int pkt_size)
++{
++ struct mvneta_bm_pool *new_pool = &priv->bm_pools[pool_id];
++ int num, err;
++
++ if (new_pool->type == MVNETA_BM_LONG &&
++ new_pool->port_map != 1 << port_id) {
++ dev_err(&priv->pdev->dev,
++ "long pool cannot be shared by the ports\n");
++ return NULL;
++ }
++
++ if (new_pool->type == MVNETA_BM_SHORT && new_pool->type != type) {
++ dev_err(&priv->pdev->dev,
++ "mixing pools' types between the ports is forbidden\n");
++ return NULL;
++ }
++
++ if (new_pool->pkt_size == 0 || type != MVNETA_BM_SHORT)
++ new_pool->pkt_size = pkt_size;
++
++ /* Allocate buffers in case BM pool hasn't been used yet */
++ if (new_pool->type == MVNETA_BM_FREE) {
++ new_pool->type = type;
++ new_pool->buf_size = MVNETA_RX_BUF_SIZE(new_pool->pkt_size);
++ new_pool->frag_size =
++ SKB_DATA_ALIGN(MVNETA_RX_BUF_SIZE(new_pool->pkt_size)) +
++ SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
++
++ /* Create new pool */
++ err = mvneta_bm_pool_create(priv, new_pool);
++ if (err) {
++ dev_err(&priv->pdev->dev, "fail to create pool %d\n",
++ new_pool->id);
++ return NULL;
++ }
++
++ /* Allocate buffers for this pool */
++ num = mvneta_bm_bufs_add(priv, new_pool, new_pool->size);
++ if (num != new_pool->size) {
++ WARN(1, "pool %d: %d of %d allocated\n",
++ new_pool->id, num, new_pool->size);
++ return NULL;
++ }
++ }
++
++ return new_pool;
++}
++EXPORT_SYMBOL_GPL(mvneta_bm_pool_use);
++
++/* Free all buffers from the pool */
++void mvneta_bm_bufs_free(struct mvneta_bm *priv, struct mvneta_bm_pool *bm_pool,
++ u8 port_map)
++{
++ int i;
++
++ bm_pool->port_map &= ~port_map;
++ if (bm_pool->port_map)
++ return;
++
++ mvneta_bm_config_set(priv, MVNETA_BM_EMPTY_LIMIT_MASK);
++
++ for (i = 0; i < bm_pool->buf_num; i++) {
++ dma_addr_t buf_phys_addr;
++ u32 *vaddr;
++
++ /* Get buffer physical address (indirect access) */
++ buf_phys_addr = mvneta_bm_pool_get_bp(priv, bm_pool);
++
++ /* Work-around to the problems when destroying the pool,
++ * when it occurs that a read access to BPPI returns 0.
++ */
++ if (buf_phys_addr == 0)
++ continue;
++
++ vaddr = phys_to_virt(buf_phys_addr);
++ if (!vaddr)
++ break;
++
++ dma_unmap_single(&priv->pdev->dev, buf_phys_addr,
++ bm_pool->buf_size, DMA_FROM_DEVICE);
++ mvneta_frag_free(bm_pool->frag_size, vaddr);
++ }
++
++ mvneta_bm_config_clear(priv, MVNETA_BM_EMPTY_LIMIT_MASK);
++
++ /* Update BM driver with number of buffers removed from pool */
++ bm_pool->buf_num -= i;
++}
++EXPORT_SYMBOL_GPL(mvneta_bm_bufs_free);
++
++/* Cleanup pool */
++void mvneta_bm_pool_destroy(struct mvneta_bm *priv,
++ struct mvneta_bm_pool *bm_pool, u8 port_map)
++{
++ bm_pool->port_map &= ~port_map;
++ if (bm_pool->port_map)
++ return;
++
++ bm_pool->type = MVNETA_BM_FREE;
++
++ mvneta_bm_bufs_free(priv, bm_pool, port_map);
++ if (bm_pool->buf_num)
++ WARN(1, "cannot free all buffers in pool %d\n", bm_pool->id);
++
++ if (bm_pool->virt_addr) {
++ dma_free_coherent(&priv->pdev->dev, sizeof(u32) * bm_pool->size,
++ bm_pool->virt_addr, bm_pool->phys_addr);
++ bm_pool->virt_addr = NULL;
++ }
++
++ mvneta_bm_pool_disable(priv, bm_pool->id);
++}
++EXPORT_SYMBOL_GPL(mvneta_bm_pool_destroy);
++
++static void mvneta_bm_pools_init(struct mvneta_bm *priv)
++{
++ struct device_node *dn = priv->pdev->dev.of_node;
++ struct mvneta_bm_pool *bm_pool;
++ char prop[15];
++ u32 size;
++ int i;
++
++ /* Activate BM unit */
++ mvneta_bm_write(priv, MVNETA_BM_COMMAND_REG, MVNETA_BM_START_MASK);
++
++ /* Create all pools with maximum size */
++ for (i = 0; i < MVNETA_BM_POOLS_NUM; i++) {
++ bm_pool = &priv->bm_pools[i];
++ bm_pool->id = i;
++ bm_pool->type = MVNETA_BM_FREE;
++
++ /* Reset read pointer */
++ mvneta_bm_write(priv, MVNETA_BM_POOL_READ_PTR_REG(i), 0);
++
++ /* Reset write pointer */
++ mvneta_bm_write(priv, MVNETA_BM_POOL_WRITE_PTR_REG(i), 0);
++
++ /* Configure pool size according to DT or use default value */
++ sprintf(prop, "pool%d,capacity", i);
++ if (of_property_read_u32(dn, prop, &size)) {
++ size = MVNETA_BM_POOL_CAP_DEF;
++ } else if (size > MVNETA_BM_POOL_CAP_MAX) {
++ dev_warn(&priv->pdev->dev,
++ "Illegal pool %d capacity %d, set to %d\n",
++ i, size, MVNETA_BM_POOL_CAP_MAX);
++ size = MVNETA_BM_POOL_CAP_MAX;
++ } else if (size < MVNETA_BM_POOL_CAP_MIN) {
++ dev_warn(&priv->pdev->dev,
++ "Illegal pool %d capacity %d, set to %d\n",
++ i, size, MVNETA_BM_POOL_CAP_MIN);
++ size = MVNETA_BM_POOL_CAP_MIN;
++ } else if (!IS_ALIGNED(size, MVNETA_BM_POOL_CAP_ALIGN)) {
++ dev_warn(&priv->pdev->dev,
++ "Illegal pool %d capacity %d, round to %d\n",
++ i, size, ALIGN(size,
++ MVNETA_BM_POOL_CAP_ALIGN));
++ size = ALIGN(size, MVNETA_BM_POOL_CAP_ALIGN);
++ }
++ bm_pool->size = size;
++
++ mvneta_bm_write(priv, MVNETA_BM_POOL_SIZE_REG(i),
++ bm_pool->size);
++
++ /* Obtain custom pkt_size from DT */
++ sprintf(prop, "pool%d,pkt-size", i);
++ if (of_property_read_u32(dn, prop, &bm_pool->pkt_size))
++ bm_pool->pkt_size = 0;
++ }
++}
++
++static void mvneta_bm_default_set(struct mvneta_bm *priv)
++{
++ u32 val;
++
++ /* Mask BM all interrupts */
++ mvneta_bm_write(priv, MVNETA_BM_INTR_MASK_REG, 0);
++
++ /* Clear BM cause register */
++ mvneta_bm_write(priv, MVNETA_BM_INTR_CAUSE_REG, 0);
++
++ /* Set BM configuration register */
++ val = mvneta_bm_read(priv, MVNETA_BM_CONFIG_REG);
++
++ /* Reduce MaxInBurstSize from 32 BPs to 16 BPs */
++ val &= ~MVNETA_BM_MAX_IN_BURST_SIZE_MASK;
++ val |= MVNETA_BM_MAX_IN_BURST_SIZE_16BP;
++ mvneta_bm_write(priv, MVNETA_BM_CONFIG_REG, val);
++}
++
++static int mvneta_bm_init(struct mvneta_bm *priv)
++{
++ mvneta_bm_default_set(priv);
++
++ /* Allocate and initialize BM pools structures */
++ priv->bm_pools = devm_kcalloc(&priv->pdev->dev, MVNETA_BM_POOLS_NUM,
++ sizeof(struct mvneta_bm_pool),
++ GFP_KERNEL);
++ if (!priv->bm_pools)
++ return -ENOMEM;
++
++ mvneta_bm_pools_init(priv);
++
++ return 0;
++}
++
++static int mvneta_bm_get_sram(struct device_node *dn,
++ struct mvneta_bm *priv)
++{
++ priv->bppi_pool = of_gen_pool_get(dn, "internal-mem", 0);
++ if (!priv->bppi_pool)
++ return -ENOMEM;
++
++ priv->bppi_virt_addr = gen_pool_dma_alloc(priv->bppi_pool,
++ MVNETA_BM_BPPI_SIZE,
++ &priv->bppi_phys_addr);
++ if (!priv->bppi_virt_addr)
++ return -ENOMEM;
++
++ return 0;
++}
++
++static void mvneta_bm_put_sram(struct mvneta_bm *priv)
++{
++ gen_pool_free(priv->bppi_pool, priv->bppi_phys_addr,
++ MVNETA_BM_BPPI_SIZE);
++}
++
++static int mvneta_bm_probe(struct platform_device *pdev)
++{
++ struct device_node *dn = pdev->dev.of_node;
++ struct mvneta_bm *priv;
++ struct resource *res;
++ int err;
++
++ priv = devm_kzalloc(&pdev->dev, sizeof(struct mvneta_bm), GFP_KERNEL);
++ if (!priv)
++ return -ENOMEM;
++
++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
++ priv->reg_base = devm_ioremap_resource(&pdev->dev, res);
++ if (IS_ERR(priv->reg_base))
++ return PTR_ERR(priv->reg_base);
++
++ priv->clk = devm_clk_get(&pdev->dev, NULL);
++ if (IS_ERR(priv->clk))
++ return PTR_ERR(priv->clk);
++ err = clk_prepare_enable(priv->clk);
++ if (err < 0)
++ return err;
++
++ err = mvneta_bm_get_sram(dn, priv);
++ if (err < 0) {
++ dev_err(&pdev->dev, "failed to allocate internal memory\n");
++ goto err_clk;
++ }
++
++ priv->pdev = pdev;
++
++ /* Initialize buffer manager internals */
++ err = mvneta_bm_init(priv);
++ if (err < 0) {
++ dev_err(&pdev->dev, "failed to initialize controller\n");
++ goto err_sram;
++ }
++
++ dn->data = priv;
++ platform_set_drvdata(pdev, priv);
++
++ dev_info(&pdev->dev, "Buffer Manager for network controller enabled\n");
++
++ return 0;
++
++err_sram:
++ mvneta_bm_put_sram(priv);
++err_clk:
++ clk_disable_unprepare(priv->clk);
++ return err;
++}
++
++static int mvneta_bm_remove(struct platform_device *pdev)
++{
++ struct mvneta_bm *priv = platform_get_drvdata(pdev);
++ u8 all_ports_map = 0xff;
++ int i = 0;
++
++ for (i = 0; i < MVNETA_BM_POOLS_NUM; i++) {
++ struct mvneta_bm_pool *bm_pool = &priv->bm_pools[i];
++
++ mvneta_bm_pool_destroy(priv, bm_pool, all_ports_map);
++ }
++
++ mvneta_bm_put_sram(priv);
++
++ /* Dectivate BM unit */
++ mvneta_bm_write(priv, MVNETA_BM_COMMAND_REG, MVNETA_BM_STOP_MASK);
++
++ clk_disable_unprepare(priv->clk);
++
++ return 0;
++}
++
++static const struct of_device_id mvneta_bm_match[] = {
++ { .compatible = "marvell,armada-380-neta-bm" },
++ { }
++};
++MODULE_DEVICE_TABLE(of, mvneta_bm_match);
++
++static struct platform_driver mvneta_bm_driver = {
++ .probe = mvneta_bm_probe,
++ .remove = mvneta_bm_remove,
++ .driver = {
++ .name = MVNETA_BM_DRIVER_NAME,
++ .of_match_table = mvneta_bm_match,
++ },
++};
++
++module_platform_driver(mvneta_bm_driver);
++
++MODULE_DESCRIPTION("Marvell NETA Buffer Manager Driver - www.marvell.com");
++MODULE_AUTHOR("Marcin Wojtas <mw@semihalf.com>");
++MODULE_LICENSE("GPL v2");
+--- /dev/null
++++ b/drivers/net/ethernet/marvell/mvneta_bm.h
+@@ -0,0 +1,189 @@
++/*
++ * Driver for Marvell NETA network controller Buffer Manager.
++ *
++ * Copyright (C) 2015 Marvell
++ *
++ * Marcin Wojtas <mw@semihalf.com>
++ *
++ * This file is licensed under the terms of the GNU General Public
++ * License version 2. This program is licensed "as is" without any
++ * warranty of any kind, whether express or implied.
++ */
++
++#ifndef _MVNETA_BM_H_
++#define _MVNETA_BM_H_
++
++/* BM Configuration Register */
++#define MVNETA_BM_CONFIG_REG 0x0
++#define MVNETA_BM_STATUS_MASK 0x30
++#define MVNETA_BM_ACTIVE_MASK BIT(4)
++#define MVNETA_BM_MAX_IN_BURST_SIZE_MASK 0x60000
++#define MVNETA_BM_MAX_IN_BURST_SIZE_16BP BIT(18)
++#define MVNETA_BM_EMPTY_LIMIT_MASK BIT(19)
++
++/* BM Activation Register */
++#define MVNETA_BM_COMMAND_REG 0x4
++#define MVNETA_BM_START_MASK BIT(0)
++#define MVNETA_BM_STOP_MASK BIT(1)
++#define MVNETA_BM_PAUSE_MASK BIT(2)
++
++/* BM Xbar interface Register */
++#define MVNETA_BM_XBAR_01_REG 0x8
++#define MVNETA_BM_XBAR_23_REG 0xc
++#define MVNETA_BM_XBAR_POOL_REG(pool) \
++ (((pool) < 2) ? MVNETA_BM_XBAR_01_REG : MVNETA_BM_XBAR_23_REG)
++#define MVNETA_BM_TARGET_ID_OFFS(pool) (((pool) & 1) ? 16 : 0)
++#define MVNETA_BM_TARGET_ID_MASK(pool) \
++ (0xf << MVNETA_BM_TARGET_ID_OFFS(pool))
++#define MVNETA_BM_TARGET_ID_VAL(pool, id) \
++ ((id) << MVNETA_BM_TARGET_ID_OFFS(pool))
++#define MVNETA_BM_XBAR_ATTR_OFFS(pool) (((pool) & 1) ? 20 : 4)
++#define MVNETA_BM_XBAR_ATTR_MASK(pool) \
++ (0xff << MVNETA_BM_XBAR_ATTR_OFFS(pool))
++#define MVNETA_BM_XBAR_ATTR_VAL(pool, attr) \
++ ((attr) << MVNETA_BM_XBAR_ATTR_OFFS(pool))
++
++/* Address of External Buffer Pointers Pool Register */
++#define MVNETA_BM_POOL_BASE_REG(pool) (0x10 + ((pool) << 4))
++#define MVNETA_BM_POOL_ENABLE_MASK BIT(0)
++
++/* External Buffer Pointers Pool RD pointer Register */
++#define MVNETA_BM_POOL_READ_PTR_REG(pool) (0x14 + ((pool) << 4))
++#define MVNETA_BM_POOL_SET_READ_PTR_MASK 0xfffc
++#define MVNETA_BM_POOL_GET_READ_PTR_OFFS 16
++#define MVNETA_BM_POOL_GET_READ_PTR_MASK 0xfffc0000
++
++/* External Buffer Pointers Pool WR pointer */
++#define MVNETA_BM_POOL_WRITE_PTR_REG(pool) (0x18 + ((pool) << 4))
++#define MVNETA_BM_POOL_SET_WRITE_PTR_OFFS 0
++#define MVNETA_BM_POOL_SET_WRITE_PTR_MASK 0xfffc
++#define MVNETA_BM_POOL_GET_WRITE_PTR_OFFS 16
++#define MVNETA_BM_POOL_GET_WRITE_PTR_MASK 0xfffc0000
++
++/* External Buffer Pointers Pool Size Register */
++#define MVNETA_BM_POOL_SIZE_REG(pool) (0x1c + ((pool) << 4))
++#define MVNETA_BM_POOL_SIZE_MASK 0x3fff
++
++/* BM Interrupt Cause Register */
++#define MVNETA_BM_INTR_CAUSE_REG (0x50)
++
++/* BM interrupt Mask Register */
++#define MVNETA_BM_INTR_MASK_REG (0x54)
++
++/* Other definitions */
++#define MVNETA_BM_SHORT_PKT_SIZE 256
++#define MVNETA_BM_POOLS_NUM 4
++#define MVNETA_BM_POOL_CAP_MIN 128
++#define MVNETA_BM_POOL_CAP_DEF 2048
++#define MVNETA_BM_POOL_CAP_MAX \
++ (16 * 1024 - MVNETA_BM_POOL_CAP_ALIGN)
++#define MVNETA_BM_POOL_CAP_ALIGN 32
++#define MVNETA_BM_POOL_PTR_ALIGN 32
++
++#define MVNETA_BM_POOL_ACCESS_OFFS 8
++
++#define MVNETA_BM_BPPI_SIZE 0x100000
++
++#define MVNETA_RX_BUF_SIZE(pkt_size) ((pkt_size) + NET_SKB_PAD)
++
++enum mvneta_bm_type {
++ MVNETA_BM_FREE,
++ MVNETA_BM_LONG,
++ MVNETA_BM_SHORT
++};
++
++struct mvneta_bm {
++ void __iomem *reg_base;
++ struct clk *clk;
++ struct platform_device *pdev;
++
++ struct gen_pool *bppi_pool;
++ /* BPPI virtual base address */
++ void __iomem *bppi_virt_addr;
++ /* BPPI physical base address */
++ dma_addr_t bppi_phys_addr;
++
++ /* BM pools */
++ struct mvneta_bm_pool *bm_pools;
++};
++
++struct mvneta_bm_pool {
++ /* Pool number in the range 0-3 */
++ u8 id;
++ enum mvneta_bm_type type;
++
++ /* Buffer Pointers Pool External (BPPE) size in number of bytes */
++ int size;
++ /* Number of buffers used by this pool */
++ int buf_num;
++ /* Pool buffer size */
++ int buf_size;
++ /* Packet size */
++ int pkt_size;
++ /* Single frag size */
++ u32 frag_size;
++
++ /* BPPE virtual base address */
++ u32 *virt_addr;
++ /* BPPE physical base address */
++ dma_addr_t phys_addr;
++
++ /* Ports using BM pool */
++ u8 port_map;
++
++ struct mvneta_bm *priv;
++};
++
++/* Declarations and definitions */
++void *mvneta_frag_alloc(unsigned int frag_size);
++void mvneta_frag_free(unsigned int frag_size, void *data);
++
++#if defined(CONFIG_MVNETA_BM) || defined(CONFIG_MVNETA_BM_MODULE)
++void mvneta_bm_pool_destroy(struct mvneta_bm *priv,
++ struct mvneta_bm_pool *bm_pool, u8 port_map);
++void mvneta_bm_bufs_free(struct mvneta_bm *priv, struct mvneta_bm_pool *bm_pool,
++ u8 port_map);
++int mvneta_bm_bufs_add(struct mvneta_bm *priv, struct mvneta_bm_pool *bm_pool,
++ int buf_num);
++int mvneta_bm_pool_refill(struct mvneta_bm *priv,
++ struct mvneta_bm_pool *bm_pool);
++struct mvneta_bm_pool *mvneta_bm_pool_use(struct mvneta_bm *priv, u8 pool_id,
++ enum mvneta_bm_type type, u8 port_id,
++ int pkt_size);
++
++static inline void mvneta_bm_pool_put_bp(struct mvneta_bm *priv,
++ struct mvneta_bm_pool *bm_pool,
++ dma_addr_t buf_phys_addr)
++{
++ writel_relaxed(buf_phys_addr, priv->bppi_virt_addr +
++ (bm_pool->id << MVNETA_BM_POOL_ACCESS_OFFS));
++}
++
++static inline u32 mvneta_bm_pool_get_bp(struct mvneta_bm *priv,
++ struct mvneta_bm_pool *bm_pool)
++{
++ return readl_relaxed(priv->bppi_virt_addr +
++ (bm_pool->id << MVNETA_BM_POOL_ACCESS_OFFS));
++}
++#else
++void mvneta_bm_pool_destroy(struct mvneta_bm *priv,
++ struct mvneta_bm_pool *bm_pool, u8 port_map) {}
++void mvneta_bm_bufs_free(struct mvneta_bm *priv, struct mvneta_bm_pool *bm_pool,
++ u8 port_map) {}
++int mvneta_bm_bufs_add(struct mvneta_bm *priv, struct mvneta_bm_pool *bm_pool,
++ int buf_num) { return 0; }
++int mvneta_bm_pool_refill(struct mvneta_bm *priv,
++ struct mvneta_bm_pool *bm_pool) {return 0; }
++struct mvneta_bm_pool *mvneta_bm_pool_use(struct mvneta_bm *priv, u8 pool_id,
++ enum mvneta_bm_type type, u8 port_id,
++ int pkt_size) { return NULL; }
++
++static inline void mvneta_bm_pool_put_bp(struct mvneta_bm *priv,
++ struct mvneta_bm_pool *bm_pool,
++ dma_addr_t buf_phys_addr) {}
++
++static inline u32 mvneta_bm_pool_get_bp(struct mvneta_bm *priv,
++ struct mvneta_bm_pool *bm_pool)
++{ return 0; }
++#endif /* CONFIG_MVNETA_BM */
++#endif
--- /dev/null
+From: Gregory CLEMENT <gregory.clement@free-electrons.com>
+Date: Mon, 14 Mar 2016 09:39:05 +0100
+Subject: [PATCH] net: mvneta: Use the new hwbm framework
+
+Now that the hardware buffer management framework had been introduced,
+let's use it.
+
+Tested-by: Sebastian Careba <nitroshift@yahoo.com>
+Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+
+--- a/drivers/net/ethernet/marvell/Kconfig
++++ b/drivers/net/ethernet/marvell/Kconfig
+@@ -43,6 +43,7 @@ config MVMDIO
+ config MVNETA_BM
+ tristate "Marvell Armada 38x/XP network interface BM support"
+ depends on MVNETA
++ select HWBM
+ ---help---
+ This driver supports auxiliary block of the network
+ interface units in the Marvell ARMADA XP and ARMADA 38x SoC
+--- a/drivers/net/ethernet/marvell/mvneta.c
++++ b/drivers/net/ethernet/marvell/mvneta.c
+@@ -30,6 +30,7 @@
+ #include <linux/phy.h>
+ #include <linux/platform_device.h>
+ #include <linux/skbuff.h>
++#include <net/hwbm.h>
+ #include "mvneta_bm.h"
+ #include <net/ip.h>
+ #include <net/ipv6.h>
+@@ -1024,11 +1025,12 @@ static int mvneta_bm_port_init(struct pl
+ static void mvneta_bm_update_mtu(struct mvneta_port *pp, int mtu)
+ {
+ struct mvneta_bm_pool *bm_pool = pp->pool_long;
++ struct hwbm_pool *hwbm_pool = &bm_pool->hwbm_pool;
+ int num;
+
+ /* Release all buffers from long pool */
+ mvneta_bm_bufs_free(pp->bm_priv, bm_pool, 1 << pp->id);
+- if (bm_pool->buf_num) {
++ if (hwbm_pool->buf_num) {
+ WARN(1, "cannot free all buffers in pool %d\n",
+ bm_pool->id);
+ goto bm_mtu_err;
+@@ -1036,14 +1038,14 @@ static void mvneta_bm_update_mtu(struct
+
+ bm_pool->pkt_size = MVNETA_RX_PKT_SIZE(mtu);
+ bm_pool->buf_size = MVNETA_RX_BUF_SIZE(bm_pool->pkt_size);
+- bm_pool->frag_size = SKB_DATA_ALIGN(sizeof(struct skb_shared_info)) +
+- SKB_DATA_ALIGN(MVNETA_RX_BUF_SIZE(bm_pool->pkt_size));
++ hwbm_pool->frag_size = SKB_DATA_ALIGN(sizeof(struct skb_shared_info)) +
++ SKB_DATA_ALIGN(MVNETA_RX_BUF_SIZE(bm_pool->pkt_size));
+
+ /* Fill entire long pool */
+- num = mvneta_bm_bufs_add(pp->bm_priv, bm_pool, bm_pool->size);
+- if (num != bm_pool->size) {
++ num = hwbm_pool_add(hwbm_pool, hwbm_pool->size, GFP_ATOMIC);
++ if (num != hwbm_pool->size) {
+ WARN(1, "pool %d: %d of %d allocated\n",
+- bm_pool->id, num, bm_pool->size);
++ bm_pool->id, num, hwbm_pool->size);
+ goto bm_mtu_err;
+ }
+ mvneta_bm_pool_bufsize_set(pp, bm_pool->buf_size, bm_pool->id);
+@@ -2064,14 +2066,14 @@ err_drop_frame:
+ }
+
+ /* Refill processing */
+- err = mvneta_bm_pool_refill(pp->bm_priv, bm_pool);
++ err = hwbm_pool_refill(&bm_pool->hwbm_pool, GFP_ATOMIC);
+ if (err) {
+ netdev_err(dev, "Linux processing - Can't refill\n");
+ rxq->missed++;
+ goto err_drop_frame_ret_pool;
+ }
+
+- frag_size = bm_pool->frag_size;
++ frag_size = bm_pool->hwbm_pool.frag_size;
+
+ skb = build_skb(data, frag_size > PAGE_SIZE ? 0 : frag_size);
+
+--- a/drivers/net/ethernet/marvell/mvneta_bm.c
++++ b/drivers/net/ethernet/marvell/mvneta_bm.c
+@@ -10,16 +10,17 @@
+ * warranty of any kind, whether express or implied.
+ */
+
+-#include <linux/kernel.h>
++#include <linux/clk.h>
+ #include <linux/genalloc.h>
+-#include <linux/platform_device.h>
+-#include <linux/netdevice.h>
+-#include <linux/skbuff.h>
++#include <linux/io.h>
++#include <linux/kernel.h>
+ #include <linux/mbus.h>
+ #include <linux/module.h>
+-#include <linux/io.h>
++#include <linux/netdevice.h>
+ #include <linux/of.h>
+-#include <linux/clk.h>
++#include <linux/platform_device.h>
++#include <linux/skbuff.h>
++#include <net/hwbm.h>
+ #include "mvneta_bm.h"
+
+ #define MVNETA_BM_DRIVER_NAME "mvneta_bm"
+@@ -88,17 +89,13 @@ static void mvneta_bm_pool_target_set(st
+ mvneta_bm_write(priv, MVNETA_BM_XBAR_POOL_REG(pool_id), val);
+ }
+
+-/* Allocate skb for BM pool */
+-void *mvneta_buf_alloc(struct mvneta_bm *priv, struct mvneta_bm_pool *bm_pool,
+- dma_addr_t *buf_phys_addr)
++int mvneta_bm_construct(struct hwbm_pool *hwbm_pool, void *buf)
+ {
+- void *buf;
++ struct mvneta_bm_pool *bm_pool =
++ (struct mvneta_bm_pool *)hwbm_pool->priv;
++ struct mvneta_bm *priv = bm_pool->priv;
+ dma_addr_t phys_addr;
+
+- buf = mvneta_frag_alloc(bm_pool->frag_size);
+- if (!buf)
+- return NULL;
+-
+ /* In order to update buf_cookie field of RX descriptor properly,
+ * BM hardware expects buf virtual address to be placed in the
+ * first four bytes of mapped buffer.
+@@ -106,75 +103,13 @@ void *mvneta_buf_alloc(struct mvneta_bm
+ *(u32 *)buf = (u32)buf;
+ phys_addr = dma_map_single(&priv->pdev->dev, buf, bm_pool->buf_size,
+ DMA_FROM_DEVICE);
+- if (unlikely(dma_mapping_error(&priv->pdev->dev, phys_addr))) {
+- mvneta_frag_free(bm_pool->frag_size, buf);
+- return NULL;
+- }
+- *buf_phys_addr = phys_addr;
+-
+- return buf;
+-}
+-
+-/* Refill processing for HW buffer management */
+-int mvneta_bm_pool_refill(struct mvneta_bm *priv,
+- struct mvneta_bm_pool *bm_pool)
+-{
+- dma_addr_t buf_phys_addr;
+- void *buf;
+-
+- buf = mvneta_buf_alloc(priv, bm_pool, &buf_phys_addr);
+- if (!buf)
++ if (unlikely(dma_mapping_error(&priv->pdev->dev, phys_addr)))
+ return -ENOMEM;
+
+- mvneta_bm_pool_put_bp(priv, bm_pool, buf_phys_addr);
+-
++ mvneta_bm_pool_put_bp(priv, bm_pool, phys_addr);
+ return 0;
+ }
+-EXPORT_SYMBOL_GPL(mvneta_bm_pool_refill);
+-
+-/* Allocate buffers for the pool */
+-int mvneta_bm_bufs_add(struct mvneta_bm *priv, struct mvneta_bm_pool *bm_pool,
+- int buf_num)
+-{
+- int err, i;
+-
+- if (bm_pool->buf_num == bm_pool->size) {
+- dev_dbg(&priv->pdev->dev, "pool %d already filled\n",
+- bm_pool->id);
+- return bm_pool->buf_num;
+- }
+-
+- if (buf_num < 0 ||
+- (buf_num + bm_pool->buf_num > bm_pool->size)) {
+- dev_err(&priv->pdev->dev,
+- "cannot allocate %d buffers for pool %d\n",
+- buf_num, bm_pool->id);
+- return 0;
+- }
+-
+- for (i = 0; i < buf_num; i++) {
+- err = mvneta_bm_pool_refill(priv, bm_pool);
+- if (err < 0)
+- break;
+- }
+-
+- /* Update BM driver with number of buffers added to pool */
+- bm_pool->buf_num += i;
+-
+- dev_dbg(&priv->pdev->dev,
+- "%s pool %d: pkt_size=%4d, buf_size=%4d, frag_size=%4d\n",
+- bm_pool->type == MVNETA_BM_SHORT ? "short" : "long",
+- bm_pool->id, bm_pool->pkt_size, bm_pool->buf_size,
+- bm_pool->frag_size);
+-
+- dev_dbg(&priv->pdev->dev,
+- "%s pool %d: %d of %d buffers added\n",
+- bm_pool->type == MVNETA_BM_SHORT ? "short" : "long",
+- bm_pool->id, i, buf_num);
+-
+- return i;
+-}
+-EXPORT_SYMBOL_GPL(mvneta_bm_bufs_add);
++EXPORT_SYMBOL_GPL(mvneta_bm_construct);
+
+ /* Create pool */
+ static int mvneta_bm_pool_create(struct mvneta_bm *priv,
+@@ -183,8 +118,7 @@ static int mvneta_bm_pool_create(struct
+ struct platform_device *pdev = priv->pdev;
+ u8 target_id, attr;
+ int size_bytes, err;
+-
+- size_bytes = sizeof(u32) * bm_pool->size;
++ size_bytes = sizeof(u32) * bm_pool->hwbm_pool.size;
+ bm_pool->virt_addr = dma_alloc_coherent(&pdev->dev, size_bytes,
+ &bm_pool->phys_addr,
+ GFP_KERNEL);
+@@ -245,11 +179,16 @@ struct mvneta_bm_pool *mvneta_bm_pool_us
+
+ /* Allocate buffers in case BM pool hasn't been used yet */
+ if (new_pool->type == MVNETA_BM_FREE) {
++ struct hwbm_pool *hwbm_pool = &new_pool->hwbm_pool;
++
++ new_pool->priv = priv;
+ new_pool->type = type;
+ new_pool->buf_size = MVNETA_RX_BUF_SIZE(new_pool->pkt_size);
+- new_pool->frag_size =
++ hwbm_pool->frag_size =
+ SKB_DATA_ALIGN(MVNETA_RX_BUF_SIZE(new_pool->pkt_size)) +
+ SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
++ hwbm_pool->construct = mvneta_bm_construct;
++ hwbm_pool->priv = new_pool;
+
+ /* Create new pool */
+ err = mvneta_bm_pool_create(priv, new_pool);
+@@ -260,10 +199,10 @@ struct mvneta_bm_pool *mvneta_bm_pool_us
+ }
+
+ /* Allocate buffers for this pool */
+- num = mvneta_bm_bufs_add(priv, new_pool, new_pool->size);
+- if (num != new_pool->size) {
++ num = hwbm_pool_add(hwbm_pool, hwbm_pool->size, GFP_ATOMIC);
++ if (num != hwbm_pool->size) {
+ WARN(1, "pool %d: %d of %d allocated\n",
+- new_pool->id, num, new_pool->size);
++ new_pool->id, num, hwbm_pool->size);
+ return NULL;
+ }
+ }
+@@ -284,7 +223,7 @@ void mvneta_bm_bufs_free(struct mvneta_b
+
+ mvneta_bm_config_set(priv, MVNETA_BM_EMPTY_LIMIT_MASK);
+
+- for (i = 0; i < bm_pool->buf_num; i++) {
++ for (i = 0; i < bm_pool->hwbm_pool.buf_num; i++) {
+ dma_addr_t buf_phys_addr;
+ u32 *vaddr;
+
+@@ -303,13 +242,13 @@ void mvneta_bm_bufs_free(struct mvneta_b
+
+ dma_unmap_single(&priv->pdev->dev, buf_phys_addr,
+ bm_pool->buf_size, DMA_FROM_DEVICE);
+- mvneta_frag_free(bm_pool->frag_size, vaddr);
++ hwbm_buf_free(&bm_pool->hwbm_pool, vaddr);
+ }
+
+ mvneta_bm_config_clear(priv, MVNETA_BM_EMPTY_LIMIT_MASK);
+
+ /* Update BM driver with number of buffers removed from pool */
+- bm_pool->buf_num -= i;
++ bm_pool->hwbm_pool.buf_num -= i;
+ }
+ EXPORT_SYMBOL_GPL(mvneta_bm_bufs_free);
+
+@@ -317,6 +256,7 @@ EXPORT_SYMBOL_GPL(mvneta_bm_bufs_free);
+ void mvneta_bm_pool_destroy(struct mvneta_bm *priv,
+ struct mvneta_bm_pool *bm_pool, u8 port_map)
+ {
++ struct hwbm_pool *hwbm_pool = &bm_pool->hwbm_pool;
+ bm_pool->port_map &= ~port_map;
+ if (bm_pool->port_map)
+ return;
+@@ -324,11 +264,12 @@ void mvneta_bm_pool_destroy(struct mvnet
+ bm_pool->type = MVNETA_BM_FREE;
+
+ mvneta_bm_bufs_free(priv, bm_pool, port_map);
+- if (bm_pool->buf_num)
++ if (hwbm_pool->buf_num)
+ WARN(1, "cannot free all buffers in pool %d\n", bm_pool->id);
+
+ if (bm_pool->virt_addr) {
+- dma_free_coherent(&priv->pdev->dev, sizeof(u32) * bm_pool->size,
++ dma_free_coherent(&priv->pdev->dev,
++ sizeof(u32) * hwbm_pool->size,
+ bm_pool->virt_addr, bm_pool->phys_addr);
+ bm_pool->virt_addr = NULL;
+ }
+@@ -381,10 +322,10 @@ static void mvneta_bm_pools_init(struct
+ MVNETA_BM_POOL_CAP_ALIGN));
+ size = ALIGN(size, MVNETA_BM_POOL_CAP_ALIGN);
+ }
+- bm_pool->size = size;
++ bm_pool->hwbm_pool.size = size;
+
+ mvneta_bm_write(priv, MVNETA_BM_POOL_SIZE_REG(i),
+- bm_pool->size);
++ bm_pool->hwbm_pool.size);
+
+ /* Obtain custom pkt_size from DT */
+ sprintf(prop, "pool%d,pkt-size", i);
+--- a/drivers/net/ethernet/marvell/mvneta_bm.h
++++ b/drivers/net/ethernet/marvell/mvneta_bm.h
+@@ -108,20 +108,15 @@ struct mvneta_bm {
+ };
+
+ struct mvneta_bm_pool {
++ struct hwbm_pool hwbm_pool;
+ /* Pool number in the range 0-3 */
+ u8 id;
+ enum mvneta_bm_type type;
+
+- /* Buffer Pointers Pool External (BPPE) size in number of bytes */
+- int size;
+- /* Number of buffers used by this pool */
+- int buf_num;
+- /* Pool buffer size */
+- int buf_size;
+ /* Packet size */
+ int pkt_size;
+- /* Single frag size */
+- u32 frag_size;
++ /* Size of the buffer acces through DMA*/
++ u32 buf_size;
+
+ /* BPPE virtual base address */
+ u32 *virt_addr;
+@@ -143,8 +138,7 @@ void mvneta_bm_pool_destroy(struct mvnet
+ struct mvneta_bm_pool *bm_pool, u8 port_map);
+ void mvneta_bm_bufs_free(struct mvneta_bm *priv, struct mvneta_bm_pool *bm_pool,
+ u8 port_map);
+-int mvneta_bm_bufs_add(struct mvneta_bm *priv, struct mvneta_bm_pool *bm_pool,
+- int buf_num);
++int mvneta_bm_construct(struct hwbm_pool *hwbm_pool, void *buf);
+ int mvneta_bm_pool_refill(struct mvneta_bm *priv,
+ struct mvneta_bm_pool *bm_pool);
+ struct mvneta_bm_pool *mvneta_bm_pool_use(struct mvneta_bm *priv, u8 pool_id,
+@@ -170,8 +164,7 @@ void mvneta_bm_pool_destroy(struct mvnet
+ struct mvneta_bm_pool *bm_pool, u8 port_map) {}
+ void mvneta_bm_bufs_free(struct mvneta_bm *priv, struct mvneta_bm_pool *bm_pool,
+ u8 port_map) {}
+-int mvneta_bm_bufs_add(struct mvneta_bm *priv, struct mvneta_bm_pool *bm_pool,
+- int buf_num) { return 0; }
++int mvneta_bm_construct(struct hwbm_pool *hwbm_pool, void *buf) { return 0; }
+ int mvneta_bm_pool_refill(struct mvneta_bm *priv,
+ struct mvneta_bm_pool *bm_pool) {return 0; }
+ struct mvneta_bm_pool *mvneta_bm_pool_use(struct mvneta_bm *priv, u8 pool_id,
--- /dev/null
+From: Gregory CLEMENT <gregory.clement@free-electrons.com>
+Date: Sat, 12 Mar 2016 18:44:17 +0100
+Subject: [PATCH] net: mvneta: Fix spinlock usage
+
+In the previous patch, the spinlock was not initialized. While it didn't
+cause any trouble yet it could be a problem to use it uninitialized.
+
+The most annoying part was the critical section protected by the spinlock
+in mvneta_stop(). Some of the functions could sleep as pointed when
+activated CONFIG_DEBUG_ATOMIC_SLEEP. Actually, in mvneta_stop() we only
+need to protect the is_stopped flagged, indeed the code of the notifier
+for CPU online is protected by the same spinlock, so when we get the
+lock, the notifer work is done.
+
+Reported-by: Patrick Uiterwijk <patrick@puiterwijk.org>
+Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+
+--- a/drivers/net/ethernet/marvell/mvneta.c
++++ b/drivers/net/ethernet/marvell/mvneta.c
+@@ -3484,17 +3484,17 @@ static int mvneta_stop(struct net_device
+ struct mvneta_port *pp = netdev_priv(dev);
+
+ /* Inform that we are stopping so we don't want to setup the
+- * driver for new CPUs in the notifiers
++ * driver for new CPUs in the notifiers. The code of the
++ * notifier for CPU online is protected by the same spinlock,
++ * so when we get the lock, the notifer work is done.
+ */
+ spin_lock(&pp->lock);
+ pp->is_stopped = true;
++ spin_unlock(&pp->lock);
++
+ mvneta_stop_dev(pp);
+ mvneta_mdio_remove(pp);
+ unregister_cpu_notifier(&pp->cpu_notifier);
+- /* Now that the notifier are unregistered, we can release le
+- * lock
+- */
+- spin_unlock(&pp->lock);
+ on_each_cpu(mvneta_percpu_disable, pp, true);
+ free_percpu_irq(dev->irq, pp->ports);
+ mvneta_cleanup_rxqs(pp);
+@@ -4027,6 +4027,7 @@ static int mvneta_probe(struct platform_
+ dev->ethtool_ops = &mvneta_eth_tool_ops;
+
+ pp = netdev_priv(dev);
++ spin_lock_init(&pp->lock);
+ pp->phy_node = phy_node;
+ pp->phy_interface = phy_mode;
+
--- /dev/null
+From: Dmitri Epshtein <dima@marvell.com>
+Date: Sat, 12 Mar 2016 18:44:19 +0100
+Subject: [PATCH] net: mvneta: fix error messages in mvneta_port_down function
+
+This commit corrects error printing when shutting down the port.
+
+[gregory.clement@free-electrons.com: split initial commit in two
+individual changes]
+Signed-off-by: Dmitri Epshtein <dima@marvell.com>
+Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+
+--- a/drivers/net/ethernet/marvell/mvneta.c
++++ b/drivers/net/ethernet/marvell/mvneta.c
+@@ -1105,7 +1105,7 @@ static void mvneta_port_down(struct mvne
+ do {
+ if (count++ >= MVNETA_RX_DISABLE_TIMEOUT_MSEC) {
+ netdev_warn(pp->dev,
+- "TIMEOUT for RX stopped ! rx_queue_cmd: 0x08%x\n",
++ "TIMEOUT for RX stopped ! rx_queue_cmd: 0x%08x\n",
+ val);
+ break;
+ }
+@@ -1144,7 +1144,7 @@ static void mvneta_port_down(struct mvne
+ do {
+ if (count++ >= MVNETA_TX_FIFO_EMPTY_TIMEOUT) {
+ netdev_warn(pp->dev,
+- "TX FIFO empty timeout status=0x08%x\n",
++ "TX FIFO empty timeout status=0x%08x\n",
+ val);
+ break;
+ }
--- /dev/null
+From: Jisheng Zhang <jszhang@marvell.com>
+Date: Wed, 30 Mar 2016 19:55:21 +0800
+Subject: [PATCH] net: mvneta: replace MVNETA_CPU_D_CACHE_LINE_SIZE with
+ L1_CACHE_BYTES
+
+The mvneta is also used in some Marvell berlin family SoCs which may
+have 64bytes cacheline size. Replace the MVNETA_CPU_D_CACHE_LINE_SIZE
+usage with L1_CACHE_BYTES.
+
+And since dma_alloc_coherent() is always cacheline size aligned, so
+remove the align checks.
+
+Signed-off-by: Jisheng Zhang <jszhang@marvell.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+
+--- a/drivers/net/ethernet/marvell/mvneta.c
++++ b/drivers/net/ethernet/marvell/mvneta.c
+@@ -260,7 +260,6 @@
+
+ #define MVNETA_VLAN_TAG_LEN 4
+
+-#define MVNETA_CPU_D_CACHE_LINE_SIZE 32
+ #define MVNETA_TX_CSUM_DEF_SIZE 1600
+ #define MVNETA_TX_CSUM_MAX_SIZE 9800
+ #define MVNETA_ACC_MODE_EXT1 1
+@@ -300,7 +299,7 @@
+ #define MVNETA_RX_PKT_SIZE(mtu) \
+ ALIGN((mtu) + MVNETA_MH_SIZE + MVNETA_VLAN_TAG_LEN + \
+ ETH_HLEN + ETH_FCS_LEN, \
+- MVNETA_CPU_D_CACHE_LINE_SIZE)
++ L1_CACHE_BYTES)
+
+ #define IS_TSO_HEADER(txq, addr) \
+ ((addr >= txq->tso_hdrs_phys) && \
+@@ -2762,9 +2761,6 @@ static int mvneta_rxq_init(struct mvneta
+ if (rxq->descs == NULL)
+ return -ENOMEM;
+
+- BUG_ON(rxq->descs !=
+- PTR_ALIGN(rxq->descs, MVNETA_CPU_D_CACHE_LINE_SIZE));
+-
+ rxq->last_desc = rxq->size - 1;
+
+ /* Set Rx descriptors queue starting address */
+@@ -2835,10 +2831,6 @@ static int mvneta_txq_init(struct mvneta
+ if (txq->descs == NULL)
+ return -ENOMEM;
+
+- /* Make sure descriptor address is cache line size aligned */
+- BUG_ON(txq->descs !=
+- PTR_ALIGN(txq->descs, MVNETA_CPU_D_CACHE_LINE_SIZE));
+-
+ txq->last_desc = txq->size - 1;
+
+ /* Set maximum bandwidth for enabled TXQs */
--- /dev/null
+From: Marcin Wojtas <mw@semihalf.com>
+Date: Fri, 1 Apr 2016 15:21:18 +0200
+Subject: [PATCH] net: mvneta: fix changing MTU when using per-cpu processing
+
+After enabling per-cpu processing it appeared that under heavy load
+changing MTU can result in blocking all port's interrupts and
+transmitting data is not possible after the change.
+
+This commit fixes above issue by disabling percpu interrupts for the
+time, when TXQs and RXQs are reconfigured.
+
+Signed-off-by: Marcin Wojtas <mw@semihalf.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+
+--- a/drivers/net/ethernet/marvell/mvneta.c
++++ b/drivers/net/ethernet/marvell/mvneta.c
+@@ -3040,6 +3040,20 @@ static int mvneta_check_mtu_valid(struct
+ return mtu;
+ }
+
++static void mvneta_percpu_enable(void *arg)
++{
++ struct mvneta_port *pp = arg;
++
++ enable_percpu_irq(pp->dev->irq, IRQ_TYPE_NONE);
++}
++
++static void mvneta_percpu_disable(void *arg)
++{
++ struct mvneta_port *pp = arg;
++
++ disable_percpu_irq(pp->dev->irq);
++}
++
+ /* Change the device mtu */
+ static int mvneta_change_mtu(struct net_device *dev, int mtu)
+ {
+@@ -3064,6 +3078,7 @@ static int mvneta_change_mtu(struct net_
+ * reallocation of the queues
+ */
+ mvneta_stop_dev(pp);
++ on_each_cpu(mvneta_percpu_disable, pp, true);
+
+ mvneta_cleanup_txqs(pp);
+ mvneta_cleanup_rxqs(pp);
+@@ -3087,6 +3102,7 @@ static int mvneta_change_mtu(struct net_
+ return ret;
+ }
+
++ on_each_cpu(mvneta_percpu_enable, pp, true);
+ mvneta_start_dev(pp);
+ mvneta_port_up(pp);
+
+@@ -3240,20 +3256,6 @@ static void mvneta_mdio_remove(struct mv
+ pp->phy_dev = NULL;
+ }
+
+-static void mvneta_percpu_enable(void *arg)
+-{
+- struct mvneta_port *pp = arg;
+-
+- enable_percpu_irq(pp->dev->irq, IRQ_TYPE_NONE);
+-}
+-
+-static void mvneta_percpu_disable(void *arg)
+-{
+- struct mvneta_port *pp = arg;
+-
+- disable_percpu_irq(pp->dev->irq);
+-}
+-
+ /* Electing a CPU must be done in an atomic way: it should be done
+ * after or before the removal/insertion of a CPU and this function is
+ * not reentrant.
--- /dev/null
+From: Marcin Wojtas <mw@semihalf.com>
+Date: Mon, 14 Mar 2016 09:38:57 +0100
+Subject: [PATCH] ARM: dts: armada-38x: add buffer manager nodes
+
+Armada 38x network controller supports hardware buffer management (BM).
+Since it is now enabled in mvneta driver, appropriate nodes can be added
+to armada-38x.dtsi - for the actual common BM unit (bm@c8000) and its
+internal SRAM (bm-bppi), which is used for indirect access to buffer
+pointer ring residing in DRAM.
+
+Pools - ports mapping, bm-bppi entry in 'soc' node's ranges and optional
+parameters are supposed to be set in board files.
+
+Signed-off-by: Marcin Wojtas <mw@semihalf.com>
+Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+
+--- a/arch/arm/boot/dts/armada-38x.dtsi
++++ b/arch/arm/boot/dts/armada-38x.dtsi
+@@ -540,6 +540,14 @@
+ status = "disabled";
+ };
+
++ bm: bm@c8000 {
++ compatible = "marvell,armada-380-neta-bm";
++ reg = <0xc8000 0xac>;
++ clocks = <&gateclk 13>;
++ internal-mem = <&bm_bppi>;
++ status = "disabled";
++ };
++
+ sata@e0000 {
+ compatible = "marvell,armada-380-ahci";
+ reg = <0xe0000 0x2000>;
+@@ -618,6 +626,17 @@
+ #size-cells = <1>;
+ ranges = <0 MBUS_ID(0x09, 0x15) 0 0x800>;
+ };
++
++ bm_bppi: bm-bppi {
++ compatible = "mmio-sram";
++ reg = <MBUS_ID(0x0c, 0x04) 0 0x100000>;
++ ranges = <0 MBUS_ID(0x0c, 0x04) 0 0x100000>;
++ #address-cells = <1>;
++ #size-cells = <1>;
++ clocks = <&gateclk 13>;
++ no-memory-wc;
++ status = "disabled";
++ };
+ };
+
+ clocks {
--- /dev/null
+From: Marcin Wojtas <mw@semihalf.com>
+Date: Mon, 14 Mar 2016 09:38:59 +0100
+Subject: [PATCH] ARM: dts: armada-xp: add buffer manager nodes
+
+Armada XP network controller supports hardware buffer management (BM).
+Since it is now enabled in mvneta driver, appropriate nodes can be added
+to armada-xp.dtsi - for the actual common BM unit (bm@c0000) and its
+internal SRAM (bm-bppi), which is used for indirect access to buffer
+pointer ring residing in DRAM.
+
+Pools - ports mapping, bm-bppi entry in 'soc' node's ranges and optional
+parameters are supposed to be set in board files.
+
+Signed-off-by: Marcin Wojtas <mw@semihalf.com>
+Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+
+--- a/arch/arm/boot/dts/armada-xp.dtsi
++++ b/arch/arm/boot/dts/armada-xp.dtsi
+@@ -253,6 +253,14 @@
+ marvell,crypto-sram-size = <0x800>;
+ };
+
++ bm: bm@c0000 {
++ compatible = "marvell,armada-380-neta-bm";
++ reg = <0xc0000 0xac>;
++ clocks = <&gateclk 13>;
++ internal-mem = <&bm_bppi>;
++ status = "disabled";
++ };
++
+ xor@f0900 {
+ compatible = "marvell,orion-xor";
+ reg = <0xF0900 0x100
+@@ -291,6 +299,17 @@
+ #size-cells = <1>;
+ ranges = <0 MBUS_ID(0x09, 0x05) 0 0x800>;
+ };
++
++ bm_bppi: bm-bppi {
++ compatible = "mmio-sram";
++ reg = <MBUS_ID(0x0c, 0x04) 0 0x100000>;
++ ranges = <0 MBUS_ID(0x0c, 0x04) 0 0x100000>;
++ #address-cells = <1>;
++ #size-cells = <1>;
++ clocks = <&gateclk 13>;
++ no-memory-wc;
++ status = "disabled";
++ };
+ };
+
+ clocks {
--- /dev/null
+From 4c945e8556ec7ea5b19d4f8721b212f468656e0d Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Sun, 6 Dec 2015 21:52:06 +0000
+Subject: [PATCH] ARM: dts: Add SolidRun Armada 388 Clearfog A1 DT file
+
+Add support for the SolidRun Armada 388 Clearfog A1 board. This board
+has an Armada 388 microsom, dedicated gigabit ethernet, six switched
+gigabit ethernet ports, SFP cage, two Mini-PCIe/mSATA slots, a m.2 SATA
+slot, and a MikroBUS connector to allow MikroBUS modules to be added.
+
+This DT file adds support for all board facilities with the exception
+of full SFP support.
+
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+Acked-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
+Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
+---
+ arch/arm/boot/dts/Makefile | 1 +
+ arch/arm/boot/dts/armada-388-clearfog.dts | 456 +++++++++++++++++++++
+ .../arm/boot/dts/armada-38x-solidrun-microsom.dtsi | 115 ++++++
+ 3 files changed, 572 insertions(+)
+ create mode 100644 arch/arm/boot/dts/armada-388-clearfog.dts
+ create mode 100644 arch/arm/boot/dts/armada-38x-solidrun-microsom.dtsi
+
+--- a/arch/arm/boot/dts/Makefile
++++ b/arch/arm/boot/dts/Makefile
+@@ -750,6 +750,7 @@ dtb-$(CONFIG_MACH_ARMADA_38X) += \
+ armada-385-linksys-cobra.dtb \
+ armada-385-linksys-rango.dtb \
+ armada-385-linksys-shelby.dtb \
++ armada-388-clearfog.dtb \
+ armada-388-db.dtb \
+ armada-388-gp.dtb \
+ armada-388-rd.dtb
+--- /dev/null
++++ b/arch/arm/boot/dts/armada-388-clearfog.dts
+@@ -0,0 +1,456 @@
++/*
++ * Device Tree file for SolidRun Clearfog revision A1 rev 2.0 (88F6828)
++ *
++ * Copyright (C) 2015 Russell King
++ *
++ * This board is in development; the contents of this file work with
++ * the A1 rev 2.0 of the board, which does not represent final
++ * production board. Things will change, don't expect this file to
++ * remain compatible info the future.
++ *
++ * This file is dual-licensed: you can use it either under the terms
++ * of the GPL or the X11 license, at your option. Note that this dual
++ * licensing only applies to this file, and not this project as a
++ * whole.
++ *
++ * a) This file is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * version 2 as published by the Free Software Foundation.
++ *
++ * This file 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.
++ *
++ * Or, alternatively
++ *
++ * b) Permission is hereby granted, free of charge, to any person
++ * obtaining a copy of this software and associated documentation
++ * files (the "Software"), to deal in the Software without
++ * restriction, including without limitation the rights to use
++ * copy, modify, merge, publish, distribute, sublicense, and/or
++ * sell copies of the Software, and to permit persons to whom the
++ * Software is furnished to do so, subject to the following
++ * conditions:
++ *
++ * The above copyright notice and this permission notice shall be
++ * included in all copies or substantial portions of the Software.
++ *
++ * THE SOFTWARE IS PROVIDED , WITHOUT WARRANTY OF ANY KIND
++ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
++ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
++ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
++ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY
++ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
++ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
++ * OTHER DEALINGS IN THE SOFTWARE.
++ */
++
++/dts-v1/;
++#include "armada-388.dtsi"
++#include "armada-38x-solidrun-microsom.dtsi"
++
++/ {
++ model = "SolidRun Clearfog A1";
++ compatible = "solidrun,clearfog-a1", "marvell,armada388",
++ "marvell,armada385", "marvell,armada380";
++
++ aliases {
++ /* So that mvebu u-boot can update the MAC addresses */
++ ethernet1 = ð0;
++ ethernet2 = ð1;
++ ethernet3 = ð2;
++ };
++
++ chosen {
++ stdout-path = "serial0:115200n8";
++ };
++
++ reg_3p3v: regulator-3p3v {
++ compatible = "regulator-fixed";
++ regulator-name = "3P3V";
++ regulator-min-microvolt = <3300000>;
++ regulator-max-microvolt = <3300000>;
++ regulator-always-on;
++ };
++
++ soc {
++ internal-regs {
++ ethernet@30000 {
++ phy-mode = "sgmii";
++ status = "okay";
++
++ fixed-link {
++ speed = <1000>;
++ full-duplex;
++ };
++ };
++
++ ethernet@34000 {
++ phy-mode = "sgmii";
++ status = "okay";
++
++ fixed-link {
++ speed = <1000>;
++ full-duplex;
++ };
++ };
++
++ i2c@11000 {
++ /* Is there anything on this? */
++ clock-frequency = <100000>;
++ pinctrl-0 = <&i2c0_pins>;
++ pinctrl-names = "default";
++ status = "okay";
++
++ /*
++ * PCA9655 GPIO expander, up to 1MHz clock.
++ * 0-CON3 CLKREQ#
++ * 1-CON3 PERST#
++ * 2-CON2 PERST#
++ * 3-CON3 W_DISABLE
++ * 4-CON2 CLKREQ#
++ * 5-USB3 overcurrent
++ * 6-USB3 power
++ * 7-CON2 W_DISABLE
++ * 8-JP4 P1
++ * 9-JP4 P4
++ * 10-JP4 P5
++ * 11-m.2 DEVSLP
++ * 12-SFP_LOS
++ * 13-SFP_TX_FAULT
++ * 14-SFP_TX_DISABLE
++ * 15-SFP_MOD_DEF0
++ */
++ expander0: gpio-expander@20 {
++ /*
++ * This is how it should be:
++ * compatible = "onnn,pca9655",
++ * "nxp,pca9555";
++ * but you can't do this because of
++ * the way I2C works.
++ */
++ compatible = "nxp,pca9555";
++ gpio-controller;
++ #gpio-cells = <2>;
++ reg = <0x20>;
++
++ pcie1_0_clkreq {
++ gpio-hog;
++ gpios = <0 GPIO_ACTIVE_LOW>;
++ input;
++ line-name = "pcie1.0-clkreq";
++ };
++ pcie1_0_w_disable {
++ gpio-hog;
++ gpios = <3 GPIO_ACTIVE_LOW>;
++ output-low;
++ line-name = "pcie1.0-w-disable";
++ };
++ pcie2_0_clkreq {
++ gpio-hog;
++ gpios = <4 GPIO_ACTIVE_LOW>;
++ input;
++ line-name = "pcie2.0-clkreq";
++ };
++ pcie2_0_w_disable {
++ gpio-hog;
++ gpios = <7 GPIO_ACTIVE_LOW>;
++ output-low;
++ line-name = "pcie2.0-w-disable";
++ };
++ usb3_ilimit {
++ gpio-hog;
++ gpios = <5 GPIO_ACTIVE_LOW>;
++ input;
++ line-name = "usb3-current-limit";
++ };
++ usb3_power {
++ gpio-hog;
++ gpios = <6 GPIO_ACTIVE_HIGH>;
++ output-high;
++ line-name = "usb3-power";
++ };
++ m2_devslp {
++ gpio-hog;
++ gpios = <11 GPIO_ACTIVE_HIGH>;
++ output-low;
++ line-name = "m.2 devslp";
++ };
++ sfp_los {
++ /* SFP loss of signal */
++ gpio-hog;
++ gpios = <12 GPIO_ACTIVE_HIGH>;
++ input;
++ line-name = "sfp-los";
++ };
++ sfp_tx_fault {
++ /* SFP laser fault */
++ gpio-hog;
++ gpios = <13 GPIO_ACTIVE_HIGH>;
++ input;
++ line-name = "sfp-tx-fault";
++ };
++ sfp_tx_disable {
++ /* SFP transmit disable */
++ gpio-hog;
++ gpios = <14 GPIO_ACTIVE_HIGH>;
++ output-low;
++ line-name = "sfp-tx-disable";
++ };
++ sfp_mod_def0 {
++ /* SFP module present */
++ gpio-hog;
++ gpios = <15 GPIO_ACTIVE_LOW>;
++ input;
++ line-name = "sfp-mod-def0";
++ };
++ };
++
++ /* The MCP3021 is 100kHz clock only */
++ mikrobus_adc: mcp3021@4c {
++ compatible = "microchip,mcp3021";
++ reg = <0x4c>;
++ };
++
++ /* Also something at 0x64 */
++ };
++
++ i2c@11100 {
++ /*
++ * Routed to SFP, mikrobus, and PCIe.
++ * SFP limits this to 100kHz, and requires
++ * an AT24C01A/02/04 with address pins tied
++ * low, which takes addresses 0x50 and 0x51.
++ * Mikrobus doesn't specify beyond an I2C
++ * bus being present.
++ * PCIe uses ARP to assign addresses, or
++ * 0x63-0x64.
++ */
++ clock-frequency = <100000>;
++ pinctrl-0 = <&clearfog_i2c1_pins>;
++ pinctrl-names = "default";
++ status = "okay";
++ };
++
++ mdio@72004 {
++ pinctrl-0 = <&mdio_pins>;
++ pinctrl-names = "default";
++
++ phy_dedicated: ethernet-phy@0 {
++ /*
++ * Annoyingly, the marvell phy driver
++ * configures the LED register, rather
++ * than preserving reset-loaded setting.
++ * We undo that rubbish here.
++ */
++ marvell,reg-init = <3 16 0 0x101e>;
++ reg = <0>;
++ };
++ };
++
++ pinctrl@18000 {
++ clearfog_dsa0_clk_pins: clearfog-dsa0-clk-pins {
++ marvell,pins = "mpp46";
++ marvell,function = "ref";
++ };
++ clearfog_dsa0_pins: clearfog-dsa0-pins {
++ marvell,pins = "mpp23", "mpp41";
++ marvell,function = "gpio";
++ };
++ clearfog_i2c1_pins: i2c1-pins {
++ /* SFP, PCIe, mSATA, mikrobus */
++ marvell,pins = "mpp26", "mpp27";
++ marvell,function = "i2c1";
++ };
++ clearfog_sdhci_cd_pins: clearfog-sdhci-cd-pins {
++ marvell,pins = "mpp20";
++ marvell,function = "gpio";
++ };
++ clearfog_sdhci_pins: clearfog-sdhci-pins {
++ marvell,pins = "mpp21", "mpp28",
++ "mpp37", "mpp38",
++ "mpp39", "mpp40";
++ marvell,function = "sd0";
++ };
++ clearfog_spi1_cs_pins: spi1-cs-pins {
++ marvell,pins = "mpp55";
++ marvell,function = "spi1";
++ };
++ mikro_pins: mikro-pins {
++ /* int: mpp22 rst: mpp29 */
++ marvell,pins = "mpp22", "mpp29";
++ marvell,function = "gpio";
++ };
++ mikro_spi_pins: mikro-spi-pins {
++ marvell,pins = "mpp43";
++ marvell,function = "spi1";
++ };
++ mikro_uart_pins: mikro-uart-pins {
++ marvell,pins = "mpp24", "mpp25";
++ marvell,function = "ua1";
++ };
++ rear_button_pins: rear-button-pins {
++ marvell,pins = "mpp34";
++ marvell,function = "gpio";
++ };
++ };
++
++ sata@a8000 {
++ /* pinctrl? */
++ status = "okay";
++ };
++
++ sata@e0000 {
++ /* pinctrl? */
++ status = "okay";
++ };
++
++ sdhci@d8000 {
++ bus-width = <4>;
++ cd-gpios = <&gpio0 20 GPIO_ACTIVE_LOW>;
++ no-1-8-v;
++ pinctrl-0 = <&clearfog_sdhci_pins
++ &clearfog_sdhci_cd_pins>;
++ pinctrl-names = "default";
++ status = "okay";
++ vmmc = <®_3p3v>;
++ wp-inverted;
++ };
++
++ serial@12100 {
++ /* mikrobus uart */
++ pinctrl-0 = <&mikro_uart_pins>;
++ pinctrl-names = "default";
++ status = "okay";
++ };
++
++ spi@10680 {
++ /*
++ * We don't seem to have the W25Q32 on the
++ * A1 Rev 2.0 boards, so disable SPI.
++ * CS0: W25Q32 (doesn't appear to be present)
++ * CS1:
++ * CS2: mikrobus
++ */
++ pinctrl-0 = <&spi1_pins
++ &clearfog_spi1_cs_pins
++ &mikro_spi_pins>;
++ pinctrl-names = "default";
++ status = "okay";
++
++ spi-flash@0 {
++ #address-cells = <1>;
++ #size-cells = <0>;
++ compatible = "w25q32", "jedec,spi-nor";
++ reg = <0>; /* Chip select 0 */
++ spi-max-frequency = <3000000>;
++ status = "disabled";
++ };
++ };
++
++ usb@58000 {
++ /* CON3, nearest power. */
++ status = "okay";
++ };
++
++ usb3@f0000 {
++ /* CON2, nearest CPU, USB2 only. */
++ status = "okay";
++ };
++
++ usb3@f8000 {
++ /* CON7 */
++ status = "okay";
++ };
++ };
++
++ pcie-controller {
++ status = "okay";
++ /*
++ * The two PCIe units are accessible through
++ * the mini-PCIe connectors on the board.
++ */
++ pcie@2,0 {
++ /* Port 1, Lane 0. CON3, nearest power. */
++ reset-gpios = <&expander0 1 GPIO_ACTIVE_LOW>;
++ status = "okay";
++ };
++ pcie@3,0 {
++ /* Port 2, Lane 0. CON2, nearest CPU. */
++ reset-gpios = <&expander0 2 GPIO_ACTIVE_LOW>;
++ status = "okay";
++ };
++ };
++ };
++
++ dsa@0 {
++ compatible = "marvell,dsa";
++ dsa,ethernet = <ð1>;
++ dsa,mii-bus = <&mdio>;
++ pinctrl-0 = <&clearfog_dsa0_clk_pins &clearfog_dsa0_pins>;
++ pinctrl-names = "default";
++ #address-cells = <2>;
++ #size-cells = <0>;
++
++ switch@0 {
++ #address-cells = <1>;
++ #size-cells = <0>;
++ reg = <4 0>;
++
++ port@0 {
++ reg = <0>;
++ label = "lan1";
++ };
++
++ port@1 {
++ reg = <1>;
++ label = "lan2";
++ };
++
++ port@2 {
++ reg = <2>;
++ label = "lan3";
++ };
++
++ port@3 {
++ reg = <3>;
++ label = "lan4";
++ };
++
++ port@4 {
++ reg = <4>;
++ label = "lan5";
++ };
++
++ port@5 {
++ reg = <5>;
++ label = "cpu";
++ };
++
++ port@6 {
++ /* 88E1512 external phy */
++ reg = <6>;
++ label = "lan6";
++ fixed-link {
++ speed = <1000>;
++ full-duplex;
++ };
++ };
++ };
++ };
++
++ gpio-keys {
++ compatible = "gpio-keys";
++ pinctrl-0 = <&rear_button_pins>;
++ pinctrl-names = "default";
++
++ button_0 {
++ /* The rear SW3 button */
++ label = "Rear Button";
++ gpios = <&gpio1 2 GPIO_ACTIVE_LOW>;
++ linux,can-disable;
++ linux,code = <BTN_0>;
++ };
++ };
++};
+--- /dev/null
++++ b/arch/arm/boot/dts/armada-38x-solidrun-microsom.dtsi
+@@ -0,0 +1,115 @@
++/*
++ * Device Tree file for SolidRun Armada 38x Microsom
++ *
++ * Copyright (C) 2015 Russell King
++ *
++ * This board is in development; the contents of this file work with
++ * the A1 rev 2.0 of the board, which does not represent final
++ * production board. Things will change, don't expect this file to
++ * remain compatible info the future.
++ *
++ * This file is dual-licensed: you can use it either under the terms
++ * of the GPL or the X11 license, at your option. Note that this dual
++ * licensing only applies to this file, and not this project as a
++ * whole.
++ *
++ * a) This file is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * version 2 as published by the Free Software Foundation.
++ *
++ * This file 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.
++ *
++ * Or, alternatively
++ *
++ * b) Permission is hereby granted, free of charge, to any person
++ * obtaining a copy of this software and associated documentation
++ * files (the "Software"), to deal in the Software without
++ * restriction, including without limitation the rights to use
++ * copy, modify, merge, publish, distribute, sublicense, and/or
++ * sell copies of the Software, and to permit persons to whom the
++ * Software is furnished to do so, subject to the following
++ * conditions:
++ *
++ * The above copyright notice and this permission notice shall be
++ * included in all copies or substantial portions of the Software.
++ *
++ * THE SOFTWARE IS PROVIDED , WITHOUT WARRANTY OF ANY KIND
++ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
++ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
++ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
++ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY
++ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
++ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
++ * OTHER DEALINGS IN THE SOFTWARE.
++ */
++#include <dt-bindings/input/input.h>
++#include <dt-bindings/gpio/gpio.h>
++
++/ {
++ memory {
++ device_type = "memory";
++ reg = <0x00000000 0x10000000>; /* 256 MB */
++ };
++
++ soc {
++ ranges = <MBUS_ID(0xf0, 0x01) 0 0xf1000000 0x100000
++ MBUS_ID(0x01, 0x1d) 0 0xfff00000 0x100000
++ MBUS_ID(0x09, 0x19) 0 0xf1100000 0x10000
++ MBUS_ID(0x09, 0x15) 0 0xf1110000 0x10000>;
++
++ internal-regs {
++ ethernet@70000 {
++ pinctrl-0 = <&ge0_rgmii_pins>;
++ pinctrl-names = "default";
++ phy = <&phy_dedicated>;
++ phy-mode = "rgmii-id";
++ status = "okay";
++ };
++
++ mdio@72004 {
++ /*
++ * Add the phy clock here, so the phy can be
++ * accessed to read its IDs prior to binding
++ * with the driver.
++ */
++ pinctrl-0 = <&mdio_pins µsom_phy_clk_pins>;
++ pinctrl-names = "default";
++
++ phy_dedicated: ethernet-phy@0 {
++ /*
++ * Annoyingly, the marvell phy driver
++ * configures the LED register, rather
++ * than preserving reset-loaded setting.
++ * We undo that rubbish here.
++ */
++ marvell,reg-init = <3 16 0 0x101e>;
++ reg = <0>;
++ };
++ };
++
++ pinctrl@18000 {
++ microsom_phy_clk_pins: microsom-phy-clk-pins {
++ marvell,pins = "mpp45";
++ marvell,function = "ref";
++ };
++ };
++
++ rtc@a3800 {
++ /*
++ * If the rtc doesn't work, run "date reset"
++ * twice in u-boot.
++ */
++ status = "okay";
++ };
++
++ serial@12000 {
++ pinctrl-0 = <&uart0_pins>;
++ pinctrl-names = "default";
++ status = "okay";
++ };
++ };
++ };
++};
--- /dev/null
+From c49e99c2b25a412623412a461bb751239208b9b3 Mon Sep 17 00:00:00 2001
+From: Marcin Wojtas <mw@semihalf.com>
+Date: Mon, 14 Mar 2016 09:38:58 +0100
+Subject: [PATCH] ARM: dts: armada-38x: enable buffer manager support on Armada
+ 38x boards
+
+Since mvneta driver supports using hardware buffer management (BM), in
+order to use it, board files have to be adjusted accordingly. This commit
+enables BM on:
+* A385-DB-AP - each port has its own pool for long and common pool for
+short packets,
+* A388-ClearFog - same as above,
+* A388-DB - to each port unique 'short' and 'long' pools are mapped,
+* A388-GP - same as above.
+
+Moreover appropriate entry is added to 'soc' node ranges, as well as "okay"
+status for 'bm' and 'bm-bppi' (internal SRAM) nodes.
+
+[gregory.clement@free-electrons.com: add suppport for the ClearFog board]
+
+Signed-off-by: Marcin Wojtas <mw@semihalf.com>
+Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
+Acked-by: Russell King <rmk+kernel@arm.linux.org.uk>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ arch/arm/boot/dts/armada-385-db-ap.dts | 20 +++++++++++++++++++-
+ arch/arm/boot/dts/armada-388-clearfog.dts | 6 ++++++
+ arch/arm/boot/dts/armada-388-db.dts | 17 ++++++++++++++++-
+ arch/arm/boot/dts/armada-388-gp.dts | 17 ++++++++++++++++-
+ arch/arm/boot/dts/armada-38x-solidrun-microsom.dtsi | 15 ++++++++++++++-
+ 5 files changed, 71 insertions(+), 4 deletions(-)
+
+--- a/arch/arm/boot/dts/armada-385-db-ap.dts
++++ b/arch/arm/boot/dts/armada-385-db-ap.dts
+@@ -61,7 +61,8 @@
+ ranges = <MBUS_ID(0xf0, 0x01) 0 0xf1000000 0x100000
+ MBUS_ID(0x01, 0x1d) 0 0xfff00000 0x100000
+ MBUS_ID(0x09, 0x19) 0 0xf1100000 0x10000
+- MBUS_ID(0x09, 0x15) 0 0xf1110000 0x10000>;
++ MBUS_ID(0x09, 0x15) 0 0xf1110000 0x10000
++ MBUS_ID(0x0c, 0x04) 0 0xf1200000 0x100000>;
+
+ internal-regs {
+ spi1: spi@10680 {
+@@ -138,12 +139,18 @@
+ status = "okay";
+ phy = <&phy2>;
+ phy-mode = "sgmii";
++ buffer-manager = <&bm>;
++ bm,pool-long = <1>;
++ bm,pool-short = <3>;
+ };
+
+ ethernet@34000 {
+ status = "okay";
+ phy = <&phy1>;
+ phy-mode = "sgmii";
++ buffer-manager = <&bm>;
++ bm,pool-long = <2>;
++ bm,pool-short = <3>;
+ };
+
+ ethernet@70000 {
+@@ -157,6 +164,13 @@
+ status = "okay";
+ phy = <&phy0>;
+ phy-mode = "rgmii-id";
++ buffer-manager = <&bm>;
++ bm,pool-long = <0>;
++ bm,pool-short = <3>;
++ };
++
++ bm@c8000 {
++ status = "okay";
+ };
+
+ nfc: flash@d0000 {
+@@ -178,6 +192,10 @@
+ };
+ };
+
++ bm-bppi {
++ status = "okay";
++ };
++
+ pcie-controller {
+ status = "okay";
+
+--- a/arch/arm/boot/dts/armada-388-clearfog.dts
++++ b/arch/arm/boot/dts/armada-388-clearfog.dts
+@@ -78,6 +78,9 @@
+ internal-regs {
+ ethernet@30000 {
+ phy-mode = "sgmii";
++ buffer-manager = <&bm>;
++ bm,pool-long = <2>;
++ bm,pool-short = <1>;
+ status = "okay";
+
+ fixed-link {
+@@ -88,6 +91,9 @@
+
+ ethernet@34000 {
+ phy-mode = "sgmii";
++ buffer-manager = <&bm>;
++ bm,pool-long = <3>;
++ bm,pool-short = <1>;
+ status = "okay";
+
+ fixed-link {
+--- a/arch/arm/boot/dts/armada-388-db.dts
++++ b/arch/arm/boot/dts/armada-388-db.dts
+@@ -66,7 +66,8 @@
+ ranges = <MBUS_ID(0xf0, 0x01) 0 0xf1000000 0x100000
+ MBUS_ID(0x01, 0x1d) 0 0xfff00000 0x100000
+ MBUS_ID(0x09, 0x19) 0 0xf1100000 0x10000
+- MBUS_ID(0x09, 0x15) 0 0xf1110000 0x10000>;
++ MBUS_ID(0x09, 0x15) 0 0xf1110000 0x10000
++ MBUS_ID(0x0c, 0x04) 0 0xf1200000 0x100000>;
+
+ internal-regs {
+ spi@10600 {
+@@ -99,6 +100,9 @@
+ status = "okay";
+ phy = <&phy1>;
+ phy-mode = "rgmii-id";
++ buffer-manager = <&bm>;
++ bm,pool-long = <2>;
++ bm,pool-short = <3>;
+ };
+
+ usb@58000 {
+@@ -109,6 +113,9 @@
+ status = "okay";
+ phy = <&phy0>;
+ phy-mode = "rgmii-id";
++ buffer-manager = <&bm>;
++ bm,pool-long = <0>;
++ bm,pool-short = <1>;
+ };
+
+ mdio@72004 {
+@@ -129,6 +136,10 @@
+ status = "okay";
+ };
+
++ bm@c8000 {
++ status = "okay";
++ };
++
+ flash@d0000 {
+ status = "okay";
+ num-cs = <1>;
+@@ -169,6 +180,10 @@
+ };
+ };
+
++ bm-bppi {
++ status = "okay";
++ };
++
+ pcie-controller {
+ status = "okay";
+ /*
+--- a/arch/arm/boot/dts/armada-388-gp.dts
++++ b/arch/arm/boot/dts/armada-388-gp.dts
+@@ -60,7 +60,8 @@
+ ranges = <MBUS_ID(0xf0, 0x01) 0 0xf1000000 0x100000
+ MBUS_ID(0x01, 0x1d) 0 0xfff00000 0x100000
+ MBUS_ID(0x09, 0x19) 0 0xf1100000 0x10000
+- MBUS_ID(0x09, 0x15) 0 0xf1110000 0x10000>;
++ MBUS_ID(0x09, 0x15) 0 0xf1110000 0x10000
++ MBUS_ID(0x0c, 0x04) 0 0xf1200000 0x100000>;
+
+ internal-regs {
+ spi@10600 {
+@@ -133,6 +134,9 @@
+ status = "okay";
+ phy = <&phy1>;
+ phy-mode = "rgmii-id";
++ buffer-manager = <&bm>;
++ bm,pool-long = <2>;
++ bm,pool-short = <3>;
+ };
+
+ /* CON4 */
+@@ -152,6 +156,9 @@
+ status = "okay";
+ phy = <&phy0>;
+ phy-mode = "rgmii-id";
++ buffer-manager = <&bm>;
++ bm,pool-long = <0>;
++ bm,pool-short = <1>;
+ };
+
+
+@@ -186,6 +193,10 @@
+ };
+ };
+
++ bm@c8000 {
++ status = "okay";
++ };
++
+ sata@e0000 {
+ pinctrl-names = "default";
+ pinctrl-0 = <&sata2_pins>, <&sata3_pins>;
+@@ -240,6 +251,10 @@
+ };
+ };
+
++ bm-bppi {
++ status = "okay";
++ };
++
+ pcie-controller {
+ status = "okay";
+ /*
+--- a/arch/arm/boot/dts/armada-38x-solidrun-microsom.dtsi
++++ b/arch/arm/boot/dts/armada-38x-solidrun-microsom.dtsi
+@@ -58,7 +58,8 @@
+ ranges = <MBUS_ID(0xf0, 0x01) 0 0xf1000000 0x100000
+ MBUS_ID(0x01, 0x1d) 0 0xfff00000 0x100000
+ MBUS_ID(0x09, 0x19) 0 0xf1100000 0x10000
+- MBUS_ID(0x09, 0x15) 0 0xf1110000 0x10000>;
++ MBUS_ID(0x09, 0x15) 0 0xf1110000 0x10000
++ MBUS_ID(0x0c, 0x04) 0 0xf1200000 0x100000>;
+
+ internal-regs {
+ ethernet@70000 {
+@@ -66,6 +67,9 @@
+ pinctrl-names = "default";
+ phy = <&phy_dedicated>;
+ phy-mode = "rgmii-id";
++ buffer-manager = <&bm>;
++ bm,pool-long = <0>;
++ bm,pool-short = <1>;
+ status = "okay";
+ };
+
+@@ -110,6 +114,15 @@
+ pinctrl-names = "default";
+ status = "okay";
+ };
++
++ bm@c8000 {
++ status = "okay";
++ };
+ };
++
++ bm-bppi {
++ status = "okay";
++ };
++
+ };
+ };
--- /dev/null
+From d261861ab52623e34a25fe6ae76714456edda033 Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@armlinux.org.uk>
+Date: Sun, 10 Jul 2016 16:27:38 +0100
+Subject: [PATCH] ARM: dts: armada-388-clearfog: remove duplicate mdio entry
+
+The clearfog DTS should not be defining the on-board phy, this device
+is located on the microsom. Remove the duplicated definition.
+
+Reported-by: Jon Nettleton <jon@solid-run.com>
+Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
+---
+ arch/arm/boot/dts/armada-388-clearfog.dts | 16 ----------------
+ 1 file changed, 16 deletions(-)
+
+--- a/arch/arm/boot/dts/armada-388-clearfog.dts
++++ b/arch/arm/boot/dts/armada-388-clearfog.dts
+@@ -239,22 +239,6 @@
+ status = "okay";
+ };
+
+- mdio@72004 {
+- pinctrl-0 = <&mdio_pins>;
+- pinctrl-names = "default";
+-
+- phy_dedicated: ethernet-phy@0 {
+- /*
+- * Annoyingly, the marvell phy driver
+- * configures the LED register, rather
+- * than preserving reset-loaded setting.
+- * We undo that rubbish here.
+- */
+- marvell,reg-init = <3 16 0 0x101e>;
+- reg = <0>;
+- };
+- };
+-
+ pinctrl@18000 {
+ clearfog_dsa0_clk_pins: clearfog-dsa0-clk-pins {
+ marvell,pins = "mpp46";
--- /dev/null
+The WRT1900AC among other Linksys routers uses a dual-firmware layout.
+Dynamically rename the active partition to "ubi".
+
+Signed-off-by: Imre Kaloz <kaloz@openwrt.org>
+
+--- a/drivers/mtd/ofpart.c
++++ b/drivers/mtd/ofpart.c
+@@ -25,6 +25,8 @@ static bool node_has_compatible(struct d
+ return of_get_property(pp, "compatible", NULL);
+ }
+
++static int mangled_rootblock;
++
+ static int parse_ofpart_partitions(struct mtd_info *master,
+ struct mtd_partition **pparts,
+ struct mtd_part_parser_data *data)
+@@ -32,6 +34,7 @@ static int parse_ofpart_partitions(struc
+ struct device_node *mtd_node;
+ struct device_node *ofpart_node;
+ const char *partname;
++ const char *owrtpart = "ubi";
+ struct device_node *pp;
+ int nr_parts, i, ret = 0;
+ bool dedicated = true;
+@@ -110,9 +113,15 @@ static int parse_ofpart_partitions(struc
+ (*pparts)[i].offset = of_read_number(reg, a_cells);
+ (*pparts)[i].size = of_read_number(reg + a_cells, s_cells);
+
+- partname = of_get_property(pp, "label", &len);
+- if (!partname)
+- partname = of_get_property(pp, "name", &len);
++ if (mangled_rootblock && (i == mangled_rootblock)) {
++ partname = owrtpart;
++ } else {
++ partname = of_get_property(pp, "label", &len);
++
++ if (!partname)
++ partname = of_get_property(pp, "name", &len);
++ }
++
+ (*pparts)[i].name = partname;
+
+ if (of_get_property(pp, "read-only", &len))
+@@ -215,6 +224,18 @@ static int __init ofpart_parser_init(voi
+ return 0;
+ }
+
++static int __init active_root(char *str)
++{
++ get_option(&str, &mangled_rootblock);
++
++ if (!mangled_rootblock)
++ return 1;
++
++ return 1;
++}
++
++__setup("mangled_rootblock=", active_root);
++
+ static void __exit ofpart_parser_exit(void)
+ {
+ deregister_mtd_parser(&ofpart_parser);
--- /dev/null
+--- a/arch/arm/boot/dts/armada-xp.dtsi
++++ b/arch/arm/boot/dts/armada-xp.dtsi
+@@ -98,12 +98,10 @@
+
+
+ i2c0: i2c@11000 {
+- compatible = "marvell,mv78230-i2c", "marvell,mv64xxx-i2c";
+ reg = <0x11000 0x100>;
+ };
+
+ i2c1: i2c@11100 {
+- compatible = "marvell,mv78230-i2c", "marvell,mv64xxx-i2c";
+ reg = <0x11100 0x100>;
+ };
+
--- /dev/null
+Remove a BUG() call that would crash on a race condition that should
+otherwise be harmless.
+
+--- a/drivers/mtd/nand/pxa3xx_nand.c
++++ b/drivers/mtd/nand/pxa3xx_nand.c
+@@ -701,7 +701,6 @@ static void handle_data_pio(struct pxa3x
+ default:
+ dev_err(&info->pdev->dev, "%s: invalid state %d\n", __func__,
+ info->state);
+- BUG();
+ }
+
+ /* Update buffer pointers for multi-page read/write */
--- /dev/null
+--- a/arch/arm/boot/dts/armada-xp-linksys-mamba.dts
++++ b/arch/arm/boot/dts/armada-xp-linksys-mamba.dts
+@@ -298,7 +298,6 @@
+ nand@d0000 {
+ status = "okay";
+ num-cs = <1>;
+- marvell,nand-keep-config;
+ marvell,nand-enable-arbiter;
+ nand-on-flash-bbt;
+ nand-ecc-strength = <4>;
--- /dev/null
+--- a/arch/arm/boot/dts/armada-385-linksys.dtsi
++++ b/arch/arm/boot/dts/armada-385-linksys.dtsi
+@@ -59,7 +59,8 @@
+ ranges = <MBUS_ID(0xf0, 0x01) 0 0xf1000000 0x100000
+ MBUS_ID(0x01, 0x1d) 0 0xfff00000 0x100000
+ MBUS_ID(0x09, 0x19) 0 0xf1100000 0x10000
+- MBUS_ID(0x09, 0x15) 0 0xf1110000 0x10000>;
++ MBUS_ID(0x09, 0x15) 0 0xf1110000 0x10000
++ MBUS_ID(0x0c, 0x04) 0 0xf1200000 0x100000>;
+
+ internal-regs {
+
+@@ -93,6 +94,9 @@
+ ethernet@70000 {
+ status = "okay";
+ phy-mode = "rgmii-id";
++ buffer-manager = <&bm>;
++ bm,pool-long = <0>;
++ bm,pool-short = <3>;
+ fixed-link {
+ speed = <1000>;
+ full-duplex;
+@@ -102,6 +106,9 @@
+ ethernet@34000 {
+ status = "okay";
+ phy-mode = "sgmii";
++ buffer-manager = <&bm>;
++ bm,pool-long = <2>;
++ bm,pool-short = <3>;
+ fixed-link {
+ speed = <1000>;
+ full-duplex;
+@@ -112,6 +119,10 @@
+ status = "okay";
+ };
+
++ bm@c8000 {
++ status = "okay";
++ };
++
+ sata@a8000 {
+ status = "okay";
+ };
+@@ -198,6 +209,10 @@
+ };
+ };
+
++ bm-bppi {
++ status = "okay";
++ };
++
+ pcie-controller {
+ status = "okay";
+
+--- a/arch/arm/boot/dts/armada-xp-linksys-mamba.dts
++++ b/arch/arm/boot/dts/armada-xp-linksys-mamba.dts
+@@ -71,7 +71,8 @@
+ ranges = <MBUS_ID(0xf0, 0x01) 0 0 0xf1000000 0x100000
+ MBUS_ID(0x01, 0x1d) 0 0 0xfff00000 0x100000
+ MBUS_ID(0x09, 0x09) 0 0 0xf1100000 0x10000
+- MBUS_ID(0x09, 0x05) 0 0 0xf1110000 0x10000>;
++ MBUS_ID(0x09, 0x05) 0 0 0xf1110000 0x10000
++ MBUS_ID(0x0c, 0x04) 0 0 0xf1200000 0x100000>;
+
+ pcie-controller {
+ status = "okay";
+@@ -205,6 +206,9 @@
+ pinctrl-names = "default";
+ status = "okay";
+ phy-mode = "rgmii-id";
++ buffer-manager = <&bm>;
++ bm,pool-long = <0>;
++ bm,pool-short = <3>;
+ fixed-link {
+ speed = <1000>;
+ full-duplex;
+@@ -216,12 +220,19 @@
+ pinctrl-names = "default";
+ status = "okay";
+ phy-mode = "rgmii-id";
++ buffer-manager = <&bm>;
++ bm,pool-long = <1>;
++ bm,pool-short = <3>;
+ fixed-link {
+ speed = <1000>;
+ full-duplex;
+ };
+ };
+
++ bm@c0000 {
++ status = "okay";
++ };
++
+ /* USB part of the eSATA/USB 2.0 port */
+ usb@50000 {
+ status = "okay";
+@@ -379,6 +390,10 @@
+ };
+ };
+ };
++
++ bm-bppi {
++ status = "okay";
++ };
+ };
+
+ gpio_keys {
--- /dev/null
+Revert "mtd: pxa3xx-nand: handle PIO in threaded interrupt"
+
+This reverts commit 24542257a3b987025d4b998ec2d15e556c98ad3f
+This upstream change has been causing spurious timeouts on accesses
+to the NAND flash if something else on the system is causing
+significant latency.
+
+Nothing guarantees that the thread will run in time, so the
+usual timeout is unreliable.
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+
+--- a/drivers/mtd/nand/pxa3xx_nand.c
++++ b/drivers/mtd/nand/pxa3xx_nand.c
+@@ -765,24 +765,11 @@ static void start_data_dma(struct pxa3xx
+ __func__, direction, info->dma_cookie, info->sg.length);
+ }
+
+-static irqreturn_t pxa3xx_nand_irq_thread(int irq, void *data)
+-{
+- struct pxa3xx_nand_info *info = data;
+-
+- handle_data_pio(info);
+-
+- info->state = STATE_CMD_DONE;
+- nand_writel(info, NDSR, NDSR_WRDREQ | NDSR_RDDREQ);
+-
+- return IRQ_HANDLED;
+-}
+-
+ static irqreturn_t pxa3xx_nand_irq(int irq, void *devid)
+ {
+ struct pxa3xx_nand_info *info = devid;
+ unsigned int status, is_completed = 0, is_ready = 0;
+ unsigned int ready, cmd_done;
+- irqreturn_t ret = IRQ_HANDLED;
+
+ if (info->cs == 0) {
+ ready = NDSR_FLASH_RDY;
+@@ -824,8 +811,7 @@ static irqreturn_t pxa3xx_nand_irq(int i
+ } else {
+ info->state = (status & NDSR_RDDREQ) ?
+ STATE_PIO_READING : STATE_PIO_WRITING;
+- ret = IRQ_WAKE_THREAD;
+- goto NORMAL_IRQ_EXIT;
++ handle_data_pio(info);
+ }
+ }
+ if (status & cmd_done) {
+@@ -870,7 +856,7 @@ static irqreturn_t pxa3xx_nand_irq(int i
+ if (is_ready)
+ complete(&info->dev_ready);
+ NORMAL_IRQ_EXIT:
+- return ret;
++ return IRQ_HANDLED;
+ }
+
+ static inline int is_buf_blank(uint8_t *buf, size_t len)
+@@ -1849,9 +1835,7 @@ static int alloc_nand_resource(struct pl
+ /* initialize all interrupts to be disabled */
+ disable_int(info, NDSR_MASK);
+
+- ret = request_threaded_irq(irq, pxa3xx_nand_irq,
+- pxa3xx_nand_irq_thread, IRQF_ONESHOT,
+- pdev->name, info);
++ ret = request_irq(irq, pxa3xx_nand_irq, 0, pdev->name, info);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to request IRQ\n");
+ goto fail_free_buf;
--- /dev/null
+From 4d5621372f6e7ddbfd5879602f82073987bcc722 Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Sun, 20 Sep 2015 09:57:10 +0100
+Subject: [PATCH 709/744] phy: move fixed_phy MII register generation to a
+ library
+
+Move the fixed_phy MII register generation to a library to allow other
+software phy implementations to use this code.
+
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ drivers/net/phy/Kconfig | 4 ++
+ drivers/net/phy/Makefile | 3 +-
+ drivers/net/phy/fixed_phy.c | 95 ++-------------------------------
+ drivers/net/phy/swphy.c | 126 ++++++++++++++++++++++++++++++++++++++++++++
+ drivers/net/phy/swphy.h | 8 +++
+ 5 files changed, 143 insertions(+), 93 deletions(-)
+ create mode 100644 drivers/net/phy/swphy.c
+ create mode 100644 drivers/net/phy/swphy.h
+
+--- a/drivers/net/phy/Kconfig
++++ b/drivers/net/phy/Kconfig
+@@ -26,6 +26,9 @@ config SWCONFIG_LEDS
+ bool "Switch LED trigger support"
+ depends on (SWCONFIG && LEDS_TRIGGERS)
+
++config SWPHY
++ bool
++
+ comment "MII PHY device drivers"
+
+ config AQUANTIA_PHY
+@@ -205,6 +208,7 @@ config RTL8306_PHY
+ config FIXED_PHY
+ tristate "Driver for MDIO Bus/PHY emulation with fixed speed/link PHYs"
+ depends on PHYLIB
++ select SWPHY
+ ---help---
+ Adds the platform "fixed" MDIO Bus to cover the boards that use
+ PHYs that are not connected to the real MDIO bus.
+--- a/drivers/net/phy/Makefile
++++ b/drivers/net/phy/Makefile
+@@ -1,6 +1,7 @@
+ # Makefile for Linux PHY drivers
+
+-libphy-objs := phy.o phy_device.o mdio_bus.o
++libphy-y := phy.o phy_device.o mdio_bus.o
++libphy-$(CONFIG_SWPHY) += swphy.o
+
+ obj-$(CONFIG_MDIO_BOARDINFO) += mdio-boardinfo.o
+
+--- a/drivers/net/phy/fixed_phy.c
++++ b/drivers/net/phy/fixed_phy.c
+@@ -24,6 +24,8 @@
+ #include <linux/of.h>
+ #include <linux/gpio.h>
+
++#include "swphy.h"
++
+ #define MII_REGS_NUM 29
+
+ struct fixed_mdio_bus {
+@@ -49,101 +51,10 @@ static struct fixed_mdio_bus platform_fm
+
+ static int fixed_phy_update_regs(struct fixed_phy *fp)
+ {
+- u16 bmsr = BMSR_ANEGCAPABLE;
+- u16 bmcr = 0;
+- u16 lpagb = 0;
+- u16 lpa = 0;
+-
+ if (gpio_is_valid(fp->link_gpio))
+ fp->status.link = !!gpio_get_value_cansleep(fp->link_gpio);
+
+- if (fp->status.duplex) {
+- switch (fp->status.speed) {
+- case 1000:
+- bmsr |= BMSR_ESTATEN;
+- break;
+- case 100:
+- bmsr |= BMSR_100FULL;
+- break;
+- case 10:
+- bmsr |= BMSR_10FULL;
+- break;
+- default:
+- break;
+- }
+- } else {
+- switch (fp->status.speed) {
+- case 1000:
+- bmsr |= BMSR_ESTATEN;
+- break;
+- case 100:
+- bmsr |= BMSR_100HALF;
+- break;
+- case 10:
+- bmsr |= BMSR_10HALF;
+- break;
+- default:
+- break;
+- }
+- }
+-
+- if (fp->status.link) {
+- bmsr |= BMSR_LSTATUS | BMSR_ANEGCOMPLETE;
+-
+- if (fp->status.duplex) {
+- bmcr |= BMCR_FULLDPLX;
+-
+- switch (fp->status.speed) {
+- case 1000:
+- bmcr |= BMCR_SPEED1000;
+- lpagb |= LPA_1000FULL;
+- break;
+- case 100:
+- bmcr |= BMCR_SPEED100;
+- lpa |= LPA_100FULL;
+- break;
+- case 10:
+- lpa |= LPA_10FULL;
+- break;
+- default:
+- pr_warn("fixed phy: unknown speed\n");
+- return -EINVAL;
+- }
+- } else {
+- switch (fp->status.speed) {
+- case 1000:
+- bmcr |= BMCR_SPEED1000;
+- lpagb |= LPA_1000HALF;
+- break;
+- case 100:
+- bmcr |= BMCR_SPEED100;
+- lpa |= LPA_100HALF;
+- break;
+- case 10:
+- lpa |= LPA_10HALF;
+- break;
+- default:
+- pr_warn("fixed phy: unknown speed\n");
+- return -EINVAL;
+- }
+- }
+-
+- if (fp->status.pause)
+- lpa |= LPA_PAUSE_CAP;
+-
+- if (fp->status.asym_pause)
+- lpa |= LPA_PAUSE_ASYM;
+- }
+-
+- fp->regs[MII_PHYSID1] = 0;
+- fp->regs[MII_PHYSID2] = 0;
+-
+- fp->regs[MII_BMSR] = bmsr;
+- fp->regs[MII_BMCR] = bmcr;
+- fp->regs[MII_LPA] = lpa;
+- fp->regs[MII_STAT1000] = lpagb;
+-
+- return 0;
++ return swphy_update_regs(fp->regs, &fp->status);
+ }
+
+ static int fixed_mdio_read(struct mii_bus *bus, int phy_addr, int reg_num)
+--- /dev/null
++++ b/drivers/net/phy/swphy.c
+@@ -0,0 +1,126 @@
++/*
++ * Software PHY emulation
++ *
++ * Code taken from fixed_phy.c by Russell King <rmk+kernel@arm.linux.org.uk>
++ *
++ * Author: Vitaly Bordug <vbordug@ru.mvista.com>
++ * Anton Vorontsov <avorontsov@ru.mvista.com>
++ *
++ * Copyright (c) 2006-2007 MontaVista Software, Inc.
++ *
++ * 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.
++ */
++#include <linux/export.h>
++#include <linux/mii.h>
++#include <linux/phy.h>
++#include <linux/phy_fixed.h>
++
++#include "swphy.h"
++
++/**
++ * swphy_update_regs - update MII register array with fixed phy state
++ * @regs: array of 32 registers to update
++ * @state: fixed phy status
++ *
++ * Update the array of MII registers with the fixed phy link, speed,
++ * duplex and pause mode settings.
++ */
++int swphy_update_regs(u16 *regs, const struct fixed_phy_status *state)
++{
++ u16 bmsr = BMSR_ANEGCAPABLE;
++ u16 bmcr = 0;
++ u16 lpagb = 0;
++ u16 lpa = 0;
++
++ if (state->duplex) {
++ switch (state->speed) {
++ case 1000:
++ bmsr |= BMSR_ESTATEN;
++ break;
++ case 100:
++ bmsr |= BMSR_100FULL;
++ break;
++ case 10:
++ bmsr |= BMSR_10FULL;
++ break;
++ default:
++ break;
++ }
++ } else {
++ switch (state->speed) {
++ case 1000:
++ bmsr |= BMSR_ESTATEN;
++ break;
++ case 100:
++ bmsr |= BMSR_100HALF;
++ break;
++ case 10:
++ bmsr |= BMSR_10HALF;
++ break;
++ default:
++ break;
++ }
++ }
++
++ if (state->link) {
++ bmsr |= BMSR_LSTATUS | BMSR_ANEGCOMPLETE;
++
++ if (state->duplex) {
++ bmcr |= BMCR_FULLDPLX;
++
++ switch (state->speed) {
++ case 1000:
++ bmcr |= BMCR_SPEED1000;
++ lpagb |= LPA_1000FULL;
++ break;
++ case 100:
++ bmcr |= BMCR_SPEED100;
++ lpa |= LPA_100FULL;
++ break;
++ case 10:
++ lpa |= LPA_10FULL;
++ break;
++ default:
++ pr_warn("swphy: unknown speed\n");
++ return -EINVAL;
++ }
++ } else {
++ switch (state->speed) {
++ case 1000:
++ bmcr |= BMCR_SPEED1000;
++ lpagb |= LPA_1000HALF;
++ break;
++ case 100:
++ bmcr |= BMCR_SPEED100;
++ lpa |= LPA_100HALF;
++ break;
++ case 10:
++ lpa |= LPA_10HALF;
++ break;
++ default:
++ pr_warn("swphy: unknown speed\n");
++ return -EINVAL;
++ }
++ }
++
++ if (state->pause)
++ lpa |= LPA_PAUSE_CAP;
++
++ if (state->asym_pause)
++ lpa |= LPA_PAUSE_ASYM;
++ }
++
++ regs[MII_PHYSID1] = 0;
++ regs[MII_PHYSID2] = 0;
++
++ regs[MII_BMSR] = bmsr;
++ regs[MII_BMCR] = bmcr;
++ regs[MII_LPA] = lpa;
++ regs[MII_STAT1000] = lpagb;
++
++ return 0;
++}
++EXPORT_SYMBOL_GPL(swphy_update_regs);
+--- /dev/null
++++ b/drivers/net/phy/swphy.h
+@@ -0,0 +1,8 @@
++#ifndef SWPHY_H
++#define SWPHY_H
++
++struct fixed_phy_status;
++
++int swphy_update_regs(u16 *regs, const struct fixed_phy_status *state);
++
++#endif
--- /dev/null
+From cd834fe430f030a63bfa9277bba194e8eef4dbd0 Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Sun, 20 Sep 2015 10:18:59 +0100
+Subject: [PATCH 710/744] phy: convert swphy register generation to tabular
+ form
+
+Convert the swphy register generation to tabular form which allows us
+to eliminate multiple switch() statements. This results in a smaller
+object code size, more efficient, and easier to add support for faster
+speeds.
+
+Before:
+
+Idx Name Size VMA LMA File off Algn
+ 0 .text 00000164 00000000 00000000 00000034 2**2
+
+ text data bss dec hex filename
+ 388 0 0 388 184 swphy.o
+
+After:
+
+Idx Name Size VMA LMA File off Algn
+ 0 .text 000000fc 00000000 00000000 00000034 2**2
+ 5 .rodata 00000028 00000000 00000000 00000138 2**2
+
+ text data bss dec hex filename
+ 324 0 0 324 144 swphy.o
+
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ drivers/net/phy/swphy.c | 143 ++++++++++++++++++++++++++----------------------
+ 1 file changed, 78 insertions(+), 65 deletions(-)
+
+--- a/drivers/net/phy/swphy.c
++++ b/drivers/net/phy/swphy.c
+@@ -20,6 +20,72 @@
+
+ #include "swphy.h"
+
++struct swmii_regs {
++ u16 bmcr;
++ u16 bmsr;
++ u16 lpa;
++ u16 lpagb;
++};
++
++enum {
++ SWMII_SPEED_10 = 0,
++ SWMII_SPEED_100,
++ SWMII_SPEED_1000,
++ SWMII_DUPLEX_HALF = 0,
++ SWMII_DUPLEX_FULL,
++};
++
++/*
++ * These two tables get bitwise-anded together to produce the final result.
++ * This means the speed table must contain both duplex settings, and the
++ * duplex table must contain all speed settings.
++ */
++static const struct swmii_regs speed[] = {
++ [SWMII_SPEED_10] = {
++ .bmcr = BMCR_FULLDPLX,
++ .lpa = LPA_10FULL | LPA_10HALF,
++ },
++ [SWMII_SPEED_100] = {
++ .bmcr = BMCR_FULLDPLX | BMCR_SPEED100,
++ .bmsr = BMSR_100FULL | BMSR_100HALF,
++ .lpa = LPA_100FULL | LPA_100HALF,
++ },
++ [SWMII_SPEED_1000] = {
++ .bmcr = BMCR_FULLDPLX | BMCR_SPEED1000,
++ .bmsr = BMSR_ESTATEN,
++ .lpagb = LPA_1000FULL | LPA_1000HALF,
++ },
++};
++
++static const struct swmii_regs duplex[] = {
++ [SWMII_DUPLEX_HALF] = {
++ .bmcr = ~BMCR_FULLDPLX,
++ .bmsr = BMSR_ESTATEN | BMSR_100HALF,
++ .lpa = LPA_10HALF | LPA_100HALF,
++ .lpagb = LPA_1000HALF,
++ },
++ [SWMII_DUPLEX_FULL] = {
++ .bmcr = ~0,
++ .bmsr = BMSR_ESTATEN | BMSR_100FULL,
++ .lpa = LPA_10FULL | LPA_100FULL,
++ .lpagb = LPA_1000FULL,
++ },
++};
++
++static int swphy_decode_speed(int speed)
++{
++ switch (speed) {
++ case 1000:
++ return SWMII_SPEED_1000;
++ case 100:
++ return SWMII_SPEED_100;
++ case 10:
++ return SWMII_SPEED_10;
++ default:
++ return -EINVAL;
++ }
++}
++
+ /**
+ * swphy_update_regs - update MII register array with fixed phy state
+ * @regs: array of 32 registers to update
+@@ -30,81 +96,28 @@
+ */
+ int swphy_update_regs(u16 *regs, const struct fixed_phy_status *state)
+ {
++ int speed_index, duplex_index;
+ u16 bmsr = BMSR_ANEGCAPABLE;
+ u16 bmcr = 0;
+ u16 lpagb = 0;
+ u16 lpa = 0;
+
+- if (state->duplex) {
+- switch (state->speed) {
+- case 1000:
+- bmsr |= BMSR_ESTATEN;
+- break;
+- case 100:
+- bmsr |= BMSR_100FULL;
+- break;
+- case 10:
+- bmsr |= BMSR_10FULL;
+- break;
+- default:
+- break;
+- }
+- } else {
+- switch (state->speed) {
+- case 1000:
+- bmsr |= BMSR_ESTATEN;
+- break;
+- case 100:
+- bmsr |= BMSR_100HALF;
+- break;
+- case 10:
+- bmsr |= BMSR_10HALF;
+- break;
+- default:
+- break;
+- }
++ speed_index = swphy_decode_speed(state->speed);
++ if (speed_index < 0) {
++ pr_warn("swphy: unknown speed\n");
++ return -EINVAL;
+ }
+
++ duplex_index = state->duplex ? SWMII_DUPLEX_FULL : SWMII_DUPLEX_HALF;
++
++ bmsr |= speed[speed_index].bmsr & duplex[duplex_index].bmsr;
++
+ if (state->link) {
+ bmsr |= BMSR_LSTATUS | BMSR_ANEGCOMPLETE;
+
+- if (state->duplex) {
+- bmcr |= BMCR_FULLDPLX;
+-
+- switch (state->speed) {
+- case 1000:
+- bmcr |= BMCR_SPEED1000;
+- lpagb |= LPA_1000FULL;
+- break;
+- case 100:
+- bmcr |= BMCR_SPEED100;
+- lpa |= LPA_100FULL;
+- break;
+- case 10:
+- lpa |= LPA_10FULL;
+- break;
+- default:
+- pr_warn("swphy: unknown speed\n");
+- return -EINVAL;
+- }
+- } else {
+- switch (state->speed) {
+- case 1000:
+- bmcr |= BMCR_SPEED1000;
+- lpagb |= LPA_1000HALF;
+- break;
+- case 100:
+- bmcr |= BMCR_SPEED100;
+- lpa |= LPA_100HALF;
+- break;
+- case 10:
+- lpa |= LPA_10HALF;
+- break;
+- default:
+- pr_warn("swphy: unknown speed\n");
+- return -EINVAL;
+- }
+- }
++ bmcr |= speed[speed_index].bmcr & duplex[duplex_index].bmcr;
++ lpa |= speed[speed_index].lpa & duplex[duplex_index].lpa;
++ lpagb |= speed[speed_index].lpagb & duplex[duplex_index].lpagb;
+
+ if (state->pause)
+ lpa |= LPA_PAUSE_CAP;
--- /dev/null
+From e07630ad84c7dc145863f079f108154fb7c975e7 Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Sun, 20 Sep 2015 11:12:15 +0100
+Subject: [PATCH 711/744] phy: separate swphy state validation from register
+ generation
+
+Separate out the generation of MII registers from the state validation.
+This allows us to simplify the error handing in fixed_phy() by allowing
+earlier error detection.
+
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ drivers/net/phy/fixed_phy.c | 15 +++++++--------
+ drivers/net/phy/swphy.c | 33 ++++++++++++++++++++++++++-------
+ drivers/net/phy/swphy.h | 3 ++-
+ 3 files changed, 35 insertions(+), 16 deletions(-)
+
+--- a/drivers/net/phy/fixed_phy.c
++++ b/drivers/net/phy/fixed_phy.c
+@@ -49,12 +49,12 @@ static struct fixed_mdio_bus platform_fm
+ .phys = LIST_HEAD_INIT(platform_fmb.phys),
+ };
+
+-static int fixed_phy_update_regs(struct fixed_phy *fp)
++static void fixed_phy_update_regs(struct fixed_phy *fp)
+ {
+ if (gpio_is_valid(fp->link_gpio))
+ fp->status.link = !!gpio_get_value_cansleep(fp->link_gpio);
+
+- return swphy_update_regs(fp->regs, &fp->status);
++ swphy_update_regs(fp->regs, &fp->status);
+ }
+
+ static int fixed_mdio_read(struct mii_bus *bus, int phy_addr, int reg_num)
+@@ -161,6 +161,10 @@ int fixed_phy_add(unsigned int irq, int
+ struct fixed_mdio_bus *fmb = &platform_fmb;
+ struct fixed_phy *fp;
+
++ ret = swphy_validate_state(status);
++ if (ret < 0)
++ return ret;
++
+ fp = kzalloc(sizeof(*fp), GFP_KERNEL);
+ if (!fp)
+ return -ENOMEM;
+@@ -180,17 +184,12 @@ int fixed_phy_add(unsigned int irq, int
+ goto err_regs;
+ }
+
+- ret = fixed_phy_update_regs(fp);
+- if (ret)
+- goto err_gpio;
++ fixed_phy_update_regs(fp);
+
+ list_add_tail(&fp->node, &fmb->phys);
+
+ return 0;
+
+-err_gpio:
+- if (gpio_is_valid(fp->link_gpio))
+- gpio_free(fp->link_gpio);
+ err_regs:
+ kfree(fp);
+ return ret;
+--- a/drivers/net/phy/swphy.c
++++ b/drivers/net/phy/swphy.c
+@@ -87,6 +87,29 @@ static int swphy_decode_speed(int speed)
+ }
+
+ /**
++ * swphy_validate_state - validate the software phy status
++ * @state: software phy status
++ *
++ * This checks that we can represent the state stored in @state can be
++ * represented in the emulated MII registers. Returns 0 if it can,
++ * otherwise returns -EINVAL.
++ */
++int swphy_validate_state(const struct fixed_phy_status *state)
++{
++ int err;
++
++ if (state->link) {
++ err = swphy_decode_speed(state->speed);
++ if (err < 0) {
++ pr_warn("swphy: unknown speed\n");
++ return -EINVAL;
++ }
++ }
++ return 0;
++}
++EXPORT_SYMBOL_GPL(swphy_validate_state);
++
++/**
+ * swphy_update_regs - update MII register array with fixed phy state
+ * @regs: array of 32 registers to update
+ * @state: fixed phy status
+@@ -94,7 +117,7 @@ static int swphy_decode_speed(int speed)
+ * Update the array of MII registers with the fixed phy link, speed,
+ * duplex and pause mode settings.
+ */
+-int swphy_update_regs(u16 *regs, const struct fixed_phy_status *state)
++void swphy_update_regs(u16 *regs, const struct fixed_phy_status *state)
+ {
+ int speed_index, duplex_index;
+ u16 bmsr = BMSR_ANEGCAPABLE;
+@@ -103,10 +126,8 @@ int swphy_update_regs(u16 *regs, const s
+ u16 lpa = 0;
+
+ speed_index = swphy_decode_speed(state->speed);
+- if (speed_index < 0) {
+- pr_warn("swphy: unknown speed\n");
+- return -EINVAL;
+- }
++ if (WARN_ON(speed_index < 0))
++ return;
+
+ duplex_index = state->duplex ? SWMII_DUPLEX_FULL : SWMII_DUPLEX_HALF;
+
+@@ -133,7 +154,5 @@ int swphy_update_regs(u16 *regs, const s
+ regs[MII_BMCR] = bmcr;
+ regs[MII_LPA] = lpa;
+ regs[MII_STAT1000] = lpagb;
+-
+- return 0;
+ }
+ EXPORT_SYMBOL_GPL(swphy_update_regs);
+--- a/drivers/net/phy/swphy.h
++++ b/drivers/net/phy/swphy.h
+@@ -3,6 +3,7 @@
+
+ struct fixed_phy_status;
+
+-int swphy_update_regs(u16 *regs, const struct fixed_phy_status *state);
++int swphy_validate_state(const struct fixed_phy_status *state);
++void swphy_update_regs(u16 *regs, const struct fixed_phy_status *state);
+
+ #endif
--- /dev/null
+From e0f33a88243329da1aa5a90fe10ab25c9fb0a091 Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Sun, 20 Sep 2015 11:28:39 +0100
+Subject: [PATCH 712/744] phy: generate swphy registers on the fly
+
+Generate software phy registers as and when requested, rather than
+duplicating the state in fixed_phy. This allows us to eliminate
+the duplicate storage of of the same data, which is only different
+in format.
+
+As fixed_phy_update_regs() no longer updates register state, rename
+it to fixed_phy_update().
+
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ drivers/net/phy/fixed_phy.c | 31 +++++-------------------------
+ drivers/net/phy/swphy.c | 47 ++++++++++++++++++++++++++++++++-------------
+ drivers/net/phy/swphy.h | 2 +-
+ 3 files changed, 40 insertions(+), 40 deletions(-)
+
+--- a/drivers/net/phy/fixed_phy.c
++++ b/drivers/net/phy/fixed_phy.c
+@@ -26,8 +26,6 @@
+
+ #include "swphy.h"
+
+-#define MII_REGS_NUM 29
+-
+ struct fixed_mdio_bus {
+ int irqs[PHY_MAX_ADDR];
+ struct mii_bus *mii_bus;
+@@ -36,7 +34,6 @@ struct fixed_mdio_bus {
+
+ struct fixed_phy {
+ int addr;
+- u16 regs[MII_REGS_NUM];
+ struct phy_device *phydev;
+ struct fixed_phy_status status;
+ int (*link_update)(struct net_device *, struct fixed_phy_status *);
+@@ -49,12 +46,10 @@ static struct fixed_mdio_bus platform_fm
+ .phys = LIST_HEAD_INIT(platform_fmb.phys),
+ };
+
+-static void fixed_phy_update_regs(struct fixed_phy *fp)
++static void fixed_phy_update(struct fixed_phy *fp)
+ {
+ if (gpio_is_valid(fp->link_gpio))
+ fp->status.link = !!gpio_get_value_cansleep(fp->link_gpio);
+-
+- swphy_update_regs(fp->regs, &fp->status);
+ }
+
+ static int fixed_mdio_read(struct mii_bus *bus, int phy_addr, int reg_num)
+@@ -62,29 +57,15 @@ static int fixed_mdio_read(struct mii_bu
+ struct fixed_mdio_bus *fmb = bus->priv;
+ struct fixed_phy *fp;
+
+- if (reg_num >= MII_REGS_NUM)
+- return -1;
+-
+- /* We do not support emulating Clause 45 over Clause 22 register reads
+- * return an error instead of bogus data.
+- */
+- switch (reg_num) {
+- case MII_MMD_CTRL:
+- case MII_MMD_DATA:
+- return -1;
+- default:
+- break;
+- }
+-
+ list_for_each_entry(fp, &fmb->phys, node) {
+ if (fp->addr == phy_addr) {
+ /* Issue callback if user registered it. */
+ if (fp->link_update) {
+ fp->link_update(fp->phydev->attached_dev,
+ &fp->status);
+- fixed_phy_update_regs(fp);
++ fixed_phy_update(fp);
+ }
+- return fp->regs[reg_num];
++ return swphy_read_reg(reg_num, &fp->status);
+ }
+ }
+
+@@ -144,7 +125,7 @@ int fixed_phy_update_state(struct phy_de
+ _UPD(pause);
+ _UPD(asym_pause);
+ #undef _UPD
+- fixed_phy_update_regs(fp);
++ fixed_phy_update(fp);
+ return 0;
+ }
+ }
+@@ -169,8 +150,6 @@ int fixed_phy_add(unsigned int irq, int
+ if (!fp)
+ return -ENOMEM;
+
+- memset(fp->regs, 0xFF, sizeof(fp->regs[0]) * MII_REGS_NUM);
+-
+ fmb->irqs[phy_addr] = irq;
+
+ fp->addr = phy_addr;
+@@ -184,7 +163,7 @@ int fixed_phy_add(unsigned int irq, int
+ goto err_regs;
+ }
+
+- fixed_phy_update_regs(fp);
++ fixed_phy_update(fp);
+
+ list_add_tail(&fp->node, &fmb->phys);
+
+--- a/drivers/net/phy/swphy.c
++++ b/drivers/net/phy/swphy.c
+@@ -20,6 +20,8 @@
+
+ #include "swphy.h"
+
++#define MII_REGS_NUM 29
++
+ struct swmii_regs {
+ u16 bmcr;
+ u16 bmsr;
+@@ -110,14 +112,13 @@ int swphy_validate_state(const struct fi
+ EXPORT_SYMBOL_GPL(swphy_validate_state);
+
+ /**
+- * swphy_update_regs - update MII register array with fixed phy state
+- * @regs: array of 32 registers to update
++ * swphy_read_reg - return a MII register from the fixed phy state
++ * @reg: MII register
+ * @state: fixed phy status
+ *
+- * Update the array of MII registers with the fixed phy link, speed,
+- * duplex and pause mode settings.
++ * Return the MII @reg register generated from the fixed phy state @state.
+ */
+-void swphy_update_regs(u16 *regs, const struct fixed_phy_status *state)
++int swphy_read_reg(int reg, const struct fixed_phy_status *state)
+ {
+ int speed_index, duplex_index;
+ u16 bmsr = BMSR_ANEGCAPABLE;
+@@ -125,9 +126,12 @@ void swphy_update_regs(u16 *regs, const
+ u16 lpagb = 0;
+ u16 lpa = 0;
+
++ if (reg > MII_REGS_NUM)
++ return -1;
++
+ speed_index = swphy_decode_speed(state->speed);
+ if (WARN_ON(speed_index < 0))
+- return;
++ return 0;
+
+ duplex_index = state->duplex ? SWMII_DUPLEX_FULL : SWMII_DUPLEX_HALF;
+
+@@ -147,12 +151,29 @@ void swphy_update_regs(u16 *regs, const
+ lpa |= LPA_PAUSE_ASYM;
+ }
+
+- regs[MII_PHYSID1] = 0;
+- regs[MII_PHYSID2] = 0;
++ switch (reg) {
++ case MII_BMCR:
++ return bmcr;
++ case MII_BMSR:
++ return bmsr;
++ case MII_PHYSID1:
++ case MII_PHYSID2:
++ return 0;
++ case MII_LPA:
++ return lpa;
++ case MII_STAT1000:
++ return lpagb;
++
++ /*
++ * We do not support emulating Clause 45 over Clause 22 register
++ * reads. Return an error instead of bogus data.
++ */
++ case MII_MMD_CTRL:
++ case MII_MMD_DATA:
++ return -1;
+
+- regs[MII_BMSR] = bmsr;
+- regs[MII_BMCR] = bmcr;
+- regs[MII_LPA] = lpa;
+- regs[MII_STAT1000] = lpagb;
++ default:
++ return 0xffff;
++ }
+ }
+-EXPORT_SYMBOL_GPL(swphy_update_regs);
++EXPORT_SYMBOL_GPL(swphy_read_reg);
+--- a/drivers/net/phy/swphy.h
++++ b/drivers/net/phy/swphy.h
+@@ -4,6 +4,6 @@
+ struct fixed_phy_status;
+
+ int swphy_validate_state(const struct fixed_phy_status *state);
+-void swphy_update_regs(u16 *regs, const struct fixed_phy_status *state);
++int swphy_read_reg(int reg, const struct fixed_phy_status *state);
+
+ #endif
--- /dev/null
+From c36739c3cfd277a4cc9820a29dd0f4b7fbac795b Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Sun, 20 Sep 2015 18:31:36 +0100
+Subject: [PATCH 713/744] phy: improve safety of fixed-phy MII register reading
+
+There is no prevention of a concurrent call to both fixed_mdio_read()
+and fixed_phy_update_state(), which can result in the state being
+modified while it's being inspected. Fix this by using a seqcount
+to detect modifications, and memcpy()ing the state.
+
+We remain slightly naughty here, calling link_update() and updating
+the link status within the read-side loop - which would need rework
+of the design to change.
+
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ drivers/net/phy/fixed_phy.c | 28 +++++++++++++++++++++-------
+ 1 file changed, 21 insertions(+), 7 deletions(-)
+
+--- a/drivers/net/phy/fixed_phy.c
++++ b/drivers/net/phy/fixed_phy.c
+@@ -23,6 +23,7 @@
+ #include <linux/slab.h>
+ #include <linux/of.h>
+ #include <linux/gpio.h>
++#include <linux/seqlock.h>
+
+ #include "swphy.h"
+
+@@ -35,6 +36,7 @@ struct fixed_mdio_bus {
+ struct fixed_phy {
+ int addr;
+ struct phy_device *phydev;
++ seqcount_t seqcount;
+ struct fixed_phy_status status;
+ int (*link_update)(struct net_device *, struct fixed_phy_status *);
+ struct list_head node;
+@@ -59,13 +61,21 @@ static int fixed_mdio_read(struct mii_bu
+
+ list_for_each_entry(fp, &fmb->phys, node) {
+ if (fp->addr == phy_addr) {
+- /* Issue callback if user registered it. */
+- if (fp->link_update) {
+- fp->link_update(fp->phydev->attached_dev,
+- &fp->status);
+- fixed_phy_update(fp);
+- }
+- return swphy_read_reg(reg_num, &fp->status);
++ struct fixed_phy_status state;
++ int s;
++
++ do {
++ s = read_seqcount_begin(&fp->seqcount);
++ /* Issue callback if user registered it. */
++ if (fp->link_update) {
++ fp->link_update(fp->phydev->attached_dev,
++ &fp->status);
++ fixed_phy_update(fp);
++ }
++ state = fp->status;
++ } while (read_seqcount_retry(&fp->seqcount, s));
++
++ return swphy_read_reg(reg_num, &state);
+ }
+ }
+
+@@ -117,6 +127,7 @@ int fixed_phy_update_state(struct phy_de
+
+ list_for_each_entry(fp, &fmb->phys, node) {
+ if (fp->addr == phydev->addr) {
++ write_seqcount_begin(&fp->seqcount);
+ #define _UPD(x) if (changed->x) \
+ fp->status.x = status->x
+ _UPD(link);
+@@ -126,6 +137,7 @@ int fixed_phy_update_state(struct phy_de
+ _UPD(asym_pause);
+ #undef _UPD
+ fixed_phy_update(fp);
++ write_seqcount_end(&fp->seqcount);
+ return 0;
+ }
+ }
+@@ -150,6 +162,8 @@ int fixed_phy_add(unsigned int irq, int
+ if (!fp)
+ return -ENOMEM;
+
++ seqcount_init(&fp->seqcount);
++
+ fmb->irqs[phy_addr] = irq;
+
+ fp->addr = phy_addr;
--- /dev/null
+From d8b4e728f598d3c8a9b219d4679d5de350caa082 Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Fri, 18 Sep 2015 14:42:16 +0100
+Subject: [PATCH 714/744] phy: provide a hook for link up/link down events
+
+Sometimes, we need to do additional work between the PHY coming up and
+marking the carrier present - for example, we may need to wait for the
+PHY to MAC link to finish negotiation. This changes phylib to provide
+a notification function pointer which avoids the built-in
+netif_carrier_on() and netif_carrier_off() functions.
+
+Standard ->adjust_link functionality is provided by hooking a helper
+into the new ->phy_link_change method.
+
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ drivers/net/phy/phy.c | 42 ++++++++++++++++++++++--------------------
+ drivers/net/phy/phy_device.c | 14 ++++++++++++++
+ include/linux/phy.h | 1 +
+ 3 files changed, 37 insertions(+), 20 deletions(-)
+
+--- a/drivers/net/phy/phy.c
++++ b/drivers/net/phy/phy.c
+@@ -849,6 +849,16 @@ void phy_start(struct phy_device *phydev
+ }
+ EXPORT_SYMBOL(phy_start);
+
++static void phy_link_up(struct phy_device *phydev)
++{
++ phydev->phy_link_change(phydev, true, true);
++}
++
++static void phy_link_down(struct phy_device *phydev, bool do_carrier)
++{
++ phydev->phy_link_change(phydev, false, do_carrier);
++}
++
+ /**
+ * phy_state_machine - Handle the state machine
+ * @work: work_struct that describes the work to be done
+@@ -890,8 +900,7 @@ void phy_state_machine(struct work_struc
+ /* If the link is down, give up on negotiation for now */
+ if (!phydev->link) {
+ phydev->state = PHY_NOLINK;
+- netif_carrier_off(phydev->attached_dev);
+- phydev->adjust_link(phydev->attached_dev);
++ phy_link_down(phydev, true);
+ break;
+ }
+
+@@ -903,9 +912,7 @@ void phy_state_machine(struct work_struc
+ /* If AN is done, we're running */
+ if (err > 0) {
+ phydev->state = PHY_RUNNING;
+- netif_carrier_on(phydev->attached_dev);
+- phydev->adjust_link(phydev->attached_dev);
+-
++ phy_link_up(phydev);
+ } else if (0 == phydev->link_timeout--)
+ needs_aneg = true;
+ break;
+@@ -930,8 +937,7 @@ void phy_state_machine(struct work_struc
+ }
+ }
+ phydev->state = PHY_RUNNING;
+- netif_carrier_on(phydev->attached_dev);
+- phydev->adjust_link(phydev->attached_dev);
++ phy_link_up(phydev);
+ }
+ break;
+ case PHY_FORCING:
+@@ -941,13 +947,12 @@ void phy_state_machine(struct work_struc
+
+ if (phydev->link) {
+ phydev->state = PHY_RUNNING;
+- netif_carrier_on(phydev->attached_dev);
++ phy_link_up(phydev);
+ } else {
+ if (0 == phydev->link_timeout--)
+ needs_aneg = true;
++ phy_link_down(phydev, false);
+ }
+-
+- phydev->adjust_link(phydev->attached_dev);
+ break;
+ case PHY_RUNNING:
+ /* Only register a CHANGE if we are polling or ignoring
+@@ -970,14 +975,12 @@ void phy_state_machine(struct work_struc
+
+ if (phydev->link) {
+ phydev->state = PHY_RUNNING;
+- netif_carrier_on(phydev->attached_dev);
++ phy_link_up(phydev);
+ } else {
+ phydev->state = PHY_NOLINK;
+- netif_carrier_off(phydev->attached_dev);
++ phy_link_down(phydev, true);
+ }
+
+- phydev->adjust_link(phydev->attached_dev);
+-
+ if (phy_interrupt_is_valid(phydev))
+ err = phy_config_interrupt(phydev,
+ PHY_INTERRUPT_ENABLED);
+@@ -985,8 +988,7 @@ void phy_state_machine(struct work_struc
+ case PHY_HALTED:
+ if (phydev->link) {
+ phydev->link = 0;
+- netif_carrier_off(phydev->attached_dev);
+- phydev->adjust_link(phydev->attached_dev);
++ phy_link_down(phydev, true);
+ do_suspend = true;
+ }
+ break;
+@@ -1006,11 +1008,11 @@ void phy_state_machine(struct work_struc
+
+ if (phydev->link) {
+ phydev->state = PHY_RUNNING;
+- netif_carrier_on(phydev->attached_dev);
++ phy_link_up(phydev);
+ } else {
+ phydev->state = PHY_NOLINK;
++ phy_link_down(phydev, false);
+ }
+- phydev->adjust_link(phydev->attached_dev);
+ } else {
+ phydev->state = PHY_AN;
+ phydev->link_timeout = PHY_AN_TIMEOUT;
+@@ -1022,11 +1024,11 @@ void phy_state_machine(struct work_struc
+
+ if (phydev->link) {
+ phydev->state = PHY_RUNNING;
+- netif_carrier_on(phydev->attached_dev);
++ phy_link_up(phydev);
+ } else {
+ phydev->state = PHY_NOLINK;
++ phy_link_down(phydev, false);
+ }
+- phydev->adjust_link(phydev->attached_dev);
+ }
+ break;
+ }
+--- a/drivers/net/phy/phy_device.c
++++ b/drivers/net/phy/phy_device.c
+@@ -441,6 +441,19 @@ struct phy_device *phy_find_first(struct
+ }
+ EXPORT_SYMBOL(phy_find_first);
+
++static void phy_link_change(struct phy_device *phydev, bool up, bool do_carrier)
++{
++ struct net_device *netdev = phydev->attached_dev;
++
++ if (do_carrier) {
++ if (up)
++ netif_carrier_on(netdev);
++ else
++ netif_carrier_off(netdev);
++ }
++ phydev->adjust_link(netdev);
++}
++
+ /**
+ * phy_prepare_link - prepares the PHY layer to monitor link status
+ * @phydev: target phy_device struct
+@@ -659,6 +672,7 @@ int phy_attach_direct(struct net_device
+ goto error;
+ }
+
++ phydev->phy_link_change = phy_link_change;
+ phydev->attached_dev = dev;
+ dev->phydev = phydev;
+
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -433,6 +433,7 @@ struct phy_device {
+
+ u8 mdix;
+
++ void (*phy_link_change)(struct phy_device *, bool up, bool do_carrier);
+ void (*adjust_link)(struct net_device *dev);
+ };
+ #define to_phy_device(d) container_of(d, struct phy_device, dev)
--- /dev/null
+From 5eed0bf3bc3e69b20a13d8ffcdf97cb720391637 Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Thu, 1 Oct 2015 00:34:08 +0100
+Subject: [PATCH 735/744] phy: marvell: 88E1512: add flow control support
+
+The Marvell PHYs support pause frame advertisments, so we should not be
+masking their support off. Add the necessary flag to the Marvell PHY
+to allow any MAC level pause frame support to be advertised.
+
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ drivers/net/phy/marvell.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/net/phy/marvell.c
++++ b/drivers/net/phy/marvell.c
+@@ -1142,7 +1142,7 @@ static struct phy_driver marvell_drivers
+ .phy_id = MARVELL_PHY_ID_88E1510,
+ .phy_id_mask = MARVELL_PHY_ID_MASK,
+ .name = "Marvell 88E1510",
+- .features = PHY_GBIT_FEATURES,
++ .features = PHY_GBIT_FEATURES | SUPPORTED_Pause,
+ .flags = PHY_HAS_INTERRUPT,
+ .config_aneg = &m88e1510_config_aneg,
+ .read_status = &marvell_read_status,
--- /dev/null
+From f2a9687b39cda3fb67ecd5eaa88e3545e78c982c Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Fri, 16 Oct 2015 12:18:41 +0100
+Subject: [PATCH 715/744] phy: export phy_start_machine() for phylink
+
+phylink will need phy_start_machine exported, so lets export it as a
+GPL symbol. Documentation/networking/phy.txt indicates that this
+should be a PHY API function.
+
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ drivers/net/phy/phy.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/drivers/net/phy/phy.c
++++ b/drivers/net/phy/phy.c
+@@ -568,6 +568,7 @@ void phy_start_machine(struct phy_device
+ {
+ queue_delayed_work(system_power_efficient_wq, &phydev->state_queue, HZ);
+ }
++EXPORT_SYMBOL_GPL(phy_start_machine);
+
+ /**
+ * phy_stop_machine - stop the PHY state machine tracking
--- /dev/null
+From 5c77cc2ffd5deb4762d9551409472f2441297fe7 Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Sun, 18 Oct 2015 19:51:10 +0100
+Subject: [PATCH 716/744] phy: export phy_speed_to_str() for phylink
+
+phylink would like to reuse phy_speed_to_str() to convert the speed
+to a string. Add a prototype and export this helper function.
+
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ drivers/net/phy/phy.c | 3 ++-
+ include/linux/phy.h | 1 +
+ 2 files changed, 3 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/phy/phy.c
++++ b/drivers/net/phy/phy.c
+@@ -38,7 +38,7 @@
+
+ #include <asm/irq.h>
+
+-static const char *phy_speed_to_str(int speed)
++const char *phy_speed_to_str(int speed)
+ {
+ switch (speed) {
+ case SPEED_10:
+@@ -57,6 +57,7 @@ static const char *phy_speed_to_str(int
+ return "Unsupported (update phy.c)";
+ }
+ }
++EXPORT_SYMBOL_GPL(phy_speed_to_str);
+
+ #define PHY_STATE_STR(_state) \
+ case PHY_##_state: \
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -812,6 +812,7 @@ int phy_ethtool_gset(struct phy_device *
+ int phy_ethtool_ioctl(struct phy_device *phydev, void *useraddr);
+ int phy_mii_ioctl(struct phy_device *phydev, struct ifreq *ifr, int cmd);
+ int phy_start_interrupts(struct phy_device *phydev);
++const char *phy_speed_to_str(int speed);
+ void phy_print_status(struct phy_device *phydev);
+ void phy_device_free(struct phy_device *phydev);
+ int phy_set_max_speed(struct phy_device *phydev, u32 max_speed);
--- /dev/null
+From 7f36ac946bfbd4090b8b94be3661b41ac73e21f4 Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Fri, 25 Sep 2015 17:43:52 +0100
+Subject: [PATCH 717/744] phy: add I2C mdio bus
+
+Add an I2C MDIO bus bridge library, to allow phylib to access PHYs which
+are connected to an I2C bus instead of the more conventional MDIO bus.
+Such PHYs can be found in SFP adapters and SFF modules.
+
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ drivers/net/phy/Kconfig | 10 ++++++
+ drivers/net/phy/Makefile | 1 +
+ drivers/net/phy/mdio-i2c.c | 90 ++++++++++++++++++++++++++++++++++++++++++++++
+ drivers/net/phy/mdio-i2c.h | 19 ++++++++++
+ 4 files changed, 120 insertions(+)
+ create mode 100644 drivers/net/phy/mdio-i2c.c
+ create mode 100644 drivers/net/phy/mdio-i2c.h
+
+--- a/drivers/net/phy/Kconfig
++++ b/drivers/net/phy/Kconfig
+@@ -233,6 +233,16 @@ config MDIO_GPIO
+ To compile this driver as a module, choose M here: the module
+ will be called mdio-gpio.
+
++config MDIO_I2C
++ tristate
++ depends on I2C
++ help
++ Support I2C based PHYs. This provides a MDIO bus bridged
++ to I2C to allow PHYs connected in I2C mode to be accessed
++ using the existing infrastructure.
++
++ This is library mode.
++
+ config MDIO_OCTEON
+ tristate "Support for MDIO buses on Octeon and ThunderX SOCs"
+ depends on 64BIT
+--- a/drivers/net/phy/Makefile
++++ b/drivers/net/phy/Makefile
+@@ -41,6 +41,7 @@ obj-$(CONFIG_SWCONFIG_B53) += b53/
+ obj-$(CONFIG_FIXED_PHY) += fixed_phy.o
+ obj-$(CONFIG_MDIO_BITBANG) += mdio-bitbang.o
+ obj-$(CONFIG_MDIO_GPIO) += mdio-gpio.o
++obj-$(CONFIG_MDIO_I2C) += mdio-i2c.o
+ obj-$(CONFIG_NATIONAL_PHY) += national.o
+ obj-$(CONFIG_DP83640_PHY) += dp83640.o
+ obj-$(CONFIG_DP83848_PHY) += dp83848.o
+--- /dev/null
++++ b/drivers/net/phy/mdio-i2c.c
+@@ -0,0 +1,90 @@
++/*
++ * MDIO I2C bridge
++ *
++ * Copyright (C) 2015 Russell King
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2 as
++ * published by the Free Software Foundation.
++ */
++#include <linux/i2c.h>
++#include <linux/phy.h>
++
++#include "mdio-i2c.h"
++
++static int i2c_mii_read(struct mii_bus *bus, int phy_id, int reg)
++{
++ struct i2c_adapter *i2c = bus->priv;
++ struct i2c_msg msgs[2];
++ u8 data[2], dev_addr = reg;
++ int bus_addr, ret;
++
++ bus_addr = 0x40 + phy_id;
++ if (bus_addr == 0x50 || bus_addr == 0x51)
++ return 0xffff;
++
++ msgs[0].addr = bus_addr;
++ msgs[0].flags = 0;
++ msgs[0].len = 1;
++ msgs[0].buf = &dev_addr;
++ msgs[1].addr = bus_addr;
++ msgs[1].flags = I2C_M_RD;
++ msgs[1].len = sizeof(data);
++ msgs[1].buf = data;
++
++ ret = i2c_transfer(i2c, msgs, ARRAY_SIZE(msgs));
++ if (ret != ARRAY_SIZE(msgs))
++ return 0xffff;
++
++ return data[0] << 8 | data[1];
++}
++
++static int i2c_mii_write(struct mii_bus *bus, int phy_id, int reg, u16 val)
++{
++ struct i2c_adapter *i2c = bus->priv;
++ struct i2c_msg msg;
++ int bus_addr, ret;
++ u8 data[3];
++
++ bus_addr = 0x40 + phy_id;
++ if (bus_addr == 0x50 || bus_addr == 0x51)
++ return 0;
++
++ data[0] = reg;
++ data[1] = val >> 8;
++ data[2] = val;
++
++ msg.addr = bus_addr;
++ msg.flags = 0;
++ msg.len = 3;
++ msg.buf = data;
++
++ ret = i2c_transfer(i2c, &msg, 1);
++
++ return ret < 0 ? ret : 0;
++}
++
++struct mii_bus *mdio_i2c_alloc(struct device *parent, struct i2c_adapter *i2c)
++{
++ struct mii_bus *mii;
++
++ if (!i2c_check_functionality(i2c, I2C_FUNC_I2C))
++ return ERR_PTR(-EINVAL);
++
++ mii = mdiobus_alloc();
++ if (!mii)
++ return ERR_PTR(-ENOMEM);
++
++ snprintf(mii->id, MII_BUS_ID_SIZE, "i2c:%s", dev_name(parent));
++ mii->parent = parent;
++ mii->read = i2c_mii_read;
++ mii->write = i2c_mii_write;
++ mii->priv = i2c;
++
++ return mii;
++}
++EXPORT_SYMBOL_GPL(mdio_i2c_alloc);
++
++MODULE_AUTHOR("Russell King");
++MODULE_DESCRIPTION("MDIO I2C bridge library");
++MODULE_LICENSE("GPL v2");
+--- /dev/null
++++ b/drivers/net/phy/mdio-i2c.h
+@@ -0,0 +1,19 @@
++/*
++ * MDIO I2C bridge
++ *
++ * Copyright (C) 2015 Russell King
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2 as
++ * published by the Free Software Foundation.
++ */
++#ifndef MDIO_I2C_H
++#define MDIO_I2C_H
++
++struct device;
++struct i2c_adapter;
++struct mii_bus;
++
++struct mii_bus *mdio_i2c_alloc(struct device *parent, struct i2c_adapter *i2c);
++
++#endif
--- /dev/null
+From c6de6de7d3df13822872ac756eebe868d236297a Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Tue, 22 Sep 2015 20:52:18 +0100
+Subject: [PATCH 718/744] phylink: add phylink infrastructure
+
+The link between the ethernet MAC and its PHY has become more complex
+as the interface evolves. This is especially true with serdes links,
+where the part of the PHY is effectively integrated into the MAC.
+
+Serdes links can be connected to a variety of devices, including SFF
+modules soldered down onto the board with the MAC, a SFP cage with
+a hotpluggable SFP module which may contain a PHY or directly modulate
+the serdes signals onto optical media with or without a PHY, or even
+a classical PHY connection.
+
+Moreover, the negotiation information on serdes links comes in two
+varieties - SGMII mode, where the PHY provides its speed/duplex/flow
+control information to the MAC, and 1000base-X mode where both ends
+exchange their abilities and each resolve the link capabilities.
+
+This means we need a more flexible means to support these arrangements,
+particularly with the hotpluggable nature of SFP, where the PHY can
+be attached or detached after the network device has been brought up.
+
+Ethtool information can come from multiple sources:
+- we may have a PHY operating in either SGMII or 1000base-X mode, in
+ which case we take ethtool/mii data directly from the PHY.
+- we may have a optical SFP module without a PHY, with the MAC
+ operating in 1000base-X mode - the ethtool/mii data needs to come
+ from the MAC.
+- we may have a copper SFP module with a PHY whic can't be accessed,
+ which means we need to take ethtool/mii data from the MAC.
+
+Phylink aims to solve this by providing an intermediary between the
+MAC and PHY, providing a safe way for PHYs to be hotplugged, and
+allowing a SFP driver to reconfigure the serdes connection.
+
+Phylink also takes over support of fixed link connections, where
+the speed/duplex/flow control are fixed, but link status may be
+controlled by a GPIO signal. By avoiding the fixed-phy implementation,
+phylink can provide a faster response to link events: fixed-phy has
+to wait for phylib to operate its state machine, which can take
+several seconds. In comparison, phylink takes milliseconds.
+
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ drivers/net/phy/Kconfig | 10 +
+ drivers/net/phy/Makefile | 1 +
+ drivers/net/phy/phy_device.c | 1 +
+ drivers/net/phy/phylink.c | 816 +++++++++++++++++++++++++++++++++++++++++++
+ include/linux/phy.h | 2 +
+ include/linux/phylink.h | 70 ++++
+ 6 files changed, 900 insertions(+)
+ create mode 100644 drivers/net/phy/phylink.c
+ create mode 100644 include/linux/phylink.h
+
+--- a/drivers/net/phy/Kconfig
++++ b/drivers/net/phy/Kconfig
+@@ -10,6 +10,16 @@ menuconfig PHYLIB
+ devices. This option provides infrastructure for
+ managing PHY devices.
+
++config PHYLINK
++ tristate
++ depends on NETDEVICES
++ select PHYLIB
++ select SWPHY
++ help
++ PHYlink models the link between the PHY and MAC, allowing fixed
++ configuration links, PHYs, and Serdes links with MAC level
++ autonegotiation modes.
++
+ if PHYLIB
+
+ config MDIO_BOARDINFO
+--- a/drivers/net/phy/Makefile
++++ b/drivers/net/phy/Makefile
+@@ -5,6 +5,7 @@ libphy-$(CONFIG_SWPHY) += swphy.o
+
+ obj-$(CONFIG_MDIO_BOARDINFO) += mdio-boardinfo.o
+
++obj-$(CONFIG_PHYLINK) += phylink.o
+ obj-$(CONFIG_PHYLIB) += libphy.o
+ obj-$(CONFIG_SWCONFIG) += swconfig.o
+ obj-$(CONFIG_AQUANTIA_PHY) += aquantia.o
+--- a/drivers/net/phy/phy_device.c
++++ b/drivers/net/phy/phy_device.c
+@@ -754,6 +754,7 @@ void phy_detach(struct phy_device *phyde
+ phydev->attached_dev->phydev = NULL;
+ phydev->attached_dev = NULL;
+ phy_suspend(phydev);
++ phydev->phylink = NULL;
+
+ /* If the device had no specific driver before (i.e. - it
+ * was using the generic driver), we unbind the device
+--- /dev/null
++++ b/drivers/net/phy/phylink.c
+@@ -0,0 +1,816 @@
++/*
++ * phylink models the MAC to optional PHY connection, supporting
++ * technologies such as SFP cages where the PHY is hot-pluggable.
++ *
++ * Copyright (C) 2015 Russell King
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2 as
++ * published by the Free Software Foundation.
++ */
++#include <linux/ethtool.h>
++#include <linux/export.h>
++#include <linux/gpio/consumer.h>
++#include <linux/netdevice.h>
++#include <linux/of.h>
++#include <linux/of_mdio.h>
++#include <linux/phy.h>
++#include <linux/phy_fixed.h>
++#include <linux/phylink.h>
++#include <linux/spinlock.h>
++#include <linux/workqueue.h>
++
++#include "swphy.h"
++
++#define SUPPORTED_INTERFACES \
++ (SUPPORTED_TP | SUPPORTED_MII | SUPPORTED_FIBRE | \
++ SUPPORTED_BNC | SUPPORTED_AUI | SUPPORTED_Backplane)
++#define ADVERTISED_INTERFACES \
++ (ADVERTISED_TP | ADVERTISED_MII | ADVERTISED_FIBRE | \
++ ADVERTISED_BNC | ADVERTISED_AUI | ADVERTISED_Backplane)
++
++enum {
++ PHYLINK_DISABLE_STOPPED,
++};
++
++struct phylink {
++ struct net_device *netdev;
++ const struct phylink_mac_ops *ops;
++ struct mutex config_mutex;
++
++ unsigned long phylink_disable_state; /* bitmask of disables */
++ struct phy_device *phydev;
++ phy_interface_t link_interface; /* PHY_INTERFACE_xxx */
++ u8 link_an_mode; /* MLO_AN_xxx */
++ u8 link_port; /* The current non-phy ethtool port */
++ u32 link_port_support; /* SUPPORTED_xxx ethtool for ports */
++
++ /* The link configuration settings */
++ struct phylink_link_state link_config;
++ struct gpio_desc *link_gpio;
++
++ struct mutex state_mutex; /* may be taken within config_mutex */
++ struct phylink_link_state phy_state;
++ struct work_struct resolve;
++
++ bool mac_link_up;
++};
++
++static const char *phylink_an_mode_str(unsigned int mode)
++{
++ static const char *modestr[] = {
++ [MLO_AN_PHY] = "phy",
++ [MLO_AN_FIXED] = "fixed",
++ [MLO_AN_SGMII] = "SGMII",
++ [MLO_AN_8023Z] = "802.3z",
++ };
++
++ return mode < ARRAY_SIZE(modestr) ? modestr[mode] : "unknown";
++}
++
++static int phylink_parse_fixedlink(struct phylink *pl, struct device_node *np)
++{
++ struct device_node *fixed_node;
++ int ret, len;
++
++ fixed_node = of_get_child_by_name(np, "fixed-link");
++ if (fixed_node) {
++ struct gpio_desc *desc;
++ u32 speed;
++
++ ret = of_property_read_u32(fixed_node, "speed", &speed);
++
++ pl->link_an_mode = MLO_AN_FIXED;
++ pl->link_config.link = 1;
++ pl->link_config.an_complete = 1;
++ pl->link_config.speed = speed;
++ pl->link_config.duplex = DUPLEX_HALF;
++ pl->link_config.pause = MLO_PAUSE_NONE;
++
++ if (of_property_read_bool(fixed_node, "full-duplex"))
++ pl->link_config.duplex = DUPLEX_FULL;
++ if (of_property_read_bool(fixed_node, "pause"))
++ pl->link_config.pause |= MLO_PAUSE_SYM;
++ if (of_property_read_bool(fixed_node, "asym-pause"))
++ pl->link_config.pause |= MLO_PAUSE_ASYM;
++
++ if (ret == 0) {
++ desc = fwnode_get_named_gpiod(&fixed_node->fwnode,
++ "link-gpios");
++
++ if (!IS_ERR(desc))
++ pl->link_gpio = desc;
++ else if (desc == ERR_PTR(-EPROBE_DEFER))
++ ret = -EPROBE_DEFER;
++ }
++ of_node_put(fixed_node);
++ } else {
++ const __be32 *fixed_prop;
++
++ fixed_prop = of_get_property(np, "fixed-link", &len);
++ if (fixed_prop && len == 5 * sizeof(*fixed_prop)) {
++ pl->link_config.duplex = be32_to_cpu(fixed_prop[1]) ?
++ DUPLEX_FULL : DUPLEX_HALF;
++ pl->link_config.speed = be32_to_cpu(fixed_prop[2]);
++ pl->link_config.pause = MLO_PAUSE_NONE;
++ if (be32_to_cpu(fixed_prop[3]))
++ pl->link_config.pause |= MLO_PAUSE_SYM;
++ if (be32_to_cpu(fixed_prop[4]))
++ pl->link_config.pause |= MLO_PAUSE_ASYM;
++
++ pl->link_an_mode = MLO_AN_FIXED;
++ }
++ ret = 0;
++ }
++
++ if (pl->link_an_mode == MLO_AN_FIXED) {
++ /* Generate the supported/advertising masks */
++ if (pl->link_config.pause & MLO_PAUSE_SYM) {
++ pl->link_config.supported |= SUPPORTED_Pause;
++ pl->link_config.advertising |= ADVERTISED_Pause;
++ }
++ if (pl->link_config.pause & MLO_PAUSE_ASYM) {
++ pl->link_config.supported |= SUPPORTED_Asym_Pause;
++ pl->link_config.advertising |= ADVERTISED_Asym_Pause;
++ }
++
++ if (pl->link_config.speed > SPEED_1000 &&
++ pl->link_config.duplex != DUPLEX_FULL)
++ netdev_warn(pl->netdev, "fixed link specifies half duplex for %dMbps link?\n",
++ pl->link_config.speed);
++
++#define S(spd) \
++ pl->link_config.supported |= pl->link_config.duplex ? \
++ SUPPORTED_##spd##_Full : SUPPORTED_##spd##_Half
++#define A(spd) \
++ pl->link_config.advertising |= pl->link_config.duplex ? \
++ ADVERTISED_##spd##_Full : ADVERTISED_##spd##_Half
++#define C(spd, tech) \
++ case spd: \
++ S(spd##tech); \
++ A(spd##tech); \
++ break
++ switch (pl->link_config.speed) {
++ C(10, baseT);
++ C(100, baseT);
++ C(1000, baseT);
++#undef S
++#undef A
++#define S(spd) pl->link_config.supported |= SUPPORTED_##spd##_Full
++#define A(spd) pl->link_config.advertising |= ADVERTISED_##spd##_Full
++ C(2500, baseX);
++ C(10000, baseT);
++ }
++#undef S
++#undef A
++#undef C
++ }
++ return ret;
++}
++
++static int phylink_parse_managed(struct phylink *pl, struct device_node *np)
++{
++ const char *managed;
++
++ if (of_property_read_string(np, "managed", &managed) == 0 &&
++ strcmp(managed, "in-band-status") == 0) {
++ if (pl->link_an_mode == MLO_AN_FIXED) {
++ netdev_err(pl->netdev,
++ "can't use both fixed-link and in-band-status\n");
++ return -EINVAL;
++ }
++ pl->link_an_mode = MLO_AN_SGMII;
++ pl->link_config.an_enabled = true;
++ }
++
++ return 0;
++}
++
++
++static int phylink_get_support(struct phylink *pl, unsigned int mode)
++{
++ struct phylink_link_state state = pl->link_config;
++ int ret;
++
++ ret = pl->ops->mac_get_support(pl->netdev, mode, &state);
++ if (ret == 0) {
++ pl->link_an_mode = mode;
++ pl->link_config = state;
++ }
++
++ return ret;
++}
++
++static void phylink_mac_config(struct phylink *pl,
++ const struct phylink_link_state *state)
++{
++ pl->ops->mac_config(pl->netdev, pl->link_an_mode, state);
++}
++
++static void phylink_mac_an_restart(struct phylink *pl)
++{
++ if (pl->link_config.an_enabled)
++ pl->ops->mac_an_restart(pl->netdev, pl->link_an_mode);
++}
++
++static int phylink_get_mac_state(struct phylink *pl, struct phylink_link_state *state)
++{
++ struct net_device *ndev = pl->netdev;
++
++ state->supported = pl->link_config.supported;
++ state->advertising = pl->link_config.advertising;
++ state->an_enabled = pl->link_config.an_enabled;
++ state->link = 1;
++ state->sync = 1;
++
++ return pl->ops->mac_link_state(ndev, state);
++}
++
++/* The fixed state is... fixed except for the link state,
++ * which may be determined by a GPIO.
++ */
++static void phylink_get_fixed_state(struct phylink *pl, struct phylink_link_state *state)
++{
++ *state = pl->link_config;
++ if (pl->link_gpio)
++ state->link = !!gpiod_get_value(pl->link_gpio);
++}
++
++extern const char *phy_speed_to_str(int speed);
++
++static void phylink_resolve(struct work_struct *w)
++{
++ struct phylink *pl = container_of(w, struct phylink, resolve);
++ struct phylink_link_state link_state;
++ struct net_device *ndev = pl->netdev;
++
++ mutex_lock(&pl->state_mutex);
++ if (pl->phylink_disable_state) {
++ link_state.link = false;
++ } else {
++ switch (pl->link_an_mode) {
++ case MLO_AN_PHY:
++ link_state = pl->phy_state;
++ break;
++
++ case MLO_AN_FIXED:
++ phylink_get_fixed_state(pl, &link_state);
++ break;
++
++ case MLO_AN_SGMII:
++ phylink_get_mac_state(pl, &link_state);
++ if (pl->phydev)
++ link_state.link = link_state.link &&
++ pl->phy_state.link;
++ break;
++
++ case MLO_AN_8023Z:
++ phylink_get_mac_state(pl, &link_state);
++ break;
++ }
++ }
++
++ if (link_state.link != netif_carrier_ok(ndev)) {
++ if (!link_state.link) {
++ netif_carrier_off(ndev);
++ pl->ops->mac_link_down(ndev, pl->link_an_mode);
++ netdev_info(ndev, "Link is Down\n");
++ } else {
++ /* If we have a PHY, we need the MAC updated with
++ * the current link parameters (eg, in SGMII mode,
++ * with flow control status.)
++ */
++ if (pl->phydev)
++ phylink_mac_config(pl, &link_state);
++
++ pl->ops->mac_link_up(ndev, pl->link_an_mode);
++
++ netif_carrier_on(ndev);
++
++ netdev_info(ndev,
++ "Link is Up - %s/%s - flow control %s\n",
++ phy_speed_to_str(link_state.speed),
++ link_state.duplex ? "Full" : "Half",
++ link_state.pause ? "rx/tx" : "off");
++ }
++ }
++ mutex_unlock(&pl->state_mutex);
++}
++
++static void phylink_run_resolve(struct phylink *pl)
++{
++ if (!pl->phylink_disable_state)
++ queue_work(system_power_efficient_wq, &pl->resolve);
++}
++
++struct phylink *phylink_create(struct net_device *ndev, struct device_node *np,
++ phy_interface_t iface, const struct phylink_mac_ops *ops)
++{
++ struct phylink *pl;
++ int ret;
++
++ pl = kzalloc(sizeof(*pl), GFP_KERNEL);
++ if (!pl)
++ return ERR_PTR(-ENOMEM);
++
++ mutex_init(&pl->state_mutex);
++ mutex_init(&pl->config_mutex);
++ INIT_WORK(&pl->resolve, phylink_resolve);
++ pl->netdev = ndev;
++ pl->link_interface = iface;
++ pl->link_port_support = SUPPORTED_MII;
++ pl->link_port = PORT_MII;
++ pl->ops = ops;
++ __set_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state);
++
++ ret = phylink_parse_fixedlink(pl, np);
++ if (ret < 0) {
++ kfree(pl);
++ return ERR_PTR(ret);
++ }
++
++ ret = phylink_parse_managed(pl, np);
++ if (ret < 0) {
++ kfree(pl);
++ return ERR_PTR(ret);
++ }
++
++ ret = phylink_get_support(pl, pl->link_an_mode);
++ if (ret) {
++ kfree(pl);
++ return ERR_PTR(ret);
++ }
++
++ return pl;
++}
++EXPORT_SYMBOL_GPL(phylink_create);
++
++void phylink_destroy(struct phylink *pl)
++{
++ cancel_work_sync(&pl->resolve);
++ kfree(pl);
++}
++EXPORT_SYMBOL_GPL(phylink_destroy);
++
++void phylink_phy_change(struct phy_device *phy, bool up, bool do_carrier)
++{
++ struct phylink *pl = phy->phylink;
++
++ mutex_lock(&pl->state_mutex);
++ pl->phy_state.speed = phy->speed;
++ pl->phy_state.duplex = phy->duplex;
++ pl->phy_state.pause = MLO_PAUSE_NONE;
++ if (phy->pause)
++ pl->phy_state.pause |= MLO_PAUSE_SYM;
++ if (phy->asym_pause)
++ pl->phy_state.pause |= MLO_PAUSE_ASYM;
++ pl->phy_state.link = up;
++ mutex_unlock(&pl->state_mutex);
++
++ phylink_run_resolve(pl);
++
++ netdev_dbg(pl->netdev, "phy link %s\n", up ? "up" : "down");
++}
++
++static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy)
++{
++ mutex_lock(&pl->config_mutex);
++ phy->phylink = pl;
++ phy->phy_link_change = phylink_phy_change;
++
++ netdev_info(pl->netdev,
++ "PHY [%s] driver [%s]\n", dev_name(&phy->dev),
++ phy->drv->name);
++
++ mutex_lock(&pl->state_mutex);
++ pl->phydev = phy;
++
++ /* Restrict the phy advertisment to the union of the PHY and
++ * MAC-level advert.
++ */
++ phy->advertising &= ADVERTISED_INTERFACES |
++ pl->link_config.advertising;
++ mutex_unlock(&pl->state_mutex);
++
++ phy_start_machine(phy);
++ if (phy->irq > 0)
++ phy_start_interrupts(phy);
++
++ mutex_unlock(&pl->config_mutex);
++
++ return 0;
++}
++
++int phylink_connect_phy(struct phylink *pl, struct phy_device *phy)
++{
++ int ret;
++
++ ret = phy_attach_direct(pl->netdev, phy, 0, pl->link_interface);
++ if (ret)
++ return ret;
++
++ ret = phylink_bringup_phy(pl, phy);
++ if (ret)
++ phy_detach(phy);
++
++ return ret;
++}
++EXPORT_SYMBOL_GPL(phylink_connect_phy);
++
++int phylink_of_phy_connect(struct phylink *pl, struct device_node *dn)
++{
++ struct device_node *phy_node;
++ struct phy_device *phy_dev;
++ int ret;
++
++ /* Fixed links are handled without needing a PHY */
++ if (pl->link_an_mode == MLO_AN_FIXED)
++ return 0;
++
++ phy_node = of_parse_phandle(dn, "phy-handle", 0);
++ if (!phy_node)
++ phy_node = of_parse_phandle(dn, "phy", 0);
++ if (!phy_node)
++ phy_node = of_parse_phandle(dn, "phy-device", 0);
++
++ if (!phy_node) {
++ if (pl->link_an_mode == MLO_AN_PHY) {
++ netdev_err(pl->netdev, "unable to find PHY node\n");
++ return -ENODEV;
++ }
++ return 0;
++ }
++
++ phy_dev = of_phy_attach(pl->netdev, phy_node, 0, pl->link_interface);
++ /* We're done with the phy_node handle */
++ of_node_put(phy_node);
++
++ if (!phy_dev)
++ return -ENODEV;
++
++ ret = phylink_bringup_phy(pl, phy_dev);
++ if (ret)
++ phy_detach(phy_dev);
++
++ return ret;
++}
++EXPORT_SYMBOL_GPL(phylink_of_phy_connect);
++
++void phylink_disconnect_phy(struct phylink *pl)
++{
++ struct phy_device *phy;
++
++ mutex_lock(&pl->config_mutex);
++ phy = pl->phydev;
++
++ mutex_lock(&pl->state_mutex);
++ pl->phydev = NULL;
++ mutex_unlock(&pl->state_mutex);
++ flush_work(&pl->resolve);
++
++ if (phy)
++ phy_disconnect(phy);
++
++ mutex_unlock(&pl->config_mutex);
++}
++EXPORT_SYMBOL_GPL(phylink_disconnect_phy);
++
++void phylink_mac_change(struct phylink *pl, bool up)
++{
++ phylink_run_resolve(pl);
++ netdev_dbg(pl->netdev, "mac link %s\n", up ? "up" : "down");
++}
++EXPORT_SYMBOL_GPL(phylink_mac_change);
++
++void phylink_start(struct phylink *pl)
++{
++ mutex_lock(&pl->config_mutex);
++
++ netdev_info(pl->netdev, "configuring for %s link mode\n",
++ phylink_an_mode_str(pl->link_an_mode));
++
++ /* Apply the link configuration to the MAC when starting. This allows
++ * a fixed-link to start with the correct parameters, and also
++ * ensures that we set the appropriate advertisment for Serdes links.
++ */
++ phylink_mac_config(pl, &pl->link_config);
++
++ clear_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state);
++ phylink_run_resolve(pl);
++
++ if (pl->phydev)
++ phy_start(pl->phydev);
++
++ mutex_unlock(&pl->config_mutex);
++}
++EXPORT_SYMBOL_GPL(phylink_start);
++
++void phylink_stop(struct phylink *pl)
++{
++ mutex_lock(&pl->config_mutex);
++
++ if (pl->phydev)
++ phy_stop(pl->phydev);
++
++ set_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state);
++ flush_work(&pl->resolve);
++
++ pl->mac_link_up = false;
++
++ mutex_unlock(&pl->config_mutex);
++}
++EXPORT_SYMBOL_GPL(phylink_stop);
++
++static void phylink_get_ethtool(const struct phylink_link_state *state,
++ struct ethtool_cmd *cmd)
++{
++ cmd->supported &= SUPPORTED_INTERFACES;
++ cmd->supported |= state->supported;
++ cmd->advertising &= ADVERTISED_INTERFACES;
++ cmd->advertising |= state->advertising;
++ ethtool_cmd_speed_set(cmd, state->speed);
++ cmd->duplex = state->duplex;
++
++ cmd->autoneg = state->an_enabled ? AUTONEG_ENABLE : AUTONEG_DISABLE;
++}
++
++static int phylink_ethtool_gset(struct phylink *pl, struct ethtool_cmd *cmd)
++{
++ struct phylink_link_state link_state;
++ int ret;
++
++ if (pl->phydev) {
++ ret = phy_ethtool_gset(pl->phydev, cmd);
++ if (ret)
++ return ret;
++
++ cmd->supported &= SUPPORTED_INTERFACES |
++ pl->link_config.supported;
++ } else {
++ cmd->supported = pl->link_port_support;
++ cmd->transceiver = XCVR_EXTERNAL;
++ cmd->port = pl->link_port;
++ }
++
++ switch (pl->link_an_mode) {
++ case MLO_AN_FIXED:
++ /* We are using fixed settings. Report these as the
++ * current link settings - and note that these also
++ * represent the supported speeds/duplex/pause modes.
++ */
++ phylink_get_fixed_state(pl, &link_state);
++ phylink_get_ethtool(&link_state, cmd);
++ break;
++
++ case MLO_AN_SGMII:
++ /* If there is a phy attached, then use the reported
++ * settings from the phy with no modification.
++ */
++ if (pl->phydev)
++ break;
++
++ case MLO_AN_8023Z:
++ phylink_get_mac_state(pl, &link_state);
++
++ /* The MAC is reporting the link results from its own PCS
++ * layer via in-band status. Report these as the current
++ * link settings.
++ */
++ phylink_get_ethtool(&link_state, cmd);
++ break;
++ }
++
++ return 0;
++}
++
++int phylink_ethtool_get_settings(struct phylink *pl, struct ethtool_cmd *cmd)
++{
++ int ret;
++
++ mutex_lock(&pl->config_mutex);
++ ret = phylink_ethtool_gset(pl, cmd);
++ mutex_unlock(&pl->config_mutex);
++
++ return ret;
++}
++EXPORT_SYMBOL_GPL(phylink_ethtool_get_settings);
++
++static int phylink_ethtool_sset(struct phylink *pl, struct ethtool_cmd *cmd)
++{
++ u32 supported;
++ int ret;
++
++ /* Calculate the union of the MAC support and attached phy support */
++ supported = pl->link_config.supported;
++ if (pl->phydev)
++ supported &= pl->phydev->supported;
++
++ /* Mask out unsupported advertisments */
++ cmd->advertising &= supported;
++
++ /* FIXME: should we reject autoneg if phy/mac does not support it? */
++
++ if (cmd->autoneg == AUTONEG_DISABLE) {
++ /* Autonegotiation disabled, validate speed and duplex */
++ if (cmd->duplex != DUPLEX_HALF && cmd->duplex != DUPLEX_FULL)
++ return -EINVAL;
++
++ /* FIXME: validate speed/duplex against supported */
++
++ cmd->advertising &= ~ADVERTISED_Autoneg;
++ } else {
++ /* Autonegotiation enabled, validate advertisment */
++ /* FIXME: shouldn't we ensure there's some duplex/speeds set */
++ if (cmd->advertising == 0)
++ return -EINVAL;
++
++ cmd->advertising |= ADVERTISED_Autoneg;
++ }
++
++ /* If we have a fixed link (as specified by firmware), refuse
++ * to enable autonegotiation, or change link parameters.
++ */
++ if (pl->link_an_mode == MLO_AN_FIXED) {
++ if (cmd->autoneg != AUTONEG_DISABLE ||
++ ethtool_cmd_speed(cmd) != pl->link_config.speed ||
++ cmd->duplex != pl->link_config.duplex)
++ return -EINVAL;
++ }
++
++ /* If we have a PHY, configure the phy */
++ if (pl->phydev) {
++ ret = phy_ethtool_sset(pl->phydev, cmd);
++ if (ret)
++ return ret;
++ }
++
++ mutex_lock(&pl->state_mutex);
++ /* Configure the MAC to match the new settings */
++ pl->link_config.advertising = cmd->advertising;
++ pl->link_config.speed = cmd->speed;
++ pl->link_config.duplex = cmd->duplex;
++ pl->link_config.an_enabled = cmd->autoneg != AUTONEG_DISABLE;
++
++ phylink_mac_config(pl, &pl->link_config);
++ phylink_mac_an_restart(pl);
++ mutex_unlock(&pl->state_mutex);
++
++ return ret;
++}
++
++int phylink_ethtool_set_settings(struct phylink *pl, struct ethtool_cmd *cmd)
++{
++ int ret;
++
++ if (cmd->autoneg != AUTONEG_DISABLE && cmd->autoneg != AUTONEG_ENABLE)
++ return -EINVAL;
++
++ mutex_lock(&pl->config_mutex);
++ ret = phylink_ethtool_sset(pl, cmd);
++ mutex_unlock(&pl->config_mutex);
++
++ return ret;
++}
++EXPORT_SYMBOL_GPL(phylink_ethtool_set_settings);
++
++/* This emulates MII registers for a fixed-mode phy operating as per the
++ * passed in state. "aneg" defines if we report negotiation is possible.
++ *
++ * FIXME: should deal with negotiation state too.
++ */
++static int phylink_mii_emul_read(struct net_device *ndev, unsigned int reg,
++ struct phylink_link_state *state, bool aneg)
++{
++ struct fixed_phy_status fs;
++ int val;
++
++ fs.link = state->link;
++ fs.speed = state->speed;
++ fs.duplex = state->duplex;
++ fs.pause = state->pause & MLO_PAUSE_SYM;
++ fs.asym_pause = state->pause & MLO_PAUSE_ASYM;
++
++ val = swphy_read_reg(reg, &fs);
++ if (reg == MII_BMSR) {
++ if (!state->an_complete)
++ val &= ~BMSR_ANEGCOMPLETE;
++ if (!aneg)
++ val &= ~BMSR_ANEGCAPABLE;
++ }
++ return val;
++}
++
++static int phylink_mii_read(struct phylink *pl, unsigned int phy_id,
++ unsigned int reg)
++{
++ struct phylink_link_state state;
++ int val = 0xffff;
++
++ if (pl->phydev && pl->phydev->addr != phy_id)
++ return mdiobus_read(pl->phydev->bus, phy_id, reg);
++
++ if (!pl->phydev && phy_id != 0)
++ return val;
++
++ switch (pl->link_an_mode) {
++ case MLO_AN_FIXED:
++ phylink_get_fixed_state(pl, &state);
++ val = phylink_mii_emul_read(pl->netdev, reg, &state, true);
++ break;
++
++ case MLO_AN_PHY:
++ val = mdiobus_read(pl->phydev->bus, phy_id, reg);
++ break;
++
++ case MLO_AN_SGMII:
++ if (pl->phydev) {
++ val = mdiobus_read(pl->phydev->bus,
++ pl->phydev->addr, reg);
++ break;
++ }
++ /* No phy, fall through to reading the MAC end */
++ case MLO_AN_8023Z:
++ val = phylink_get_mac_state(pl, &state);
++ if (val < 0)
++ return val;
++
++ val = phylink_mii_emul_read(pl->netdev, reg, &state, true);
++ break;
++ }
++
++ return val & 0xffff;
++}
++
++static void phylink_mii_write(struct phylink *pl, unsigned int phy_id,
++ unsigned int reg, unsigned int val)
++{
++ if (pl->phydev && pl->phydev->addr != phy_id) {
++ mdiobus_write(pl->phydev->bus, phy_id, reg, val);
++ return;
++ }
++
++ if (!pl->phydev && phy_id != 0)
++ return;
++
++ switch (pl->link_an_mode) {
++ case MLO_AN_FIXED:
++ break;
++
++ case MLO_AN_PHY:
++ mdiobus_write(pl->phydev->bus, pl->phydev->addr,
++ reg, val);
++ break;
++
++ case MLO_AN_SGMII:
++ if (pl->phydev) {
++ mdiobus_write(pl->phydev->bus, phy_id, reg, val);
++ break;
++ }
++ /* No phy, fall through to reading the MAC end */
++ case MLO_AN_8023Z:
++ break;
++ }
++}
++
++int phylink_mii_ioctl(struct phylink *pl, struct ifreq *ifr, int cmd)
++{
++ struct mii_ioctl_data *mii_data = if_mii(ifr);
++ int val, ret;
++
++ mutex_lock(&pl->config_mutex);
++
++ switch (cmd) {
++ case SIOCGMIIPHY:
++ mii_data->phy_id = pl->phydev ? pl->phydev->addr : 0;
++ /* fallthrough */
++
++ case SIOCGMIIREG:
++ val = phylink_mii_read(pl, mii_data->phy_id, mii_data->reg_num);
++ if (val < 0) {
++ ret = val;
++ } else {
++ mii_data->val_out = val;
++ ret = 0;
++ }
++ break;
++
++ case SIOCSMIIREG:
++ phylink_mii_write(pl, mii_data->phy_id, mii_data->reg_num,
++ mii_data->val_in);
++ ret = 0;
++ break;
++
++ default:
++ ret = -EOPNOTSUPP;
++ if (pl->phydev)
++ ret = phy_mii_ioctl(pl->phydev, ifr, cmd);
++ break;
++ }
++
++ mutex_unlock(&pl->config_mutex);
++
++ return ret;
++}
++EXPORT_SYMBOL_GPL(phylink_mii_ioctl);
++
++MODULE_LICENSE("GPL");
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -147,6 +147,7 @@ static inline const char *phy_modes(phy_
+ #define MII_ADDR_C45 (1<<30)
+
+ struct device;
++struct phylink;
+ struct sk_buff;
+
+ /*
+@@ -429,6 +430,7 @@ struct phy_device {
+
+ struct mutex lock;
+
++ struct phylink *phylink;
+ struct net_device *attached_dev;
+
+ u8 mdix;
+--- /dev/null
++++ b/include/linux/phylink.h
+@@ -0,0 +1,70 @@
++#ifndef NETDEV_PCS_H
++#define NETDEV_PCS_H
++
++#include <linux/phy.h>
++#include <linux/spinlock.h>
++#include <linux/workqueue.h>
++
++struct device_node;
++struct ethtool_cmd;
++struct net_device;
++
++enum {
++ MLO_PAUSE_NONE,
++ MLO_PAUSE_ASYM = BIT(0),
++ MLO_PAUSE_SYM = BIT(1),
++
++ MLO_AN_PHY = 0,
++ MLO_AN_FIXED,
++ MLO_AN_SGMII,
++ MLO_AN_8023Z,
++};
++
++struct phylink_link_state {
++ u32 supported;
++ u32 advertising;
++ u32 lp_advertising;
++ int speed;
++ int duplex;
++ int pause;
++ unsigned int link:1;
++ unsigned int sync:1;
++ unsigned int an_enabled:1;
++ unsigned int an_complete:1;
++};
++
++struct phylink_mac_ops {
++ /* Get the ethtool supported mask for the indicated mode */
++ int (*mac_get_support)(struct net_device *, unsigned int mode,
++ struct phylink_link_state *);
++
++ /* Read the current link state from the hardware */
++ int (*mac_link_state)(struct net_device *, struct phylink_link_state *);
++
++ /* Configure the MAC */
++ void (*mac_config)(struct net_device *, unsigned int mode,
++ const struct phylink_link_state *);
++ void (*mac_an_restart)(struct net_device *, unsigned int mode);
++
++ void (*mac_link_down)(struct net_device *, unsigned int mode);
++ void (*mac_link_up)(struct net_device *, unsigned int mode);
++};
++
++struct phylink *phylink_create(struct net_device *, struct device_node *,
++ phy_interface_t iface, const struct phylink_mac_ops *ops);
++void phylink_destroy(struct phylink *);
++
++int phylink_connect_phy(struct phylink *, struct phy_device *);
++int phylink_of_phy_connect(struct phylink *, struct device_node *);
++void phylink_disconnect_phy(struct phylink *);
++
++void phylink_mac_change(struct phylink *, bool up);
++
++void phylink_start(struct phylink *);
++void phylink_stop(struct phylink *);
++
++int phylink_ethtool_get_settings(struct phylink *, struct ethtool_cmd *);
++int phylink_ethtool_set_settings(struct phylink *, struct ethtool_cmd *);
++int phylink_mii_ioctl(struct phylink *, struct ifreq *, int);
++
++#endif
--- /dev/null
+From 0a0c4b3dd4f34df4532f254a5940b520015d766f Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Thu, 24 Sep 2015 11:01:13 +0100
+Subject: [PATCH 719/744] phylink: add hooks for SFP support
+
+Add support to phylink for SFP, which needs to control and configure
+the ethernet MAC link state. Specifically, SFP needs to:
+
+1. set the negotiation mode between SGMII and 1000base-X
+2. attach and detach the module PHY
+3. prevent the link coming up when errors are reported
+
+In the absence of a PHY, we also need to set the ethtool port type
+according to the module plugged in.
+
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ drivers/net/phy/phylink.c | 82 +++++++++++++++++++++++++++++++++++++++++++++++
+ include/linux/phylink.h | 6 ++++
+ 2 files changed, 88 insertions(+)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -11,6 +11,7 @@
+ #include <linux/ethtool.h>
+ #include <linux/export.h>
+ #include <linux/gpio/consumer.h>
++#include <linux/list.h>
+ #include <linux/netdevice.h>
+ #include <linux/of.h>
+ #include <linux/of_mdio.h>
+@@ -29,11 +30,16 @@
+ (ADVERTISED_TP | ADVERTISED_MII | ADVERTISED_FIBRE | \
+ ADVERTISED_BNC | ADVERTISED_AUI | ADVERTISED_Backplane)
+
++static LIST_HEAD(phylinks);
++static DEFINE_MUTEX(phylink_mutex);
++
+ enum {
+ PHYLINK_DISABLE_STOPPED,
++ PHYLINK_DISABLE_LINK,
+ };
+
+ struct phylink {
++ struct list_head node;
+ struct net_device *netdev;
+ const struct phylink_mac_ops *ops;
+ struct mutex config_mutex;
+@@ -341,12 +347,20 @@ struct phylink *phylink_create(struct ne
+ return ERR_PTR(ret);
+ }
+
++ mutex_lock(&phylink_mutex);
++ list_add_tail(&pl->node, &phylinks);
++ mutex_unlock(&phylink_mutex);
++
+ return pl;
+ }
+ EXPORT_SYMBOL_GPL(phylink_create);
+
+ void phylink_destroy(struct phylink *pl)
+ {
++ mutex_lock(&phylink_mutex);
++ list_del(&pl->node);
++ mutex_unlock(&phylink_mutex);
++
+ cancel_work_sync(&pl->resolve);
+ kfree(pl);
+ }
+@@ -813,4 +827,72 @@ int phylink_mii_ioctl(struct phylink *pl
+ }
+ EXPORT_SYMBOL_GPL(phylink_mii_ioctl);
+
++
++
++void phylink_disable(struct phylink *pl)
++{
++ set_bit(PHYLINK_DISABLE_LINK, &pl->phylink_disable_state);
++ flush_work(&pl->resolve);
++
++ netif_carrier_off(pl->netdev);
++}
++EXPORT_SYMBOL_GPL(phylink_disable);
++
++void phylink_enable(struct phylink *pl)
++{
++ clear_bit(PHYLINK_DISABLE_LINK, &pl->phylink_disable_state);
++ phylink_run_resolve(pl);
++}
++EXPORT_SYMBOL_GPL(phylink_enable);
++
++void phylink_set_link_port(struct phylink *pl, u32 support, u8 port)
++{
++ WARN_ON(support & ~SUPPORTED_INTERFACES);
++
++ mutex_lock(&pl->config_mutex);
++ pl->link_port_support = support;
++ pl->link_port = port;
++ mutex_unlock(&pl->config_mutex);
++}
++EXPORT_SYMBOL_GPL(phylink_set_link_port);
++
++int phylink_set_link_an_mode(struct phylink *pl, unsigned int mode)
++{
++ int ret = 0;
++
++ mutex_lock(&pl->config_mutex);
++ if (pl->link_an_mode != mode) {
++ ret = phylink_get_support(pl, mode);
++ if (ret == 0) {
++ if (!test_bit(PHYLINK_DISABLE_STOPPED,
++ &pl->phylink_disable_state))
++ phylink_mac_config(pl, &pl->link_config);
++
++ netdev_info(pl->netdev, "switched to %s link mode\n",
++ phylink_an_mode_str(mode));
++ }
++ }
++ mutex_unlock(&pl->config_mutex);
++
++ return ret;
++}
++EXPORT_SYMBOL_GPL(phylink_set_link_an_mode);
++
++struct phylink *phylink_lookup_by_netdev(struct net_device *ndev)
++{
++ struct phylink *pl, *found = NULL;
++
++ mutex_lock(&phylink_mutex);
++ list_for_each_entry(pl, &phylinks, node)
++ if (pl->netdev == ndev) {
++ found = pl;
++ break;
++ }
++
++ mutex_unlock(&phylink_mutex);
++
++ return found;
++}
++EXPORT_SYMBOL_GPL(phylink_lookup_by_netdev);
++
+ MODULE_LICENSE("GPL");
+--- a/include/linux/phylink.h
++++ b/include/linux/phylink.h
+@@ -67,4 +67,10 @@ int phylink_ethtool_get_settings(struct
+ int phylink_ethtool_set_settings(struct phylink *, struct ethtool_cmd *);
+ int phylink_mii_ioctl(struct phylink *, struct ifreq *, int);
+
++void phylink_set_link_port(struct phylink *pl, u32 support, u8 port);
++int phylink_set_link_an_mode(struct phylink *pl, unsigned int mode);
++void phylink_disable(struct phylink *pl);
++void phylink_enable(struct phylink *pl);
++struct phylink *phylink_lookup_by_netdev(struct net_device *ndev);
++
+ #endif
--- /dev/null
+From bf0a000960234c0e773fadea47240c3cda0cab02 Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Sat, 12 Sep 2015 18:43:39 +0100
+Subject: [PATCH 720/744] sfp: add phylink based SFP module support
+
+Add support for SFP hotpluggable modules via phylink. This supports
+both copper and optical SFP modules, which require different Serdes
+modes in order to properly negotiate the link.
+
+Optical SFP modules typically require the Serdes link to be talking
+1000base-X mode - this is the gigabit ethernet mode defined by the
+802.3 standard.
+
+Copper SFP modules typically integrate a PHY in the module to convert
+from Serdes to copper, and the PHY will be configured by the vendor
+to either present a 1000base-X Serdes link (for fixed 1000base-T) or
+a SGMII Serdes link. However, this is vendor defined, so we instead
+detect the PHY, switch the link to SGMII mode, and use traditional
+PHY based negotiation.
+
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ drivers/net/phy/Kconfig | 5 +
+ drivers/net/phy/Makefile | 1 +
+ drivers/net/phy/sfp.c | 986 +++++++++++++++++++++++++++++++++++++++++++++++
+ include/linux/sfp.h | 339 ++++++++++++++++
+ 4 files changed, 1331 insertions(+)
+ create mode 100644 drivers/net/phy/sfp.c
+ create mode 100644 include/linux/sfp.h
+
+--- a/drivers/net/phy/Kconfig
++++ b/drivers/net/phy/Kconfig
+@@ -225,6 +225,11 @@ config FIXED_PHY
+
+ Currently tested with mpc866ads and mpc8349e-mitx.
+
++config SFP
++ tristate "SFP cage support"
++ depends on I2C && PHYLINK
++ select MDIO_I2C
++
+ config MDIO_BITBANG
+ tristate "Support for bitbanged MDIO buses"
+ help
+--- a/drivers/net/phy/Makefile
++++ b/drivers/net/phy/Makefile
+@@ -61,3 +61,4 @@ obj-$(CONFIG_MDIO_MOXART) += mdio-moxart
+ obj-$(CONFIG_MDIO_BCM_UNIMAC) += mdio-bcm-unimac.o
+ obj-$(CONFIG_MICROCHIP_PHY) += microchip.o
+ obj-$(CONFIG_MDIO_BCM_IPROC) += mdio-bcm-iproc.o
++obj-$(CONFIG_SFP) += sfp.o
+--- /dev/null
++++ b/drivers/net/phy/sfp.c
+@@ -0,0 +1,986 @@
++#include <linux/delay.h>
++#include <linux/gpio.h>
++#include <linux/i2c.h>
++#include <linux/interrupt.h>
++#include <linux/jiffies.h>
++#include <linux/module.h>
++#include <linux/mutex.h>
++#include <linux/netdevice.h>
++#include <linux/of.h>
++#include <linux/of_net.h>
++#include <linux/phylink.h>
++#include <linux/platform_device.h>
++#include <linux/sfp.h>
++#include <linux/slab.h>
++#include <linux/workqueue.h>
++
++#include "mdio-i2c.h"
++#include "swphy.h"
++
++enum {
++ GPIO_MODDEF0,
++ GPIO_LOS,
++ GPIO_TX_FAULT,
++ GPIO_TX_DISABLE,
++ GPIO_RATE_SELECT,
++ GPIO_MAX,
++
++ SFP_F_PRESENT = BIT(GPIO_MODDEF0),
++ SFP_F_LOS = BIT(GPIO_LOS),
++ SFP_F_TX_FAULT = BIT(GPIO_TX_FAULT),
++ SFP_F_TX_DISABLE = BIT(GPIO_TX_DISABLE),
++ SFP_F_RATE_SELECT = BIT(GPIO_RATE_SELECT),
++
++ SFP_E_INSERT = 0,
++ SFP_E_REMOVE,
++ SFP_E_DEV_DOWN,
++ SFP_E_DEV_UP,
++ SFP_E_TX_FAULT,
++ SFP_E_TX_CLEAR,
++ SFP_E_LOS_HIGH,
++ SFP_E_LOS_LOW,
++ SFP_E_TIMEOUT,
++
++ SFP_MOD_EMPTY = 0,
++ SFP_MOD_PROBE,
++ SFP_MOD_PRESENT,
++ SFP_MOD_ERROR,
++
++ SFP_DEV_DOWN = 0,
++ SFP_DEV_UP,
++
++ SFP_S_DOWN = 0,
++ SFP_S_INIT,
++ SFP_S_WAIT_LOS,
++ SFP_S_LINK_UP,
++ SFP_S_TX_FAULT,
++ SFP_S_REINIT,
++ SFP_S_TX_DISABLE,
++};
++
++static const char *gpio_of_names[] = {
++ "moddef0",
++ "los",
++ "tx-fault",
++ "tx-disable",
++ "rate-select",
++};
++
++static const enum gpiod_flags gpio_flags[] = {
++ GPIOD_IN,
++ GPIOD_IN,
++ GPIOD_IN,
++ GPIOD_ASIS,
++ GPIOD_ASIS,
++};
++
++#define T_INIT_JIFFIES msecs_to_jiffies(300)
++#define T_RESET_US 10
++#define T_FAULT_RECOVER msecs_to_jiffies(1000)
++
++/* SFP module presence detection is poor: the three MOD DEF signals are
++ * the same length on the PCB, which means it's possible for MOD DEF 0 to
++ * connect before the I2C bus on MOD DEF 1/2. Try to work around this
++ * design bug by waiting 50ms before probing, and then retry every 250ms.
++ */
++#define T_PROBE_INIT msecs_to_jiffies(50)
++#define T_PROBE_RETRY msecs_to_jiffies(250)
++
++/*
++ * SFP modules appear to always have their PHY configured for bus address
++ * 0x56 (which with mdio-i2c, translates to a PHY address of 22).
++ */
++#define SFP_PHY_ADDR 22
++
++/*
++ * Give this long for the PHY to reset.
++ */
++#define T_PHY_RESET_MS 50
++
++static DEFINE_MUTEX(sfp_mutex);
++
++struct sfp {
++ struct device *dev;
++ struct i2c_adapter *i2c;
++ struct mii_bus *i2c_mii;
++ struct net_device *ndev;
++ struct phylink *phylink;
++ struct phy_device *mod_phy;
++
++ unsigned int (*get_state)(struct sfp *);
++ void (*set_state)(struct sfp *, unsigned int);
++ int (*read)(struct sfp *, bool, u8, void *, size_t);
++
++ struct gpio_desc *gpio[GPIO_MAX];
++
++ unsigned int state;
++ struct delayed_work poll;
++ struct delayed_work timeout;
++ struct mutex sm_mutex;
++ unsigned char sm_mod_state;
++ unsigned char sm_dev_state;
++ unsigned short sm_state;
++ unsigned int sm_retries;
++
++ struct sfp_eeprom_id id;
++
++ struct notifier_block netdev_nb;
++};
++
++static unsigned long poll_jiffies;
++
++static unsigned int sfp_gpio_get_state(struct sfp *sfp)
++{
++ unsigned int i, state, v;
++
++ for (i = state = 0; i < GPIO_MAX; i++) {
++ if (gpio_flags[i] != GPIOD_IN || !sfp->gpio[i])
++ continue;
++
++ v = gpiod_get_value_cansleep(sfp->gpio[i]);
++ if (v)
++ state |= BIT(i);
++ }
++
++ return state;
++}
++
++static void sfp_gpio_set_state(struct sfp *sfp, unsigned int state)
++{
++ if (state & SFP_F_PRESENT) {
++ /* If the module is present, drive the signals */
++ if (sfp->gpio[GPIO_TX_DISABLE])
++ gpiod_direction_output(sfp->gpio[GPIO_TX_DISABLE],
++ state & SFP_F_TX_DISABLE);
++ if (state & SFP_F_RATE_SELECT)
++ gpiod_direction_output(sfp->gpio[GPIO_RATE_SELECT],
++ state & SFP_F_RATE_SELECT);
++ } else {
++ /* Otherwise, let them float to the pull-ups */
++ if (sfp->gpio[GPIO_TX_DISABLE])
++ gpiod_direction_input(sfp->gpio[GPIO_TX_DISABLE]);
++ if (state & SFP_F_RATE_SELECT)
++ gpiod_direction_input(sfp->gpio[GPIO_RATE_SELECT]);
++ }
++}
++
++static int sfp__i2c_read(struct i2c_adapter *i2c, u8 bus_addr, u8 dev_addr,
++ void *buf, size_t len)
++{
++ struct i2c_msg msgs[2];
++ int ret;
++
++ msgs[0].addr = bus_addr;
++ msgs[0].flags = 0;
++ msgs[0].len = 1;
++ msgs[0].buf = &dev_addr;
++ msgs[1].addr = bus_addr;
++ msgs[1].flags = I2C_M_RD;
++ msgs[1].len = len;
++ msgs[1].buf = buf;
++
++ ret = i2c_transfer(i2c, msgs, ARRAY_SIZE(msgs));
++ if (ret < 0)
++ return ret;
++
++ return ret == ARRAY_SIZE(msgs) ? len : 0;
++}
++
++static int sfp_i2c_read(struct sfp *sfp, bool a2, u8 addr, void *buf,
++ size_t len)
++{
++ return sfp__i2c_read(sfp->i2c, a2 ? 0x51 : 0x50, addr, buf, len);
++}
++
++static int sfp_i2c_configure(struct sfp *sfp, struct i2c_adapter *i2c)
++{
++ struct mii_bus *i2c_mii;
++ int ret;
++
++ if (!i2c_check_functionality(i2c, I2C_FUNC_I2C))
++ return -EINVAL;
++
++ sfp->i2c = i2c;
++ sfp->read = sfp_i2c_read;
++
++ i2c_mii = mdio_i2c_alloc(sfp->dev, i2c);
++ if (IS_ERR(i2c_mii))
++ return PTR_ERR(i2c_mii);
++
++ i2c_mii->name = "SFP I2C Bus";
++ i2c_mii->phy_mask = ~0;
++
++ ret = mdiobus_register(i2c_mii);
++ if (ret < 0) {
++ mdiobus_free(i2c_mii);
++ return ret;
++ }
++
++ sfp->i2c_mii = i2c_mii;
++
++ return 0;
++}
++
++
++/* Interface */
++static unsigned int sfp_get_state(struct sfp *sfp)
++{
++ return sfp->get_state(sfp);
++}
++
++static void sfp_set_state(struct sfp *sfp, unsigned int state)
++{
++ sfp->set_state(sfp, state);
++}
++
++static int sfp_read(struct sfp *sfp, bool a2, u8 addr, void *buf, size_t len)
++{
++ return sfp->read(sfp, a2, addr, buf, len);
++}
++
++static unsigned int sfp_check(void *buf, size_t len)
++{
++ u8 *p, check;
++
++ for (p = buf, check = 0; len; p++, len--)
++ check += *p;
++
++ return check;
++}
++
++/* Helpers */
++static void sfp_module_tx_disable(struct sfp *sfp)
++{
++ dev_dbg(sfp->dev, "tx disable %u -> %u\n",
++ sfp->state & SFP_F_TX_DISABLE ? 1 : 0, 1);
++ sfp->state |= SFP_F_TX_DISABLE;
++ sfp_set_state(sfp, sfp->state);
++}
++
++static void sfp_module_tx_enable(struct sfp *sfp)
++{
++ dev_dbg(sfp->dev, "tx disable %u -> %u\n",
++ sfp->state & SFP_F_TX_DISABLE ? 1 : 0, 0);
++ sfp->state &= ~SFP_F_TX_DISABLE;
++ sfp_set_state(sfp, sfp->state);
++}
++
++static void sfp_module_tx_fault_reset(struct sfp *sfp)
++{
++ unsigned int state = sfp->state;
++
++ if (state & SFP_F_TX_DISABLE)
++ return;
++
++ sfp_set_state(sfp, state | SFP_F_TX_DISABLE);
++
++ udelay(T_RESET_US);
++
++ sfp_set_state(sfp, state);
++}
++
++/* SFP state machine */
++static void sfp_sm_set_timer(struct sfp *sfp, unsigned int timeout)
++{
++ if (timeout)
++ mod_delayed_work(system_power_efficient_wq, &sfp->timeout,
++ timeout);
++ else
++ cancel_delayed_work(&sfp->timeout);
++}
++
++static void sfp_sm_next(struct sfp *sfp, unsigned int state,
++ unsigned int timeout)
++{
++ sfp->sm_state = state;
++ sfp_sm_set_timer(sfp, timeout);
++}
++
++static void sfp_sm_ins_next(struct sfp *sfp, unsigned int state, unsigned int timeout)
++{
++ sfp->sm_mod_state = state;
++ sfp_sm_set_timer(sfp, timeout);
++}
++
++static void sfp_sm_phy_detach(struct sfp *sfp)
++{
++ phy_stop(sfp->mod_phy);
++ if (sfp->phylink)
++ phylink_disconnect_phy(sfp->phylink);
++ phy_device_remove(sfp->mod_phy);
++ phy_device_free(sfp->mod_phy);
++ sfp->mod_phy = NULL;
++}
++
++static void sfp_sm_probe_phy(struct sfp *sfp)
++{
++ struct phy_device *phy;
++ int err;
++
++ msleep(T_PHY_RESET_MS);
++
++ phy = mdiobus_scan(sfp->i2c_mii, SFP_PHY_ADDR);
++ if (IS_ERR(phy)) {
++ dev_err(sfp->dev, "mdiobus scan returned %ld\n", PTR_ERR(phy));
++ return;
++ }
++ if (!phy) {
++ dev_info(sfp->dev, "no PHY detected\n");
++ return;
++ }
++
++ err = phylink_connect_phy(sfp->phylink, phy);
++ if (err) {
++ phy_device_remove(phy);
++ phy_device_free(phy);
++ dev_err(sfp->dev, "phylink_connect_phy failed: %d\n", err);
++ return;
++ }
++
++ sfp->mod_phy = phy;
++ phy_start(phy);
++}
++
++static void sfp_sm_link_up(struct sfp *sfp)
++{
++ if (sfp->phylink)
++ phylink_enable(sfp->phylink);
++
++ sfp_sm_next(sfp, SFP_S_LINK_UP, 0);
++}
++
++static void sfp_sm_link_down(struct sfp *sfp)
++{
++ if (sfp->phylink)
++ phylink_disable(sfp->phylink);
++}
++
++static void sfp_sm_link_check_los(struct sfp *sfp)
++{
++ unsigned int los = sfp->state & SFP_F_LOS;
++
++ /* FIXME: what if neither SFP_OPTIONS_LOS_INVERTED nor
++ * SFP_OPTIONS_LOS_NORMAL are set? For now, we assume
++ * the same as SFP_OPTIONS_LOS_NORMAL set.
++ */
++ if (sfp->id.ext.options & SFP_OPTIONS_LOS_INVERTED)
++ los ^= SFP_F_LOS;
++
++ if (los)
++ sfp_sm_next(sfp, SFP_S_WAIT_LOS, 0);
++ else
++ sfp_sm_link_up(sfp);
++}
++
++static void sfp_sm_fault(struct sfp *sfp, bool warn)
++{
++ if (sfp->sm_retries && !--sfp->sm_retries) {
++ dev_err(sfp->dev, "module persistently indicates fault, disabling\n");
++ sfp_sm_next(sfp, SFP_S_TX_DISABLE, 0);
++ } else {
++ if (warn)
++ dev_err(sfp->dev, "module transmit fault indicated\n");
++
++ sfp_sm_next(sfp, SFP_S_TX_FAULT, T_FAULT_RECOVER);
++ }
++}
++
++static void sfp_sm_mod_init(struct sfp *sfp)
++{
++ sfp_module_tx_enable(sfp);
++
++ /* Wait t_init before indicating that the link is up, provided the
++ * current state indicates no TX_FAULT. If TX_FAULT clears before
++ * this time, that's fine too.
++ */
++ sfp_sm_next(sfp, SFP_S_INIT, T_INIT_JIFFIES);
++ sfp->sm_retries = 5;
++
++ if (sfp->phylink) {
++ /* Setting the serdes link mode is guesswork: there's no
++ * field in the EEPROM which indicates what mode should
++ * be used.
++ *
++ * If it's a gigabit-only fiber module, it probably does
++ * not have a PHY, so switch to 802.3z negotiation mode.
++ * Otherwise, switch to SGMII mode (which is required to
++ * support non-gigabit speeds) and probe for a PHY.
++ */
++ if (!sfp->id.base.e1000_base_t &&
++ !sfp->id.base.e100_base_lx &&
++ !sfp->id.base.e100_base_fx) {
++ phylink_set_link_an_mode(sfp->phylink, MLO_AN_8023Z);
++ } else {
++ phylink_set_link_an_mode(sfp->phylink, MLO_AN_SGMII);
++ sfp_sm_probe_phy(sfp);
++ }
++ }
++}
++
++static int sfp_sm_mod_probe(struct sfp *sfp)
++{
++ /* SFP module inserted - read I2C data */
++ struct sfp_eeprom_id id;
++ char vendor[17];
++ char part[17];
++ char sn[17];
++ char date[9];
++ char rev[5];
++ u8 check;
++ int err;
++
++ err = sfp_read(sfp, false, 0, &id, sizeof(id));
++ if (err < 0) {
++ dev_err(sfp->dev, "failed to read EEPROM: %d\n", err);
++ return -EAGAIN;
++ }
++
++ /* Validate the checksum over the base structure */
++ check = sfp_check(&id.base, sizeof(id.base) - 1);
++ if (check != id.base.cc_base) {
++ dev_err(sfp->dev,
++ "EEPROM base structure checksum failure: 0x%02x\n",
++ check);
++ return -EINVAL;
++ }
++
++ check = sfp_check(&id.ext, sizeof(id.ext) - 1);
++ if (check != id.ext.cc_ext) {
++ dev_err(sfp->dev,
++ "EEPROM extended structure checksum failure: 0x%02x\n",
++ check);
++ memset(&id.ext, 0, sizeof(id.ext));
++ }
++
++ sfp->id = id;
++
++ memcpy(vendor, sfp->id.base.vendor_name, 16);
++ vendor[16] = '\0';
++ memcpy(part, sfp->id.base.vendor_pn, 16);
++ part[16] = '\0';
++ memcpy(rev, sfp->id.base.vendor_rev, 4);
++ rev[4] = '\0';
++ memcpy(sn, sfp->id.ext.vendor_sn, 16);
++ sn[16] = '\0';
++ memcpy(date, sfp->id.ext.datecode, 8);
++ date[8] = '\0';
++
++ dev_info(sfp->dev, "module %s %s rev %s sn %s dc %s\n", vendor, part, rev, sn, date);
++
++ /* We only support SFP modules, not the legacy GBIC modules. */
++ if (sfp->id.base.phys_id != SFP_PHYS_ID_SFP ||
++ sfp->id.base.phys_ext_id != SFP_PHYS_EXT_ID_SFP) {
++ dev_err(sfp->dev, "module is not SFP - phys id 0x%02x 0x%02x\n",
++ sfp->id.base.phys_id, sfp->id.base.phys_ext_id);
++ return -EINVAL;
++ }
++
++ /*
++ * What isn't clear from the SFP documentation is whether this
++ * specifies the encoding expected on the TD/RD lines, or whether
++ * the TD/RD lines are always 8b10b encoded, but the transceiver
++ * converts. Eg, think of a copper SFP supporting 1G/100M/10M
++ * ethernet: this requires 8b10b encoding for 1G, 4b5b for 100M,
++ * and manchester for 10M.
++ */
++ /* 1Gbit ethernet requires 8b10b encoding */
++ if (sfp->id.base.encoding != SFP_ENCODING_8B10B) {
++ dev_err(sfp->dev, "module does not support 8B10B encoding\n");
++ return -EINVAL;
++ }
++
++ if (sfp->phylink) {
++ u32 support;
++ u8 port;
++
++ if (sfp->id.base.e1000_base_t) {
++ support = SUPPORTED_TP;
++ port = PORT_TP;
++ } else {
++ support = SUPPORTED_FIBRE;
++ port = PORT_FIBRE;
++ }
++ phylink_set_link_port(sfp->phylink, support, port);
++ }
++
++ return 0;
++}
++
++static void sfp_sm_mod_remove(struct sfp *sfp)
++{
++ if (sfp->mod_phy)
++ sfp_sm_phy_detach(sfp);
++
++ sfp_module_tx_disable(sfp);
++
++ memset(&sfp->id, 0, sizeof(sfp->id));
++
++ dev_info(sfp->dev, "module removed\n");
++}
++
++static void sfp_sm_event(struct sfp *sfp, unsigned int event)
++{
++ mutex_lock(&sfp->sm_mutex);
++
++ dev_dbg(sfp->dev, "SM: enter %u:%u:%u event %u\n",
++ sfp->sm_mod_state, sfp->sm_dev_state, sfp->sm_state, event);
++
++ /* This state machine tracks the insert/remove state of
++ * the module, and handles probing the on-board EEPROM.
++ */
++ switch (sfp->sm_mod_state) {
++ default:
++ if (event == SFP_E_INSERT) {
++ sfp_module_tx_disable(sfp);
++ sfp_sm_ins_next(sfp, SFP_MOD_PROBE, T_PROBE_INIT);
++ }
++ break;
++
++ case SFP_MOD_PROBE:
++ if (event == SFP_E_REMOVE) {
++ sfp_sm_ins_next(sfp, SFP_MOD_EMPTY, 0);
++ } else if (event == SFP_E_TIMEOUT) {
++ int err = sfp_sm_mod_probe(sfp);
++
++ if (err == 0)
++ sfp_sm_ins_next(sfp, SFP_MOD_PRESENT, 0);
++ else if (err == -EAGAIN)
++ sfp_sm_set_timer(sfp, T_PROBE_RETRY);
++ else
++ sfp_sm_ins_next(sfp, SFP_MOD_ERROR, 0);
++ }
++ break;
++
++ case SFP_MOD_PRESENT:
++ case SFP_MOD_ERROR:
++ if (event == SFP_E_REMOVE) {
++ sfp_sm_mod_remove(sfp);
++ sfp_sm_ins_next(sfp, SFP_MOD_EMPTY, 0);
++ }
++ break;
++ }
++
++ /* This state machine tracks the netdev up/down state */
++ switch (sfp->sm_dev_state) {
++ default:
++ if (event == SFP_E_DEV_UP)
++ sfp->sm_dev_state = SFP_DEV_UP;
++ break;
++
++ case SFP_DEV_UP:
++ if (event == SFP_E_DEV_DOWN) {
++ /* If the module has a PHY, avoid raising TX disable
++ * as this resets the PHY. Otherwise, raise it to
++ * turn the laser off.
++ */
++ if (!sfp->mod_phy)
++ sfp_module_tx_disable(sfp);
++ sfp->sm_dev_state = SFP_DEV_DOWN;
++ }
++ break;
++ }
++
++ /* Some events are global */
++ if (sfp->sm_state != SFP_S_DOWN &&
++ (sfp->sm_mod_state != SFP_MOD_PRESENT ||
++ sfp->sm_dev_state != SFP_DEV_UP)) {
++ if (sfp->sm_state == SFP_S_LINK_UP &&
++ sfp->sm_dev_state == SFP_DEV_UP)
++ sfp_sm_link_down(sfp);
++ if (sfp->mod_phy)
++ sfp_sm_phy_detach(sfp);
++ sfp_sm_next(sfp, SFP_S_DOWN, 0);
++ mutex_unlock(&sfp->sm_mutex);
++ return;
++ }
++
++ /* The main state machine */
++ switch (sfp->sm_state) {
++ case SFP_S_DOWN:
++ if (sfp->sm_mod_state == SFP_MOD_PRESENT &&
++ sfp->sm_dev_state == SFP_DEV_UP)
++ sfp_sm_mod_init(sfp);
++ break;
++
++ case SFP_S_INIT:
++ if (event == SFP_E_TIMEOUT && sfp->state & SFP_F_TX_FAULT)
++ sfp_sm_fault(sfp, true);
++ else if (event == SFP_E_TIMEOUT || event == SFP_E_TX_CLEAR)
++ sfp_sm_link_check_los(sfp);
++ break;
++
++ case SFP_S_WAIT_LOS:
++ if (event == SFP_E_TX_FAULT)
++ sfp_sm_fault(sfp, true);
++ else if (event ==
++ (sfp->id.ext.options & SFP_OPTIONS_LOS_INVERTED ?
++ SFP_E_LOS_HIGH : SFP_E_LOS_LOW))
++ sfp_sm_link_up(sfp);
++ break;
++
++ case SFP_S_LINK_UP:
++ if (event == SFP_E_TX_FAULT) {
++ sfp_sm_link_down(sfp);
++ sfp_sm_fault(sfp, true);
++ } else if (event ==
++ (sfp->id.ext.options & SFP_OPTIONS_LOS_INVERTED ?
++ SFP_E_LOS_LOW : SFP_E_LOS_HIGH)) {
++ sfp_sm_link_down(sfp);
++ sfp_sm_next(sfp, SFP_S_WAIT_LOS, 0);
++ }
++ break;
++
++ case SFP_S_TX_FAULT:
++ if (event == SFP_E_TIMEOUT) {
++ sfp_module_tx_fault_reset(sfp);
++ sfp_sm_next(sfp, SFP_S_REINIT, T_INIT_JIFFIES);
++ }
++ break;
++
++ case SFP_S_REINIT:
++ if (event == SFP_E_TIMEOUT && sfp->state & SFP_F_TX_FAULT) {
++ sfp_sm_fault(sfp, false);
++ } else if (event == SFP_E_TIMEOUT || event == SFP_E_TX_CLEAR) {
++ dev_info(sfp->dev, "module transmit fault recovered\n");
++ sfp_sm_link_check_los(sfp);
++ }
++ break;
++
++ case SFP_S_TX_DISABLE:
++ break;
++ }
++
++ dev_dbg(sfp->dev, "SM: exit %u:%u:%u\n",
++ sfp->sm_mod_state, sfp->sm_dev_state, sfp->sm_state);
++
++ mutex_unlock(&sfp->sm_mutex);
++}
++
++#if 0
++static int sfp_phy_module_info(struct phy_device *phy,
++ struct ethtool_modinfo *modinfo)
++{
++ struct sfp *sfp = phy->priv;
++
++ /* locking... and check module is present */
++
++ if (sfp->id.ext.sff8472_compliance) {
++ modinfo->type = ETH_MODULE_SFF_8472;
++ modinfo->eeprom_len = ETH_MODULE_SFF_8472_LEN;
++ } else {
++ modinfo->type = ETH_MODULE_SFF_8079;
++ modinfo->eeprom_len = ETH_MODULE_SFF_8079_LEN;
++ }
++ return 0;
++}
++
++static int sfp_phy_module_eeprom(struct phy_device *phy,
++ struct ethtool_eeprom *ee, u8 *data)
++{
++ struct sfp *sfp = phy->priv;
++ unsigned int first, last, len;
++ int ret;
++
++ if (ee->len == 0)
++ return -EINVAL;
++
++ first = ee->offset;
++ last = ee->offset + ee->len;
++ if (first < ETH_MODULE_SFF_8079_LEN) {
++ len = last;
++ if (len > ETH_MODULE_SFF_8079_LEN)
++ len = ETH_MODULE_SFF_8079_LEN;
++ len -= first;
++
++ ret = sfp->read(sfp, false, first, data, len);
++ if (ret < 0)
++ return ret;
++
++ first += len;
++ data += len;
++ }
++ if (first >= ETH_MODULE_SFF_8079_LEN && last > first) {
++ len = last - first;
++
++ ret = sfp->read(sfp, true, first, data, len);
++ if (ret < 0)
++ return ret;
++ }
++ return 0;
++}
++#endif
++
++static void sfp_timeout(struct work_struct *work)
++{
++ struct sfp *sfp = container_of(work, struct sfp, timeout.work);
++
++ sfp_sm_event(sfp, SFP_E_TIMEOUT);
++}
++
++static void sfp_check_state(struct sfp *sfp)
++{
++ unsigned int state, i, changed;
++
++ state = sfp_get_state(sfp);
++ changed = state ^ sfp->state;
++ changed &= SFP_F_PRESENT | SFP_F_LOS | SFP_F_TX_FAULT;
++
++ for (i = 0; i < GPIO_MAX; i++)
++ if (changed & BIT(i))
++ dev_dbg(sfp->dev, "%s %u -> %u\n", gpio_of_names[i],
++ !!(sfp->state & BIT(i)), !!(state & BIT(i)));
++
++ state |= sfp->state & (SFP_F_TX_DISABLE | SFP_F_RATE_SELECT);
++ sfp->state = state;
++
++ if (changed & SFP_F_PRESENT)
++ sfp_sm_event(sfp, state & SFP_F_PRESENT ?
++ SFP_E_INSERT : SFP_E_REMOVE);
++
++ if (changed & SFP_F_TX_FAULT)
++ sfp_sm_event(sfp, state & SFP_F_TX_FAULT ?
++ SFP_E_TX_FAULT : SFP_E_TX_CLEAR);
++
++ if (changed & SFP_F_LOS)
++ sfp_sm_event(sfp, state & SFP_F_LOS ?
++ SFP_E_LOS_HIGH : SFP_E_LOS_LOW);
++}
++
++static irqreturn_t sfp_irq(int irq, void *data)
++{
++ struct sfp *sfp = data;
++
++ sfp_check_state(sfp);
++
++ return IRQ_HANDLED;
++}
++
++static void sfp_poll(struct work_struct *work)
++{
++ struct sfp *sfp = container_of(work, struct sfp, poll.work);
++
++ sfp_check_state(sfp);
++ mod_delayed_work(system_wq, &sfp->poll, poll_jiffies);
++}
++
++static int sfp_netdev_notify(struct notifier_block *nb, unsigned long act, void *data)
++{
++ struct sfp *sfp = container_of(nb, struct sfp, netdev_nb);
++ struct netdev_notifier_info *info = data;
++ struct net_device *ndev = info->dev;
++
++ if (!sfp->ndev || ndev != sfp->ndev)
++ return NOTIFY_DONE;
++
++ switch (act) {
++ case NETDEV_UP:
++ sfp_sm_event(sfp, SFP_E_DEV_UP);
++ break;
++
++ case NETDEV_GOING_DOWN:
++ sfp_sm_event(sfp, SFP_E_DEV_DOWN);
++ break;
++
++ case NETDEV_UNREGISTER:
++ if (sfp->mod_phy && sfp->phylink)
++ phylink_disconnect_phy(sfp->phylink);
++ sfp->phylink = NULL;
++ dev_put(sfp->ndev);
++ sfp->ndev = NULL;
++ break;
++ }
++ return NOTIFY_OK;
++}
++
++static struct sfp *sfp_alloc(struct device *dev)
++{
++ struct sfp *sfp;
++
++ sfp = kzalloc(sizeof(*sfp), GFP_KERNEL);
++ if (!sfp)
++ return ERR_PTR(-ENOMEM);
++
++ sfp->dev = dev;
++
++ mutex_init(&sfp->sm_mutex);
++ INIT_DELAYED_WORK(&sfp->poll, sfp_poll);
++ INIT_DELAYED_WORK(&sfp->timeout, sfp_timeout);
++
++ sfp->netdev_nb.notifier_call = sfp_netdev_notify;
++
++ return sfp;
++}
++
++static void sfp_destroy(struct sfp *sfp)
++{
++ cancel_delayed_work_sync(&sfp->poll);
++ cancel_delayed_work_sync(&sfp->timeout);
++ if (sfp->i2c_mii) {
++ mdiobus_unregister(sfp->i2c_mii);
++ mdiobus_free(sfp->i2c_mii);
++ }
++ if (sfp->i2c)
++ i2c_put_adapter(sfp->i2c);
++ of_node_put(sfp->dev->of_node);
++ kfree(sfp);
++}
++
++static void sfp_cleanup(void *data)
++{
++ struct sfp *sfp = data;
++
++ sfp_destroy(sfp);
++}
++
++static int sfp_probe(struct platform_device *pdev)
++{
++ struct sfp *sfp;
++ bool poll = false;
++ int irq, err, i;
++
++ sfp = sfp_alloc(&pdev->dev);
++ if (IS_ERR(sfp))
++ return PTR_ERR(sfp);
++
++ platform_set_drvdata(pdev, sfp);
++
++ err = devm_add_action(sfp->dev, sfp_cleanup, sfp);
++ if (err < 0)
++ return err;
++
++ if (pdev->dev.of_node) {
++ struct device_node *node = pdev->dev.of_node;
++ struct device_node *np;
++
++ np = of_parse_phandle(node, "i2c-bus", 0);
++ if (np) {
++ struct i2c_adapter *i2c;
++
++ i2c = of_find_i2c_adapter_by_node(np);
++ of_node_put(np);
++ if (!i2c)
++ return -EPROBE_DEFER;
++
++ err = sfp_i2c_configure(sfp, i2c);
++ if (err < 0) {
++ i2c_put_adapter(i2c);
++ return err;
++ }
++ }
++
++ for (i = 0; i < GPIO_MAX; i++) {
++ sfp->gpio[i] = devm_gpiod_get_optional(sfp->dev,
++ gpio_of_names[i], gpio_flags[i]);
++ if (IS_ERR(sfp->gpio[i]))
++ return PTR_ERR(sfp->gpio[i]);
++ }
++
++ sfp->get_state = sfp_gpio_get_state;
++ sfp->set_state = sfp_gpio_set_state;
++
++ np = of_parse_phandle(node, "sfp,ethernet", 0);
++ if (!np) {
++ dev_err(sfp->dev, "missing sfp,ethernet property\n");
++ return -EINVAL;
++ }
++
++ sfp->ndev = of_find_net_device_by_node(np);
++ if (!sfp->ndev) {
++ dev_err(sfp->dev, "ethernet device not found\n");
++ return -EPROBE_DEFER;
++ }
++
++ dev_hold(sfp->ndev);
++ put_device(&sfp->ndev->dev);
++
++ sfp->phylink = phylink_lookup_by_netdev(sfp->ndev);
++ if (!sfp->phylink) {
++ dev_err(sfp->dev, "ethernet device not found\n");
++ return -EPROBE_DEFER;
++ }
++
++ phylink_disable(sfp->phylink);
++ }
++
++ sfp->state = sfp_get_state(sfp);
++ if (sfp->gpio[GPIO_TX_DISABLE] &&
++ gpiod_get_value_cansleep(sfp->gpio[GPIO_TX_DISABLE]))
++ sfp->state |= SFP_F_TX_DISABLE;
++ if (sfp->gpio[GPIO_RATE_SELECT] &&
++ gpiod_get_value_cansleep(sfp->gpio[GPIO_RATE_SELECT]))
++ sfp->state |= SFP_F_RATE_SELECT;
++ sfp_set_state(sfp, sfp->state);
++ sfp_module_tx_disable(sfp);
++ if (sfp->state & SFP_F_PRESENT)
++ sfp_sm_event(sfp, SFP_E_INSERT);
++
++ for (i = 0; i < GPIO_MAX; i++) {
++ if (gpio_flags[i] != GPIOD_IN || !sfp->gpio[i])
++ continue;
++
++ irq = gpiod_to_irq(sfp->gpio[i]);
++ if (!irq) {
++ poll = true;
++ continue;
++ }
++
++ err = devm_request_threaded_irq(sfp->dev, irq, NULL, sfp_irq,
++ IRQF_ONESHOT |
++ IRQF_TRIGGER_RISING |
++ IRQF_TRIGGER_FALLING,
++ dev_name(sfp->dev), sfp);
++ if (err)
++ poll = true;
++ }
++
++ if (poll)
++ mod_delayed_work(system_wq, &sfp->poll, poll_jiffies);
++
++ register_netdevice_notifier(&sfp->netdev_nb);
++
++ return 0;
++}
++
++static int sfp_remove(struct platform_device *pdev)
++{
++ struct sfp *sfp = platform_get_drvdata(pdev);
++
++ unregister_netdevice_notifier(&sfp->netdev_nb);
++ if (sfp->ndev)
++ dev_put(sfp->ndev);
++
++ return 0;
++}
++
++static const struct of_device_id sfp_of_match[] = {
++ { .compatible = "sff,sfp", },
++ { },
++};
++MODULE_DEVICE_TABLE(of, sfp_of_match);
++
++static struct platform_driver sfp_driver = {
++ .probe = sfp_probe,
++ .remove = sfp_remove,
++ .driver = {
++ .name = "sfp",
++ .of_match_table = sfp_of_match,
++ },
++};
++
++static int sfp_init(void)
++{
++ poll_jiffies = msecs_to_jiffies(100);
++
++ return platform_driver_register(&sfp_driver);
++}
++module_init(sfp_init);
++
++static void sfp_exit(void)
++{
++ platform_driver_unregister(&sfp_driver);
++}
++module_exit(sfp_exit);
++
++MODULE_ALIAS("platform:sfp");
++MODULE_AUTHOR("Russell King");
++MODULE_LICENSE("GPL v2");
+--- /dev/null
++++ b/include/linux/sfp.h
+@@ -0,0 +1,339 @@
++#ifndef LINUX_SFP_H
++#define LINUX_SFP_H
++
++struct __packed sfp_eeprom_base {
++ u8 phys_id;
++ u8 phys_ext_id;
++ u8 connector;
++#if defined __BIG_ENDIAN_BITFIELD
++ u8 e10g_base_er:1;
++ u8 e10g_base_lrm:1;
++ u8 e10g_base_lr:1;
++ u8 e10g_base_sr:1;
++ u8 if_1x_sx:1;
++ u8 if_1x_lx:1;
++ u8 if_1x_copper_active:1;
++ u8 if_1x_copper_passive:1;
++
++ u8 escon_mmf_1310_led:1;
++ u8 escon_smf_1310_laser:1;
++ u8 sonet_oc192_short_reach:1;
++ u8 sonet_reach_bit1:1;
++ u8 sonet_reach_bit2:1;
++ u8 sonet_oc48_long_reach:1;
++ u8 sonet_oc48_intermediate_reach:1;
++ u8 sonet_oc48_short_reach:1;
++
++ u8 unallocated_5_7:1;
++ u8 sonet_oc12_smf_long_reach:1;
++ u8 sonet_oc12_smf_intermediate_reach:1;
++ u8 sonet_oc12_short_reach:1;
++ u8 unallocated_5_3:1;
++ u8 sonet_oc3_smf_long_reach:1;
++ u8 sonet_oc3_smf_intermediate_reach:1;
++ u8 sonet_oc3_short_reach:1;
++
++ u8 e_base_px:1;
++ u8 e_base_bx10:1;
++ u8 e100_base_fx:1;
++ u8 e100_base_lx:1;
++ u8 e1000_base_t:1;
++ u8 e1000_base_cx:1;
++ u8 e1000_base_lx:1;
++ u8 e1000_base_sx:1;
++
++ u8 fc_ll_v:1;
++ u8 fc_ll_s:1;
++ u8 fc_ll_i:1;
++ u8 fc_ll_l:1;
++ u8 fc_ll_m:1;
++ u8 fc_tech_sa:1;
++ u8 fc_tech_lc:1;
++ u8 fc_tech_electrical_inter_enclosure:1;
++
++ u8 fc_tech_electrical_intra_enclosure:1;
++ u8 fc_tech_sn:1;
++ u8 fc_tech_sl:1;
++ u8 fc_tech_ll:1;
++ u8 sfp_ct_active:1;
++ u8 sfp_ct_passive:1;
++ u8 unallocated_8_1:1;
++ u8 unallocated_8_0:1;
++
++ u8 fc_media_tw:1;
++ u8 fc_media_tp:1;
++ u8 fc_media_mi:1;
++ u8 fc_media_tv:1;
++ u8 fc_media_m6:1;
++ u8 fc_media_m5:1;
++ u8 unallocated_9_1:1;
++ u8 fc_media_sm:1;
++
++ u8 fc_speed_1200:1;
++ u8 fc_speed_800:1;
++ u8 fc_speed_1600:1;
++ u8 fc_speed_400:1;
++ u8 fc_speed_3200:1;
++ u8 fc_speed_200:1;
++ u8 unallocated_10_1:1;
++ u8 fc_speed_100:1;
++#elif defined __LITTLE_ENDIAN_BITFIELD
++ u8 if_1x_copper_passive:1;
++ u8 if_1x_copper_active:1;
++ u8 if_1x_lx:1;
++ u8 if_1x_sx:1;
++ u8 e10g_base_sr:1;
++ u8 e10g_base_lr:1;
++ u8 e10g_base_lrm:1;
++ u8 e10g_base_er:1;
++
++ u8 sonet_oc3_short_reach:1;
++ u8 sonet_oc3_smf_intermediate_reach:1;
++ u8 sonet_oc3_smf_long_reach:1;
++ u8 unallocated_5_3:1;
++ u8 sonet_oc12_short_reach:1;
++ u8 sonet_oc12_smf_intermediate_reach:1;
++ u8 sonet_oc12_smf_long_reach:1;
++ u8 unallocated_5_7:1;
++
++ u8 sonet_oc48_short_reach:1;
++ u8 sonet_oc48_intermediate_reach:1;
++ u8 sonet_oc48_long_reach:1;
++ u8 sonet_reach_bit2:1;
++ u8 sonet_reach_bit1:1;
++ u8 sonet_oc192_short_reach:1;
++ u8 escon_smf_1310_laser:1;
++ u8 escon_mmf_1310_led:1;
++
++ u8 e1000_base_sx:1;
++ u8 e1000_base_lx:1;
++ u8 e1000_base_cx:1;
++ u8 e1000_base_t:1;
++ u8 e100_base_lx:1;
++ u8 e100_base_fx:1;
++ u8 e_base_bx10:1;
++ u8 e_base_px:1;
++
++ u8 fc_tech_electrical_inter_enclosure:1;
++ u8 fc_tech_lc:1;
++ u8 fc_tech_sa:1;
++ u8 fc_ll_m:1;
++ u8 fc_ll_l:1;
++ u8 fc_ll_i:1;
++ u8 fc_ll_s:1;
++ u8 fc_ll_v:1;
++
++ u8 unallocated_8_0:1;
++ u8 unallocated_8_1:1;
++ u8 sfp_ct_passive:1;
++ u8 sfp_ct_active:1;
++ u8 fc_tech_ll:1;
++ u8 fc_tech_sl:1;
++ u8 fc_tech_sn:1;
++ u8 fc_tech_electrical_intra_enclosure:1;
++
++ u8 fc_media_sm:1;
++ u8 unallocated_9_1:1;
++ u8 fc_media_m5:1;
++ u8 fc_media_m6:1;
++ u8 fc_media_tv:1;
++ u8 fc_media_mi:1;
++ u8 fc_media_tp:1;
++ u8 fc_media_tw:1;
++
++ u8 fc_speed_100:1;
++ u8 unallocated_10_1:1;
++ u8 fc_speed_200:1;
++ u8 fc_speed_3200:1;
++ u8 fc_speed_400:1;
++ u8 fc_speed_1600:1;
++ u8 fc_speed_800:1;
++ u8 fc_speed_1200:1;
++#else
++#error Unknown Endian
++#endif
++ u8 encoding;
++ u8 br_nominal;
++ u8 rate_id;
++ u8 link_len[6];
++ char vendor_name[16];
++ u8 reserved36;
++ char vendor_oui[3];
++ char vendor_pn[16];
++ char vendor_rev[4];
++ union {
++ __be16 optical_wavelength;
++ u8 cable_spec;
++ };
++ u8 reserved62;
++ u8 cc_base;
++};
++
++struct __packed sfp_eeprom_ext {
++ __be16 options;
++ u8 br_max;
++ u8 br_min;
++ char vendor_sn[16];
++ char datecode[8];
++ u8 diagmon;
++ u8 enhopts;
++ u8 sff8472_compliance;
++ u8 cc_ext;
++};
++
++struct __packed sfp_eeprom_id {
++ struct sfp_eeprom_base base;
++ struct sfp_eeprom_ext ext;
++};
++
++/* SFP EEPROM registers */
++enum {
++ SFP_PHYS_ID = 0x00,
++ SFP_PHYS_EXT_ID = 0x01,
++ SFP_CONNECTOR = 0x02,
++ SFP_COMPLIANCE = 0x03,
++ SFP_ENCODING = 0x0b,
++ SFP_BR_NOMINAL = 0x0c,
++ SFP_RATE_ID = 0x0d,
++ SFP_LINK_LEN_SM_KM = 0x0e,
++ SFP_LINK_LEN_SM_100M = 0x0f,
++ SFP_LINK_LEN_50UM_OM2_10M = 0x10,
++ SFP_LINK_LEN_62_5UM_OM1_10M = 0x11,
++ SFP_LINK_LEN_COPPER_1M = 0x12,
++ SFP_LINK_LEN_50UM_OM4_10M = 0x12,
++ SFP_LINK_LEN_50UM_OM3_10M = 0x13,
++ SFP_VENDOR_NAME = 0x14,
++ SFP_VENDOR_OUI = 0x25,
++ SFP_VENDOR_PN = 0x28,
++ SFP_VENDOR_REV = 0x38,
++ SFP_OPTICAL_WAVELENGTH_MSB = 0x3c,
++ SFP_OPTICAL_WAVELENGTH_LSB = 0x3d,
++ SFP_CABLE_SPEC = 0x3c,
++ SFP_CC_BASE = 0x3f,
++ SFP_OPTIONS = 0x40, /* 2 bytes, MSB, LSB */
++ SFP_BR_MAX = 0x42,
++ SFP_BR_MIN = 0x43,
++ SFP_VENDOR_SN = 0x44,
++ SFP_DATECODE = 0x54,
++ SFP_DIAGMON = 0x5c,
++ SFP_ENHOPTS = 0x5d,
++ SFP_SFF8472_COMPLIANCE = 0x5e,
++ SFP_CC_EXT = 0x5f,
++
++ SFP_PHYS_ID_SFP = 0x03,
++ SFP_PHYS_EXT_ID_SFP = 0x04,
++ SFP_CONNECTOR_UNSPEC = 0x00,
++ /* codes 01-05 not supportable on SFP, but some modules have single SC */
++ SFP_CONNECTOR_SC = 0x01,
++ SFP_CONNECTOR_FIBERJACK = 0x06,
++ SFP_CONNECTOR_LC = 0x07,
++ SFP_CONNECTOR_MT_RJ = 0x08,
++ SFP_CONNECTOR_MU = 0x09,
++ SFP_CONNECTOR_SG = 0x0a,
++ SFP_CONNECTOR_OPTICAL_PIGTAIL = 0x0b,
++ SFP_CONNECTOR_HSSDC_II = 0x20,
++ SFP_CONNECTOR_COPPER_PIGTAIL = 0x21,
++ SFP_ENCODING_UNSPEC = 0x00,
++ SFP_ENCODING_8B10B = 0x01,
++ SFP_ENCODING_4B5B = 0x02,
++ SFP_ENCODING_NRZ = 0x03,
++ SFP_ENCODING_MANCHESTER = 0x04,
++ SFP_OPTIONS_HIGH_POWER_LEVEL = BIT(13),
++ SFP_OPTIONS_PAGING_A2 = BIT(12),
++ SFP_OPTIONS_RETIMER = BIT(11),
++ SFP_OPTIONS_COOLED_XCVR = BIT(10),
++ SFP_OPTIONS_POWER_DECL = BIT(9),
++ SFP_OPTIONS_RX_LINEAR_OUT = BIT(8),
++ SFP_OPTIONS_RX_DECISION_THRESH = BIT(7),
++ SFP_OPTIONS_TUNABLE_TX = BIT(6),
++ SFP_OPTIONS_RATE_SELECT = BIT(5),
++ SFP_OPTIONS_TX_DISABLE = BIT(4),
++ SFP_OPTIONS_TX_FAULT = BIT(3),
++ SFP_OPTIONS_LOS_INVERTED = BIT(2),
++ SFP_OPTIONS_LOS_NORMAL = BIT(1),
++ SFP_DIAGMON_DDM = BIT(6),
++ SFP_DIAGMON_INT_CAL = BIT(5),
++ SFP_DIAGMON_EXT_CAL = BIT(4),
++ SFP_DIAGMON_RXPWR_AVG = BIT(3),
++ SFP_DIAGMON_ADDRMODE = BIT(2),
++ SFP_ENHOPTS_ALARMWARN = BIT(7),
++ SFP_ENHOPTS_SOFT_TX_DISABLE = BIT(6),
++ SFP_ENHOPTS_SOFT_TX_FAULT = BIT(5),
++ SFP_ENHOPTS_SOFT_RX_LOS = BIT(4),
++ SFP_ENHOPTS_SOFT_RATE_SELECT = BIT(3),
++ SFP_ENHOPTS_APP_SELECT_SFF8079 = BIT(2),
++ SFP_ENHOPTS_SOFT_RATE_SFF8431 = BIT(1),
++ SFP_SFF8472_COMPLIANCE_NONE = 0x00,
++ SFP_SFF8472_COMPLIANCE_REV9_3 = 0x01,
++ SFP_SFF8472_COMPLIANCE_REV9_5 = 0x02,
++ SFP_SFF8472_COMPLIANCE_REV10_2 = 0x03,
++ SFP_SFF8472_COMPLIANCE_REV10_4 = 0x04,
++ SFP_SFF8472_COMPLIANCE_REV11_0 = 0x05,
++ SFP_SFF8472_COMPLIANCE_REV11_3 = 0x06,
++ SFP_SFF8472_COMPLIANCE_REV11_4 = 0x07,
++ SFP_SFF8472_COMPLIANCE_REV12_0 = 0x08,
++};
++
++/* SFP Diagnostics */
++enum {
++ /* Alarm and warnings stored MSB at lower address then LSB */
++ SFP_TEMP_HIGH_ALARM = 0x00,
++ SFP_TEMP_LOW_ALARM = 0x02,
++ SFP_TEMP_HIGH_WARN = 0x04,
++ SFP_TEMP_LOW_WARN = 0x06,
++ SFP_VOLT_HIGH_ALARM = 0x08,
++ SFP_VOLT_LOW_ALARM = 0x0a,
++ SFP_VOLT_HIGH_WARN = 0x0c,
++ SFP_VOLT_LOW_WARN = 0x0e,
++ SFP_BIAS_HIGH_ALARM = 0x10,
++ SFP_BIAS_LOW_ALARM = 0x12,
++ SFP_BIAS_HIGH_WARN = 0x14,
++ SFP_BIAS_LOW_WARN = 0x16,
++ SFP_TXPWR_HIGH_ALARM = 0x18,
++ SFP_TXPWR_LOW_ALARM = 0x1a,
++ SFP_TXPWR_HIGH_WARN = 0x1c,
++ SFP_TXPWR_LOW_WARN = 0x1e,
++ SFP_RXPWR_HIGH_ALARM = 0x20,
++ SFP_RXPWR_LOW_ALARM = 0x22,
++ SFP_RXPWR_HIGH_WARN = 0x24,
++ SFP_RXPWR_LOW_WARN = 0x26,
++ SFP_LASER_TEMP_HIGH_ALARM = 0x28,
++ SFP_LASER_TEMP_LOW_ALARM = 0x2a,
++ SFP_LASER_TEMP_HIGH_WARN = 0x2c,
++ SFP_LASER_TEMP_LOW_WARN = 0x2e,
++ SFP_TEC_CUR_HIGH_ALARM = 0x30,
++ SFP_TEC_CUR_LOW_ALARM = 0x32,
++ SFP_TEC_CUR_HIGH_WARN = 0x34,
++ SFP_TEC_CUR_LOW_WARN = 0x36,
++ SFP_CAL_RXPWR4 = 0x38,
++ SFP_CAL_RXPWR3 = 0x3c,
++ SFP_CAL_RXPWR2 = 0x40,
++ SFP_CAL_RXPWR1 = 0x44,
++ SFP_CAL_RXPWR0 = 0x48,
++ SFP_CAL_TXI_SLOPE = 0x4c,
++ SFP_CAL_TXI_OFFSET = 0x4e,
++ SFP_CAL_TXPWR_SLOPE = 0x50,
++ SFP_CAL_TXPWR_OFFSET = 0x52,
++ SFP_CAL_T_SLOPE = 0x54,
++ SFP_CAL_T_OFFSET = 0x56,
++ SFP_CAL_V_SLOPE = 0x58,
++ SFP_CAL_V_OFFSET = 0x5a,
++ SFP_CHKSUM = 0x5f,
++
++ SFP_TEMP = 0x60,
++ SFP_VCC = 0x62,
++ SFP_TX_BIAS = 0x64,
++ SFP_TX_POWER = 0x66,
++ SFP_RX_POWER = 0x68,
++ SFP_LASER_TEMP = 0x6a,
++ SFP_TEC_CUR = 0x6c,
++
++ SFP_STATUS = 0x6e,
++ SFP_ALARM = 0x70,
++
++ SFP_EXT_STATUS = 0x76,
++ SFP_VSL = 0x78,
++ SFP_PAGE = 0x7f,
++};
++
++#endif
--- /dev/null
+From 66c248886538d7ee97ef2fe498061f857d4c906a Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Sun, 13 Sep 2015 01:06:31 +0100
+Subject: [PATCH 721/744] sfp: display SFP module information
+
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ drivers/net/phy/sfp.c | 247 +++++++++++++++++++++++++++++++++++++++++++++++++-
+ 1 file changed, 246 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/phy/sfp.c
++++ b/drivers/net/phy/sfp.c
+@@ -248,6 +248,182 @@ static unsigned int sfp_check(void *buf,
+ return check;
+ }
+
++static const char *sfp_link_len(char *buf, size_t size, unsigned int length,
++ unsigned int multiplier)
++{
++ if (length == 0)
++ return "unsupported/unspecified";
++
++ if (length == 255) {
++ *buf++ = '>';
++ size -= 1;
++ length -= 1;
++ }
++
++ length *= multiplier;
++
++ if (length >= 1000)
++ snprintf(buf, size, "%u.%0*ukm",
++ length / 1000,
++ multiplier > 100 ? 1 :
++ multiplier > 10 ? 2 : 3,
++ length % 1000);
++ else
++ snprintf(buf, size, "%um", length);
++
++ return buf;
++}
++
++struct bitfield {
++ unsigned int mask;
++ unsigned int val;
++ const char *str;
++};
++
++static const struct bitfield sfp_options[] = {
++ {
++ .mask = SFP_OPTIONS_HIGH_POWER_LEVEL,
++ .val = SFP_OPTIONS_HIGH_POWER_LEVEL,
++ .str = "hpl",
++ }, {
++ .mask = SFP_OPTIONS_PAGING_A2,
++ .val = SFP_OPTIONS_PAGING_A2,
++ .str = "paginga2",
++ }, {
++ .mask = SFP_OPTIONS_RETIMER,
++ .val = SFP_OPTIONS_RETIMER,
++ .str = "retimer",
++ }, {
++ .mask = SFP_OPTIONS_COOLED_XCVR,
++ .val = SFP_OPTIONS_COOLED_XCVR,
++ .str = "cooled",
++ }, {
++ .mask = SFP_OPTIONS_POWER_DECL,
++ .val = SFP_OPTIONS_POWER_DECL,
++ .str = "powerdecl",
++ }, {
++ .mask = SFP_OPTIONS_RX_LINEAR_OUT,
++ .val = SFP_OPTIONS_RX_LINEAR_OUT,
++ .str = "rxlinear",
++ }, {
++ .mask = SFP_OPTIONS_RX_DECISION_THRESH,
++ .val = SFP_OPTIONS_RX_DECISION_THRESH,
++ .str = "rxthresh",
++ }, {
++ .mask = SFP_OPTIONS_TUNABLE_TX,
++ .val = SFP_OPTIONS_TUNABLE_TX,
++ .str = "tunabletx",
++ }, {
++ .mask = SFP_OPTIONS_RATE_SELECT,
++ .val = SFP_OPTIONS_RATE_SELECT,
++ .str = "ratesel",
++ }, {
++ .mask = SFP_OPTIONS_TX_DISABLE,
++ .val = SFP_OPTIONS_TX_DISABLE,
++ .str = "txdisable",
++ }, {
++ .mask = SFP_OPTIONS_TX_FAULT,
++ .val = SFP_OPTIONS_TX_FAULT,
++ .str = "txfault",
++ }, {
++ .mask = SFP_OPTIONS_LOS_INVERTED,
++ .val = SFP_OPTIONS_LOS_INVERTED,
++ .str = "los-",
++ }, {
++ .mask = SFP_OPTIONS_LOS_NORMAL,
++ .val = SFP_OPTIONS_LOS_NORMAL,
++ .str = "los+",
++ }, { }
++};
++
++static const struct bitfield diagmon[] = {
++ {
++ .mask = SFP_DIAGMON_DDM,
++ .val = SFP_DIAGMON_DDM,
++ .str = "ddm",
++ }, {
++ .mask = SFP_DIAGMON_INT_CAL,
++ .val = SFP_DIAGMON_INT_CAL,
++ .str = "intcal",
++ }, {
++ .mask = SFP_DIAGMON_EXT_CAL,
++ .val = SFP_DIAGMON_EXT_CAL,
++ .str = "extcal",
++ }, {
++ .mask = SFP_DIAGMON_RXPWR_AVG,
++ .val = SFP_DIAGMON_RXPWR_AVG,
++ .str = "rxpwravg",
++ }, { }
++};
++
++static const char *sfp_bitfield(char *out, size_t outsz, const struct bitfield *bits, unsigned int val)
++{
++ char *p = out;
++ int n;
++
++ *p = '\0';
++ while (bits->mask) {
++ if ((val & bits->mask) == bits->val) {
++ n = snprintf(p, outsz, "%s%s",
++ out != p ? ", " : "",
++ bits->str);
++ if (n == outsz)
++ break;
++ p += n;
++ outsz -= n;
++ }
++ bits++;
++ }
++
++ return out;
++}
++
++static const char *sfp_connector(unsigned int connector)
++{
++ switch (connector) {
++ case SFP_CONNECTOR_UNSPEC:
++ return "unknown/unspecified";
++ case SFP_CONNECTOR_SC:
++ return "SC";
++ case SFP_CONNECTOR_FIBERJACK:
++ return "Fiberjack";
++ case SFP_CONNECTOR_LC:
++ return "LC";
++ case SFP_CONNECTOR_MT_RJ:
++ return "MT-RJ";
++ case SFP_CONNECTOR_MU:
++ return "MU";
++ case SFP_CONNECTOR_SG:
++ return "SG";
++ case SFP_CONNECTOR_OPTICAL_PIGTAIL:
++ return "Optical pigtail";
++ case SFP_CONNECTOR_HSSDC_II:
++ return "HSSDC II";
++ case SFP_CONNECTOR_COPPER_PIGTAIL:
++ return "Copper pigtail";
++ default:
++ return "unknown";
++ }
++}
++
++static const char *sfp_encoding(unsigned int encoding)
++{
++ switch (encoding) {
++ case SFP_ENCODING_UNSPEC:
++ return "unspecified";
++ case SFP_ENCODING_8B10B:
++ return "8b10b";
++ case SFP_ENCODING_4B5B:
++ return "4b5b";
++ case SFP_ENCODING_NRZ:
++ return "NRZ";
++ case SFP_ENCODING_MANCHESTER:
++ return "MANCHESTER";
++ default:
++ return "unknown";
++ }
++}
++
+ /* Helpers */
+ static void sfp_module_tx_disable(struct sfp *sfp)
+ {
+@@ -426,6 +602,7 @@ static int sfp_sm_mod_probe(struct sfp *
+ char sn[17];
+ char date[9];
+ char rev[5];
++ char options[80];
+ u8 check;
+ int err;
+
+@@ -462,10 +639,78 @@ static int sfp_sm_mod_probe(struct sfp *
+ rev[4] = '\0';
+ memcpy(sn, sfp->id.ext.vendor_sn, 16);
+ sn[16] = '\0';
+- memcpy(date, sfp->id.ext.datecode, 8);
++ date[0] = sfp->id.ext.datecode[4];
++ date[1] = sfp->id.ext.datecode[5];
++ date[2] = '-';
++ date[3] = sfp->id.ext.datecode[2];
++ date[4] = sfp->id.ext.datecode[3];
++ date[5] = '-';
++ date[6] = sfp->id.ext.datecode[0];
++ date[7] = sfp->id.ext.datecode[1];
+ date[8] = '\0';
+
+ dev_info(sfp->dev, "module %s %s rev %s sn %s dc %s\n", vendor, part, rev, sn, date);
++ dev_info(sfp->dev, " %s connector, encoding %s, nominal bitrate %u.%uGbps +%u%% -%u%%\n",
++ sfp_connector(sfp->id.base.connector),
++ sfp_encoding(sfp->id.base.encoding),
++ sfp->id.base.br_nominal / 10,
++ sfp->id.base.br_nominal % 10,
++ sfp->id.ext.br_max, sfp->id.ext.br_min);
++ dev_info(sfp->dev, " 1000BaseSX%c 1000BaseLX%c 1000BaseCX%c 1000BaseT%c 100BaseTLX%c 1000BaseFX%c BaseBX10%c BasePX%c\n",
++ sfp->id.base.e1000_base_sx ? '+' : '-',
++ sfp->id.base.e1000_base_lx ? '+' : '-',
++ sfp->id.base.e1000_base_cx ? '+' : '-',
++ sfp->id.base.e1000_base_t ? '+' : '-',
++ sfp->id.base.e100_base_lx ? '+' : '-',
++ sfp->id.base.e100_base_fx ? '+' : '-',
++ sfp->id.base.e_base_bx10 ? '+' : '-',
++ sfp->id.base.e_base_px ? '+' : '-');
++
++ if (!sfp->id.base.sfp_ct_passive && !sfp->id.base.sfp_ct_active &&
++ !sfp->id.base.e1000_base_t) {
++ char len_9um[16], len_om[16];
++
++ dev_info(sfp->dev, " Wavelength %unm, fiber lengths:\n",
++ be16_to_cpup(&sfp->id.base.optical_wavelength));
++
++ if (sfp->id.base.link_len[0] == 255)
++ strcpy(len_9um, ">254km");
++ else if (sfp->id.base.link_len[1] && sfp->id.base.link_len[1] != 255)
++ sprintf(len_9um, "%um",
++ sfp->id.base.link_len[1] * 100);
++ else if (sfp->id.base.link_len[0])
++ sprintf(len_9um, "%ukm", sfp->id.base.link_len[0]);
++ else if (sfp->id.base.link_len[1] == 255)
++ strcpy(len_9um, ">25.4km");
++ else
++ strcpy(len_9um, "unsupported");
++
++ dev_info(sfp->dev, " 9µm SM : %s\n", len_9um);
++ dev_info(sfp->dev, " 62.5µm MM OM1: %s\n",
++ sfp_link_len(len_om, sizeof(len_om),
++ sfp->id.base.link_len[3], 10));
++ dev_info(sfp->dev, " 50µm MM OM2: %s\n",
++ sfp_link_len(len_om, sizeof(len_om),
++ sfp->id.base.link_len[2], 10));
++ dev_info(sfp->dev, " 50µm MM OM3: %s\n",
++ sfp_link_len(len_om, sizeof(len_om),
++ sfp->id.base.link_len[5], 10));
++ dev_info(sfp->dev, " 50µm MM OM4: %s\n",
++ sfp_link_len(len_om, sizeof(len_om),
++ sfp->id.base.link_len[4], 10));
++ } else {
++ char len[16];
++ dev_info(sfp->dev, " Copper length: %s\n",
++ sfp_link_len(len, sizeof(len),
++ sfp->id.base.link_len[4], 1));
++ }
++
++ dev_info(sfp->dev, " Options: %s\n",
++ sfp_bitfield(options, sizeof(options), sfp_options,
++ be16_to_cpu(sfp->id.ext.options)));
++ dev_info(sfp->dev, " Diagnostics: %s\n",
++ sfp_bitfield(options, sizeof(options), diagmon,
++ sfp->id.ext.diagmon));
+
+ /* We only support SFP modules, not the legacy GBIC modules. */
+ if (sfp->id.base.phys_id != SFP_PHYS_ID_SFP ||
--- /dev/null
+From e268be0ddc666f4a98db462cbed2a97637e82b5c Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Wed, 16 Sep 2015 21:27:10 +0100
+Subject: [PATCH 722/744] net: mvneta: convert to phylink
+
+Convert mvneta to use phylink, which models the MAC to PHY link in
+a generic, reusable form.
+
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ drivers/net/ethernet/marvell/Kconfig | 2 +-
+ drivers/net/ethernet/marvell/mvneta.c | 451 +++++++++++++++++-----------------
+ 2 files changed, 227 insertions(+), 226 deletions(-)
+
+--- a/drivers/net/ethernet/marvell/Kconfig
++++ b/drivers/net/ethernet/marvell/Kconfig
+@@ -58,7 +58,7 @@ config MVNETA
+ tristate "Marvell Armada 370/38x/XP network interface support"
+ depends on PLAT_ORION
+ select MVMDIO
+- select FIXED_PHY
++ select PHYLINK
+ ---help---
+ This driver supports the network interface units in the
+ Marvell ARMADA XP, ARMADA 370 and ARMADA 38x SoC family.
+--- a/drivers/net/ethernet/marvell/mvneta.c
++++ b/drivers/net/ethernet/marvell/mvneta.c
+@@ -28,6 +28,7 @@
+ #include <linux/of_mdio.h>
+ #include <linux/of_net.h>
+ #include <linux/phy.h>
++#include <linux/phylink.h>
+ #include <linux/platform_device.h>
+ #include <linux/skbuff.h>
+ #include <net/hwbm.h>
+@@ -188,6 +189,7 @@
+ #define MVNETA_GMAC_CTRL_0 0x2c00
+ #define MVNETA_GMAC_MAX_RX_SIZE_SHIFT 2
+ #define MVNETA_GMAC_MAX_RX_SIZE_MASK 0x7ffc
++#define MVNETA_GMAC0_PORT_1000BASE_X BIT(1)
+ #define MVNETA_GMAC0_PORT_ENABLE BIT(0)
+ #define MVNETA_GMAC_CTRL_2 0x2c08
+ #define MVNETA_GMAC2_INBAND_AN_ENABLE BIT(0)
+@@ -203,13 +205,19 @@
+ #define MVNETA_GMAC_TX_FLOW_CTRL_ENABLE BIT(5)
+ #define MVNETA_GMAC_RX_FLOW_CTRL_ACTIVE BIT(6)
+ #define MVNETA_GMAC_TX_FLOW_CTRL_ACTIVE BIT(7)
++#define MVNETA_GMAC_AN_COMPLETE BIT(11)
++#define MVNETA_GMAC_SYNC_OK BIT(14)
+ #define MVNETA_GMAC_AUTONEG_CONFIG 0x2c0c
+ #define MVNETA_GMAC_FORCE_LINK_DOWN BIT(0)
+ #define MVNETA_GMAC_FORCE_LINK_PASS BIT(1)
+ #define MVNETA_GMAC_INBAND_AN_ENABLE BIT(2)
++#define MVNETA_GMAC_AN_BYPASS_ENABLE BIT(3)
++#define MVNETA_GMAC_INBAND_RESTART_AN BIT(4)
+ #define MVNETA_GMAC_CONFIG_MII_SPEED BIT(5)
+ #define MVNETA_GMAC_CONFIG_GMII_SPEED BIT(6)
+ #define MVNETA_GMAC_AN_SPEED_EN BIT(7)
++#define MVNETA_GMAC_CONFIG_FLOW_CTRL BIT(8)
++#define MVNETA_GMAC_ADVERT_SYM_FLOW_CTRL BIT(9)
+ #define MVNETA_GMAC_AN_FLOW_CTRL_EN BIT(11)
+ #define MVNETA_GMAC_CONFIG_FULL_DUPLEX BIT(12)
+ #define MVNETA_GMAC_AN_DUPLEX_EN BIT(13)
+@@ -396,15 +404,9 @@ struct mvneta_port {
+ u16 tx_ring_size;
+ u16 rx_ring_size;
+
+- struct mii_bus *mii_bus;
+- struct phy_device *phy_dev;
+- phy_interface_t phy_interface;
+- struct device_node *phy_node;
+- unsigned int link;
+- unsigned int duplex;
+- unsigned int speed;
++ struct device_node *dn;
+ unsigned int tx_csum_limit;
+- unsigned int use_inband_status:1;
++ struct phylink *phylink;
+
+ struct mvneta_bm *bm_priv;
+ struct mvneta_bm_pool *pool_long;
+@@ -1236,44 +1238,6 @@ static void mvneta_set_other_mcast_table
+ mvreg_write(pp, MVNETA_DA_FILT_OTH_MCAST + offset, val);
+ }
+
+-static void mvneta_set_autoneg(struct mvneta_port *pp, int enable)
+-{
+- u32 val;
+-
+- if (enable) {
+- val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG);
+- val &= ~(MVNETA_GMAC_FORCE_LINK_PASS |
+- MVNETA_GMAC_FORCE_LINK_DOWN |
+- MVNETA_GMAC_AN_FLOW_CTRL_EN);
+- val |= MVNETA_GMAC_INBAND_AN_ENABLE |
+- MVNETA_GMAC_AN_SPEED_EN |
+- MVNETA_GMAC_AN_DUPLEX_EN;
+- mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val);
+-
+- val = mvreg_read(pp, MVNETA_GMAC_CLOCK_DIVIDER);
+- val |= MVNETA_GMAC_1MS_CLOCK_ENABLE;
+- mvreg_write(pp, MVNETA_GMAC_CLOCK_DIVIDER, val);
+-
+- val = mvreg_read(pp, MVNETA_GMAC_CTRL_2);
+- val |= MVNETA_GMAC2_INBAND_AN_ENABLE;
+- mvreg_write(pp, MVNETA_GMAC_CTRL_2, val);
+- } else {
+- val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG);
+- val &= ~(MVNETA_GMAC_INBAND_AN_ENABLE |
+- MVNETA_GMAC_AN_SPEED_EN |
+- MVNETA_GMAC_AN_DUPLEX_EN);
+- mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val);
+-
+- val = mvreg_read(pp, MVNETA_GMAC_CLOCK_DIVIDER);
+- val &= ~MVNETA_GMAC_1MS_CLOCK_ENABLE;
+- mvreg_write(pp, MVNETA_GMAC_CLOCK_DIVIDER, val);
+-
+- val = mvreg_read(pp, MVNETA_GMAC_CTRL_2);
+- val &= ~MVNETA_GMAC2_INBAND_AN_ENABLE;
+- mvreg_write(pp, MVNETA_GMAC_CTRL_2, val);
+- }
+-}
+-
+ static void mvneta_percpu_unmask_interrupt(void *arg)
+ {
+ struct mvneta_port *pp = arg;
+@@ -1421,7 +1385,6 @@ static void mvneta_defaults_set(struct m
+ val &= ~MVNETA_PHY_POLLING_ENABLE;
+ mvreg_write(pp, MVNETA_UNIT_CONTROL, val);
+
+- mvneta_set_autoneg(pp, pp->use_inband_status);
+ mvneta_set_ucast_table(pp, -1);
+ mvneta_set_special_mcast_table(pp, -1);
+ mvneta_set_other_mcast_table(pp, -1);
+@@ -2614,26 +2577,11 @@ static irqreturn_t mvneta_isr(int irq, v
+ return IRQ_HANDLED;
+ }
+
+-static int mvneta_fixed_link_update(struct mvneta_port *pp,
+- struct phy_device *phy)
++static void mvneta_link_change(struct mvneta_port *pp)
+ {
+- struct fixed_phy_status status;
+- struct fixed_phy_status changed = {};
+ u32 gmac_stat = mvreg_read(pp, MVNETA_GMAC_STATUS);
+
+- status.link = !!(gmac_stat & MVNETA_GMAC_LINK_UP);
+- if (gmac_stat & MVNETA_GMAC_SPEED_1000)
+- status.speed = SPEED_1000;
+- else if (gmac_stat & MVNETA_GMAC_SPEED_100)
+- status.speed = SPEED_100;
+- else
+- status.speed = SPEED_10;
+- status.duplex = !!(gmac_stat & MVNETA_GMAC_FULL_DUPLEX);
+- changed.link = 1;
+- changed.speed = 1;
+- changed.duplex = 1;
+- fixed_phy_update_state(phy, &status, &changed);
+- return 0;
++ phylink_mac_change(pp->phylink, !!(gmac_stat & MVNETA_GMAC_LINK_UP));
+ }
+
+ /* NAPI handler
+@@ -2662,12 +2610,11 @@ static int mvneta_poll(struct napi_struc
+ u32 cause_misc = mvreg_read(pp, MVNETA_INTR_MISC_CAUSE);
+
+ mvreg_write(pp, MVNETA_INTR_MISC_CAUSE, 0);
+- if (pp->use_inband_status && (cause_misc &
+- (MVNETA_CAUSE_PHY_STATUS_CHANGE |
+- MVNETA_CAUSE_LINK_CHANGE |
+- MVNETA_CAUSE_PSC_SYNC_CHANGE))) {
+- mvneta_fixed_link_update(pp, pp->phy_dev);
+- }
++
++ if (cause_misc & (MVNETA_CAUSE_PHY_STATUS_CHANGE |
++ MVNETA_CAUSE_LINK_CHANGE |
++ MVNETA_CAUSE_PSC_SYNC_CHANGE))
++ mvneta_link_change(pp);
+ }
+
+ /* Release Tx descriptors */
+@@ -2983,7 +2930,7 @@ static void mvneta_start_dev(struct mvne
+ MVNETA_CAUSE_LINK_CHANGE |
+ MVNETA_CAUSE_PSC_SYNC_CHANGE);
+
+- phy_start(pp->phy_dev);
++ phylink_start(pp->phylink);
+ netif_tx_start_all_queues(pp->dev);
+ }
+
+@@ -2991,7 +2938,7 @@ static void mvneta_stop_dev(struct mvnet
+ {
+ unsigned int cpu;
+
+- phy_stop(pp->phy_dev);
++ phylink_stop(pp->phylink);
+
+ for_each_online_cpu(cpu) {
+ struct mvneta_pcpu_port *port = per_cpu_ptr(pp->ports, cpu);
+@@ -3161,99 +3108,219 @@ static int mvneta_set_mac_addr(struct ne
+ return 0;
+ }
+
+-static void mvneta_adjust_link(struct net_device *ndev)
++static int mvneta_mac_support(struct net_device *ndev, unsigned int mode,
++ struct phylink_link_state *state)
++{
++ switch (mode) {
++ case MLO_AN_8023Z:
++ state->supported = SUPPORTED_1000baseT_Full |
++ SUPPORTED_Autoneg | SUPPORTED_Pause;
++ state->advertising = ADVERTISED_1000baseT_Full |
++ ADVERTISED_Autoneg | ADVERTISED_Pause;
++ state->an_enabled = 1;
++ break;
++
++ case MLO_AN_FIXED:
++ break;
++
++ default:
++ state->supported = PHY_10BT_FEATURES |
++ PHY_100BT_FEATURES |
++ SUPPORTED_1000baseT_Full |
++ SUPPORTED_Autoneg;
++ state->advertising = ADVERTISED_10baseT_Half |
++ ADVERTISED_10baseT_Full |
++ ADVERTISED_100baseT_Half |
++ ADVERTISED_100baseT_Full |
++ ADVERTISED_1000baseT_Full |
++ ADVERTISED_Autoneg;
++ state->an_enabled = 1;
++ break;
++ }
++ return 0;
++}
++
++static int mvneta_mac_link_state(struct net_device *ndev,
++ struct phylink_link_state *state)
+ {
+ struct mvneta_port *pp = netdev_priv(ndev);
+- struct phy_device *phydev = pp->phy_dev;
+- int status_change = 0;
++ u32 gmac_stat;
+
+- if (phydev->link) {
+- if ((pp->speed != phydev->speed) ||
+- (pp->duplex != phydev->duplex)) {
+- u32 val;
+-
+- val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG);
+- val &= ~(MVNETA_GMAC_CONFIG_MII_SPEED |
+- MVNETA_GMAC_CONFIG_GMII_SPEED |
+- MVNETA_GMAC_CONFIG_FULL_DUPLEX);
+-
+- if (phydev->duplex)
+- val |= MVNETA_GMAC_CONFIG_FULL_DUPLEX;
+-
+- if (phydev->speed == SPEED_1000)
+- val |= MVNETA_GMAC_CONFIG_GMII_SPEED;
+- else if (phydev->speed == SPEED_100)
+- val |= MVNETA_GMAC_CONFIG_MII_SPEED;
++ gmac_stat = mvreg_read(pp, MVNETA_GMAC_STATUS);
+
+- mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val);
++ if (gmac_stat & MVNETA_GMAC_SPEED_1000)
++ state->speed = SPEED_1000;
++ else if (gmac_stat & MVNETA_GMAC_SPEED_100)
++ state->speed = SPEED_100;
++ else
++ state->speed = SPEED_10;
+
+- pp->duplex = phydev->duplex;
+- pp->speed = phydev->speed;
+- }
++ state->an_complete = !!(gmac_stat & MVNETA_GMAC_AN_COMPLETE);
++ state->sync = !!(gmac_stat & MVNETA_GMAC_SYNC_OK);
++ state->link = !!(gmac_stat & MVNETA_GMAC_LINK_UP);
++ state->duplex = !!(gmac_stat & MVNETA_GMAC_FULL_DUPLEX);
++
++ state->pause = 0;
++ if (gmac_stat & MVNETA_GMAC_RX_FLOW_CTRL_ENABLE)
++ state->pause |= MLO_PAUSE_RX;
++ if (gmac_stat & MVNETA_GMAC_TX_FLOW_CTRL_ENABLE)
++ state->pause |= MLO_PAUSE_TX;
++
++ return 1;
++}
++
++static void mvneta_mac_an_restart(struct net_device *ndev, unsigned int mode)
++{
++ struct mvneta_port *pp = netdev_priv(ndev);
++
++ if (mode == MLO_AN_8023Z) {
++ u32 gmac_an = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG);
++
++ mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG,
++ gmac_an | MVNETA_GMAC_INBAND_RESTART_AN);
++ mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG,
++ gmac_an & ~MVNETA_GMAC_INBAND_RESTART_AN);
+ }
++}
+
+- if (phydev->link != pp->link) {
+- if (!phydev->link) {
+- pp->duplex = -1;
+- pp->speed = 0;
+- }
++static void mvneta_mac_config(struct net_device *ndev, unsigned int mode,
++ const struct phylink_link_state *state)
++{
++ struct mvneta_port *pp = netdev_priv(ndev);
++ u32 new_ctrl0, gmac_ctrl0 = mvreg_read(pp, MVNETA_GMAC_CTRL_0);
++ u32 new_ctrl2, gmac_ctrl2 = mvreg_read(pp, MVNETA_GMAC_CTRL_2);
++ u32 new_clk, gmac_clk = mvreg_read(pp, MVNETA_GMAC_CLOCK_DIVIDER);
++ u32 new_an, gmac_an = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG);
++
++ new_ctrl0 = gmac_ctrl0 & ~MVNETA_GMAC0_PORT_1000BASE_X;
++ new_ctrl2 = gmac_ctrl2 & ~MVNETA_GMAC2_INBAND_AN_ENABLE;
++ new_clk = gmac_clk & ~MVNETA_GMAC_1MS_CLOCK_ENABLE;
++ new_an = gmac_an & ~(MVNETA_GMAC_INBAND_AN_ENABLE |
++ MVNETA_GMAC_INBAND_RESTART_AN |
++ MVNETA_GMAC_CONFIG_MII_SPEED |
++ MVNETA_GMAC_CONFIG_GMII_SPEED |
++ MVNETA_GMAC_AN_SPEED_EN |
++ MVNETA_GMAC_ADVERT_SYM_FLOW_CTRL |
++ MVNETA_GMAC_CONFIG_FLOW_CTRL |
++ MVNETA_GMAC_AN_FLOW_CTRL_EN |
++ MVNETA_GMAC_CONFIG_FULL_DUPLEX |
++ MVNETA_GMAC_AN_DUPLEX_EN);
++
++ if (state->advertising & ADVERTISED_Pause)
++ new_an |= MVNETA_GMAC_ADVERT_SYM_FLOW_CTRL;
++
++ switch (mode) {
++ case MLO_AN_SGMII:
++ /* SGMII mode receives the state from the PHY */
++ new_ctrl2 |= MVNETA_GMAC2_INBAND_AN_ENABLE;
++ new_clk |= MVNETA_GMAC_1MS_CLOCK_ENABLE;
++ new_an = (new_an & ~(MVNETA_GMAC_FORCE_LINK_DOWN |
++ MVNETA_GMAC_FORCE_LINK_PASS)) |
++ MVNETA_GMAC_INBAND_AN_ENABLE |
++ MVNETA_GMAC_AN_SPEED_EN |
++ MVNETA_GMAC_AN_DUPLEX_EN;
++ break;
++
++ case MLO_AN_8023Z:
++ /* 802.3z negotiation - only 1000base-X */
++ new_ctrl0 |= MVNETA_GMAC0_PORT_1000BASE_X;
++ new_clk |= MVNETA_GMAC_1MS_CLOCK_ENABLE;
++ new_an = (new_an & ~(MVNETA_GMAC_FORCE_LINK_DOWN |
++ MVNETA_GMAC_FORCE_LINK_PASS)) |
++ MVNETA_GMAC_INBAND_AN_ENABLE |
++ MVNETA_GMAC_CONFIG_GMII_SPEED |
++ /* The MAC only supports FD mode */
++ MVNETA_GMAC_CONFIG_FULL_DUPLEX;
++
++ if (state->an_enabled)
++ new_an |= MVNETA_GMAC_AN_FLOW_CTRL_EN;
++ break;
+
+- pp->link = phydev->link;
+- status_change = 1;
++ default:
++ /* Phy or fixed speed */
++ if (state->duplex)
++ new_an |= MVNETA_GMAC_CONFIG_FULL_DUPLEX;
++
++ if (state->speed == SPEED_1000)
++ new_an |= MVNETA_GMAC_CONFIG_GMII_SPEED;
++ else if (state->speed == SPEED_100)
++ new_an |= MVNETA_GMAC_CONFIG_MII_SPEED;
++ break;
+ }
+
+- if (status_change) {
+- if (phydev->link) {
+- if (!pp->use_inband_status) {
+- u32 val = mvreg_read(pp,
+- MVNETA_GMAC_AUTONEG_CONFIG);
+- val &= ~MVNETA_GMAC_FORCE_LINK_DOWN;
+- val |= MVNETA_GMAC_FORCE_LINK_PASS;
+- mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG,
+- val);
+- }
+- mvneta_port_up(pp);
+- } else {
+- if (!pp->use_inband_status) {
+- u32 val = mvreg_read(pp,
+- MVNETA_GMAC_AUTONEG_CONFIG);
+- val &= ~MVNETA_GMAC_FORCE_LINK_PASS;
+- val |= MVNETA_GMAC_FORCE_LINK_DOWN;
+- mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG,
+- val);
+- }
+- mvneta_port_down(pp);
+- }
+- phy_print_status(phydev);
++ /* Armada 370 documentation says we can only change the port mode
++ * and in-band enable when the link is down, so force it down
++ * while making these changes. We also do this for GMAC_CTRL2 */
++ if ((new_ctrl0 ^ gmac_ctrl0) & MVNETA_GMAC0_PORT_1000BASE_X ||
++ (new_ctrl2 ^ gmac_ctrl2) & MVNETA_GMAC2_INBAND_AN_ENABLE ||
++ (new_an ^ gmac_an) & MVNETA_GMAC_INBAND_AN_ENABLE) {
++ mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG,
++ (gmac_an & ~MVNETA_GMAC_FORCE_LINK_PASS) |
++ MVNETA_GMAC_FORCE_LINK_DOWN);
++ }
++
++ if (new_ctrl0 != gmac_ctrl0)
++ mvreg_write(pp, MVNETA_GMAC_CTRL_0, new_ctrl0);
++ if (new_ctrl2 != gmac_ctrl2)
++ mvreg_write(pp, MVNETA_GMAC_CTRL_2, new_ctrl2);
++ if (new_clk != gmac_clk)
++ mvreg_write(pp, MVNETA_GMAC_CLOCK_DIVIDER, new_clk);
++ if (new_an != gmac_an)
++ mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, new_an);
++}
++
++static void mvneta_mac_link_down(struct net_device *ndev, unsigned int mode)
++{
++ struct mvneta_port *pp = netdev_priv(ndev);
++ u32 val;
++
++ mvneta_port_down(pp);
++
++ if (mode == MLO_AN_PHY || mode == MLO_AN_FIXED) {
++ val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG);
++ val &= ~MVNETA_GMAC_FORCE_LINK_PASS;
++ val |= MVNETA_GMAC_FORCE_LINK_DOWN;
++ mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val);
+ }
+ }
+
+-static int mvneta_mdio_probe(struct mvneta_port *pp)
++static void mvneta_mac_link_up(struct net_device *ndev, unsigned int mode,
++ struct phy_device *phy)
+ {
+- struct phy_device *phy_dev;
++ struct mvneta_port *pp = netdev_priv(ndev);
++ u32 val;
++
++ if (mode == MLO_AN_PHY || mode == MLO_AN_FIXED) {
++ val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG);
++ val &= ~MVNETA_GMAC_FORCE_LINK_DOWN;
++ val |= MVNETA_GMAC_FORCE_LINK_PASS;
++ mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val);
++ }
+
+- phy_dev = of_phy_connect(pp->dev, pp->phy_node, mvneta_adjust_link, 0,
+- pp->phy_interface);
+- if (!phy_dev) {
+- netdev_err(pp->dev, "could not find the PHY\n");
+- return -ENODEV;
+- }
+-
+- phy_dev->supported &= PHY_GBIT_FEATURES;
+- phy_dev->advertising = phy_dev->supported;
+-
+- pp->phy_dev = phy_dev;
+- pp->link = 0;
+- pp->duplex = 0;
+- pp->speed = 0;
++ mvneta_port_up(pp);
++}
+
+- return 0;
++static const struct phylink_mac_ops mvneta_phylink_ops = {
++ .mac_get_support = mvneta_mac_support,
++ .mac_link_state = mvneta_mac_link_state,
++ .mac_an_restart = mvneta_mac_an_restart,
++ .mac_config = mvneta_mac_config,
++ .mac_link_down = mvneta_mac_link_down,
++ .mac_link_up = mvneta_mac_link_up,
++};
++
++static int mvneta_mdio_probe(struct mvneta_port *pp)
++{
++ int err = phylink_of_phy_connect(pp->phylink, pp->dn);
++ if (err)
++ netdev_err(pp->dev, "could not attach PHY\n");
++
++ return err;
+ }
+
+ static void mvneta_mdio_remove(struct mvneta_port *pp)
+ {
+- phy_disconnect(pp->phy_dev);
+- pp->phy_dev = NULL;
++ phylink_disconnect_phy(pp->phylink);
+ }
+
+ /* Electing a CPU must be done in an atomic way: it should be done
+@@ -3501,10 +3568,7 @@ static int mvneta_ioctl(struct net_devic
+ {
+ struct mvneta_port *pp = netdev_priv(dev);
+
+- if (!pp->phy_dev)
+- return -ENOTSUPP;
+-
+- return phy_mii_ioctl(pp->phy_dev, ifr, cmd);
++ return phylink_mii_ioctl(pp->phylink, ifr, cmd);
+ }
+
+ /* Ethtool methods */
+@@ -3514,54 +3578,15 @@ int mvneta_ethtool_get_settings(struct n
+ {
+ struct mvneta_port *pp = netdev_priv(dev);
+
+- if (!pp->phy_dev)
+- return -ENODEV;
+-
+- return phy_ethtool_gset(pp->phy_dev, cmd);
++ return phylink_ethtool_get_settings(pp->phylink, cmd);
+ }
+
+ /* Set settings (phy address, speed) for ethtools */
+ int mvneta_ethtool_set_settings(struct net_device *dev, struct ethtool_cmd *cmd)
+ {
+ struct mvneta_port *pp = netdev_priv(dev);
+- struct phy_device *phydev = pp->phy_dev;
+-
+- if (!phydev)
+- return -ENODEV;
+
+- if ((cmd->autoneg == AUTONEG_ENABLE) != pp->use_inband_status) {
+- u32 val;
+-
+- mvneta_set_autoneg(pp, cmd->autoneg == AUTONEG_ENABLE);
+-
+- if (cmd->autoneg == AUTONEG_DISABLE) {
+- val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG);
+- val &= ~(MVNETA_GMAC_CONFIG_MII_SPEED |
+- MVNETA_GMAC_CONFIG_GMII_SPEED |
+- MVNETA_GMAC_CONFIG_FULL_DUPLEX);
+-
+- if (phydev->duplex)
+- val |= MVNETA_GMAC_CONFIG_FULL_DUPLEX;
+-
+- if (phydev->speed == SPEED_1000)
+- val |= MVNETA_GMAC_CONFIG_GMII_SPEED;
+- else if (phydev->speed == SPEED_100)
+- val |= MVNETA_GMAC_CONFIG_MII_SPEED;
+-
+- mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val);
+- }
+-
+- pp->use_inband_status = (cmd->autoneg == AUTONEG_ENABLE);
+- netdev_info(pp->dev, "autoneg status set to %i\n",
+- pp->use_inband_status);
+-
+- if (netif_running(dev)) {
+- mvneta_port_down(pp);
+- mvneta_port_up(pp);
+- }
+- }
+-
+- return phy_ethtool_sset(pp->phy_dev, cmd);
++ return phylink_ethtool_set_settings(pp->phylink, cmd);
+ }
+
+ /* Set interrupt coalescing for ethtools */
+@@ -3669,7 +3694,8 @@ static void mvneta_ethtool_update_stats(
+ {
+ const struct mvneta_statistic *s;
+ void __iomem *base = pp->base;
+- u32 high, low, val;
++ u32 high, low;
++ u64 val;
+ u64 val64;
+ int i;
+
+@@ -3964,14 +3990,13 @@ static int mvneta_probe(struct platform_
+ const struct mbus_dram_target_info *dram_target_info;
+ struct resource *res;
+ struct device_node *dn = pdev->dev.of_node;
+- struct device_node *phy_node;
+ struct device_node *bm_node;
+ struct mvneta_port *pp;
+ struct net_device *dev;
++ struct phylink *phylink;
+ const char *dt_mac_addr;
+ char hw_mac_addr[ETH_ALEN];
+ const char *mac_from;
+- const char *managed;
+ int tx_csum_limit;
+ int phy_mode;
+ int err;
+@@ -3987,31 +4012,11 @@ static int mvneta_probe(struct platform_
+ goto err_free_netdev;
+ }
+
+- phy_node = of_parse_phandle(dn, "phy", 0);
+- if (!phy_node) {
+- if (!of_phy_is_fixed_link(dn)) {
+- dev_err(&pdev->dev, "no PHY specified\n");
+- err = -ENODEV;
+- goto err_free_irq;
+- }
+-
+- err = of_phy_register_fixed_link(dn);
+- if (err < 0) {
+- dev_err(&pdev->dev, "cannot register fixed PHY\n");
+- goto err_free_irq;
+- }
+-
+- /* In the case of a fixed PHY, the DT node associated
+- * to the PHY is the Ethernet MAC DT node.
+- */
+- phy_node = of_node_get(dn);
+- }
+-
+ phy_mode = of_get_phy_mode(dn);
+ if (phy_mode < 0) {
+ dev_err(&pdev->dev, "incorrect phy-mode\n");
+ err = -EINVAL;
+- goto err_put_phy_node;
++ goto err_free_irq;
+ }
+
+ dev->tx_queue_len = MVNETA_MAX_TXD;
+@@ -4022,12 +4027,7 @@ static int mvneta_probe(struct platform_
+
+ pp = netdev_priv(dev);
+ spin_lock_init(&pp->lock);
+- pp->phy_node = phy_node;
+- pp->phy_interface = phy_mode;
+-
+- err = of_property_read_string(dn, "managed", &managed);
+- pp->use_inband_status = (err == 0 &&
+- strcmp(managed, "in-band-status") == 0);
++ pp->dn = dn;
+ pp->cpu_notifier.notifier_call = mvneta_percpu_notifier;
+
+ pp->rxq_def = rxq_def;
+@@ -4037,7 +4037,7 @@ static int mvneta_probe(struct platform_
+ pp->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(pp->clk)) {
+ err = PTR_ERR(pp->clk);
+- goto err_put_phy_node;
++ goto err_free_irq;
+ }
+
+ clk_prepare_enable(pp->clk);
+@@ -4140,6 +4140,14 @@ static int mvneta_probe(struct platform_
+ dev->priv_flags |= IFF_UNICAST_FLT | IFF_LIVE_ADDR_CHANGE;
+ dev->gso_max_segs = MVNETA_MAX_TSO_SEGS;
+
++ phylink = phylink_create(dev, dn, phy_mode, &mvneta_phylink_ops);
++ if (IS_ERR(phylink)) {
++ err = PTR_ERR(phylink);
++ goto err_free_stats;
++ }
++
++ pp->phylink = phylink;
++
+ err = register_netdev(dev);
+ if (err < 0) {
+ dev_err(&pdev->dev, "failed to register\n");
+@@ -4151,13 +4159,6 @@ static int mvneta_probe(struct platform_
+
+ platform_set_drvdata(pdev, pp->dev);
+
+- if (pp->use_inband_status) {
+- struct phy_device *phy = of_phy_find_device(dn);
+-
+- mvneta_fixed_link_update(pp, phy);
+-
+- put_device(&phy->dev);
+- }
+
+ return 0;
+
+@@ -4169,13 +4170,13 @@ err_netdev:
+ 1 << pp->id);
+ }
+ err_free_stats:
++ if (pp->phylink)
++ phylink_destroy(pp->phylink);
+ free_percpu(pp->stats);
+ err_free_ports:
+ free_percpu(pp->ports);
+ err_clk:
+ clk_disable_unprepare(pp->clk);
+-err_put_phy_node:
+- of_node_put(phy_node);
+ err_free_irq:
+ irq_dispose_mapping(dev->irq);
+ err_free_netdev:
+@@ -4194,7 +4195,7 @@ static int mvneta_remove(struct platform
+ free_percpu(pp->ports);
+ free_percpu(pp->stats);
+ irq_dispose_mapping(dev->irq);
+- of_node_put(pp->phy_node);
++ phylink_destroy(pp->phylink);
+ free_netdev(dev);
+
+ if (pp->bm_priv) {
--- /dev/null
+From 9be436bdb67c1f4aa9f33f2477f94e1f58a0ff02 Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Fri, 2 Oct 2015 22:46:54 +0100
+Subject: [PATCH 723/744] phy: fixed-phy: remove fixed_phy_update_state()
+
+mvneta is the only user of fixed_phy_update_state(), which has been
+converted to use phylink instead. Remove fixed_phy_update_state().
+
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ drivers/net/phy/fixed_phy.c | 31 -------------------------------
+ include/linux/phy_fixed.h | 9 ---------
+ 2 files changed, 40 deletions(-)
+
+--- a/drivers/net/phy/fixed_phy.c
++++ b/drivers/net/phy/fixed_phy.c
+@@ -115,37 +115,6 @@ int fixed_phy_set_link_update(struct phy
+ }
+ EXPORT_SYMBOL_GPL(fixed_phy_set_link_update);
+
+-int fixed_phy_update_state(struct phy_device *phydev,
+- const struct fixed_phy_status *status,
+- const struct fixed_phy_status *changed)
+-{
+- struct fixed_mdio_bus *fmb = &platform_fmb;
+- struct fixed_phy *fp;
+-
+- if (!phydev || phydev->bus != fmb->mii_bus)
+- return -EINVAL;
+-
+- list_for_each_entry(fp, &fmb->phys, node) {
+- if (fp->addr == phydev->addr) {
+- write_seqcount_begin(&fp->seqcount);
+-#define _UPD(x) if (changed->x) \
+- fp->status.x = status->x
+- _UPD(link);
+- _UPD(speed);
+- _UPD(duplex);
+- _UPD(pause);
+- _UPD(asym_pause);
+-#undef _UPD
+- fixed_phy_update(fp);
+- write_seqcount_end(&fp->seqcount);
+- return 0;
+- }
+- }
+-
+- return -ENOENT;
+-}
+-EXPORT_SYMBOL(fixed_phy_update_state);
+-
+ int fixed_phy_add(unsigned int irq, int phy_addr,
+ struct fixed_phy_status *status,
+ int link_gpio)
+--- a/include/linux/phy_fixed.h
++++ b/include/linux/phy_fixed.h
+@@ -23,9 +23,6 @@ extern void fixed_phy_del(int phy_addr);
+ extern int fixed_phy_set_link_update(struct phy_device *phydev,
+ int (*link_update)(struct net_device *,
+ struct fixed_phy_status *));
+-extern int fixed_phy_update_state(struct phy_device *phydev,
+- const struct fixed_phy_status *status,
+- const struct fixed_phy_status *changed);
+ #else
+ static inline int fixed_phy_add(unsigned int irq, int phy_id,
+ struct fixed_phy_status *status,
+@@ -50,12 +47,6 @@ static inline int fixed_phy_set_link_upd
+ {
+ return -ENODEV;
+ }
+-static inline int fixed_phy_update_state(struct phy_device *phydev,
+- const struct fixed_phy_status *status,
+- const struct fixed_phy_status *changed)
+-{
+- return -ENODEV;
+-}
+ #endif /* CONFIG_FIXED_PHY */
+
+ #endif /* __PHY_FIXED_H */
--- /dev/null
+From d6bd25b692378ec17bdb1023d398c03c45829947 Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Thu, 1 Oct 2015 20:27:19 +0100
+Subject: [PATCH 724/744] phylink: add ethtool nway_reset support
+
+Add ethtool nway_reset support to phylink, to allow userspace to
+request a re-negotiation of the link.
+
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ drivers/net/phy/phylink.c | 14 ++++++++++++++
+ include/linux/phylink.h | 1 +
+ 2 files changed, 15 insertions(+)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -687,6 +687,20 @@ int phylink_ethtool_set_settings(struct
+ }
+ EXPORT_SYMBOL_GPL(phylink_ethtool_set_settings);
+
++int phylink_ethtool_nway_reset(struct phylink *pl)
++{
++ int ret = 0;
++
++ mutex_lock(&pl->config_mutex);
++ if (pl->phydev)
++ ret = genphy_restart_aneg(pl->phydev);
++ phylink_mac_an_restart(pl);
++ mutex_unlock(&pl->config_mutex);
++
++ return ret;
++}
++EXPORT_SYMBOL_GPL(phylink_ethtool_nway_reset);
++
+ /* This emulates MII registers for a fixed-mode phy operating as per the
+ * passed in state. "aneg" defines if we report negotiation is possible.
+ *
+--- a/include/linux/phylink.h
++++ b/include/linux/phylink.h
+@@ -65,6 +65,7 @@ void phylink_stop(struct phylink *);
+
+ int phylink_ethtool_get_settings(struct phylink *, struct ethtool_cmd *);
+ int phylink_ethtool_set_settings(struct phylink *, struct ethtool_cmd *);
++int phylink_ethtool_nway_reset(struct phylink *);
+ int phylink_mii_ioctl(struct phylink *, struct ifreq *, int);
+
+ void phylink_set_link_port(struct phylink *pl, u32 support, u8 port);
--- /dev/null
+From 244bee2889d08f876c64c335765a8ea6de0f5381 Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Thu, 1 Oct 2015 19:40:31 +0100
+Subject: [PATCH 725/744] net: mvneta: add nway_reset support
+
+Add ethtool nway_reset support to mvneta via phylink, so that userspace
+can request the link in whatever mode to be renegotiated via
+ethtool -r ethX.
+
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ drivers/net/ethernet/marvell/mvneta.c | 8 ++++++++
+ 1 file changed, 8 insertions(+)
+
+--- a/drivers/net/ethernet/marvell/mvneta.c
++++ b/drivers/net/ethernet/marvell/mvneta.c
+@@ -3589,6 +3589,13 @@ int mvneta_ethtool_set_settings(struct n
+ return phylink_ethtool_set_settings(pp->phylink, cmd);
+ }
+
++static int mvneta_ethtool_nway_reset(struct net_device *dev)
++{
++ struct mvneta_port *pp = netdev_priv(dev);
++
++ return phylink_ethtool_nway_reset(pp->phylink);
++}
++
+ /* Set interrupt coalescing for ethtools */
+ static int mvneta_ethtool_set_coalesce(struct net_device *dev,
+ struct ethtool_coalesce *c)
+@@ -3853,6 +3860,7 @@ const struct ethtool_ops mvneta_eth_tool
+ .get_link = ethtool_op_get_link,
+ .get_settings = mvneta_ethtool_get_settings,
+ .set_settings = mvneta_ethtool_set_settings,
++ .nway_reset = mvneta_ethtool_nway_reset,
+ .set_coalesce = mvneta_ethtool_set_coalesce,
+ .get_coalesce = mvneta_ethtool_get_coalesce,
+ .get_drvinfo = mvneta_ethtool_get_drvinfo,
--- /dev/null
+From f566177aa6661e646b83526f24391a568ffd1a75 Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Thu, 1 Oct 2015 20:32:07 +0100
+Subject: [PATCH 726/744] phylink: add flow control support
+
+Add flow control support, including ethtool support, to phylink. We
+add support to allow ethtool to get and set the current flow control
+settings, and the 802.3 specified resolution for the local and remote
+link partner abilities.
+
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ drivers/net/phy/phylink.c | 145 +++++++++++++++++++++++++++++++++++++++++-----
+ include/linux/phylink.h | 8 +++
+ 2 files changed, 139 insertions(+), 14 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -91,10 +91,12 @@ static int phylink_parse_fixedlink(struc
+ pl->link_config.an_complete = 1;
+ pl->link_config.speed = speed;
+ pl->link_config.duplex = DUPLEX_HALF;
+- pl->link_config.pause = MLO_PAUSE_NONE;
+
+ if (of_property_read_bool(fixed_node, "full-duplex"))
+ pl->link_config.duplex = DUPLEX_FULL;
++
++ /* We treat the "pause" and "asym-pause" terminology as
++ * defining the link partner's ability. */
+ if (of_property_read_bool(fixed_node, "pause"))
+ pl->link_config.pause |= MLO_PAUSE_SYM;
+ if (of_property_read_bool(fixed_node, "asym-pause"))
+@@ -118,7 +120,6 @@ static int phylink_parse_fixedlink(struc
+ pl->link_config.duplex = be32_to_cpu(fixed_prop[1]) ?
+ DUPLEX_FULL : DUPLEX_HALF;
+ pl->link_config.speed = be32_to_cpu(fixed_prop[2]);
+- pl->link_config.pause = MLO_PAUSE_NONE;
+ if (be32_to_cpu(fixed_prop[3]))
+ pl->link_config.pause |= MLO_PAUSE_SYM;
+ if (be32_to_cpu(fixed_prop[4]))
+@@ -130,16 +131,6 @@ static int phylink_parse_fixedlink(struc
+ }
+
+ if (pl->link_an_mode == MLO_AN_FIXED) {
+- /* Generate the supported/advertising masks */
+- if (pl->link_config.pause & MLO_PAUSE_SYM) {
+- pl->link_config.supported |= SUPPORTED_Pause;
+- pl->link_config.advertising |= ADVERTISED_Pause;
+- }
+- if (pl->link_config.pause & MLO_PAUSE_ASYM) {
+- pl->link_config.supported |= SUPPORTED_Asym_Pause;
+- pl->link_config.advertising |= ADVERTISED_Asym_Pause;
+- }
+-
+ if (pl->link_config.speed > SPEED_1000 &&
+ pl->link_config.duplex != DUPLEX_FULL)
+ netdev_warn(pl->netdev, "fixed link specifies half duplex for %dMbps link?\n",
+@@ -242,6 +233,56 @@ static void phylink_get_fixed_state(stru
+ state->link = !!gpiod_get_value(pl->link_gpio);
+ }
+
++/* Flow control is resolved according to our and the link partners
++ * advertisments using the following drawn from the 802.3 specs:
++ * Local device Link partner
++ * Pause AsymDir Pause AsymDir Result
++ * 1 X 1 X TX+RX
++ * 0 1 1 1 RX
++ * 1 1 0 1 TX
++ */
++static void phylink_resolve_flow(struct phylink *pl,
++ struct phylink_link_state *state)
++{
++ int new_pause = 0;
++
++ if (pl->link_config.pause & MLO_PAUSE_AN) {
++ int pause = 0;
++
++ if (pl->link_config.advertising & ADVERTISED_Pause)
++ pause |= MLO_PAUSE_SYM;
++ if (pl->link_config.advertising & ADVERTISED_Asym_Pause)
++ pause |= MLO_PAUSE_ASYM;
++
++ pause &= state->pause;
++
++ if (pause & MLO_PAUSE_SYM)
++ new_pause = MLO_PAUSE_TX | MLO_PAUSE_RX;
++ else if (pause & MLO_PAUSE_ASYM)
++ new_pause = state->pause & MLO_PAUSE_SYM ?
++ MLO_PAUSE_RX : MLO_PAUSE_TX;
++ } else {
++ new_pause = pl->link_config.pause & MLO_PAUSE_TXRX_MASK;
++ }
++
++ state->pause &= ~MLO_PAUSE_TXRX_MASK;
++ state->pause |= new_pause;
++}
++
++static const char *phylink_pause_to_str(int pause)
++{
++ switch (pause & MLO_PAUSE_TXRX_MASK) {
++ case MLO_PAUSE_TX | MLO_PAUSE_RX:
++ return "rx/tx";
++ case MLO_PAUSE_TX:
++ return "tx";
++ case MLO_PAUSE_RX:
++ return "rx";
++ default:
++ return "off";
++ }
++}
++
+ extern const char *phy_speed_to_str(int speed);
+
+ static void phylink_resolve(struct work_struct *w)
+@@ -257,6 +298,7 @@ static void phylink_resolve(struct work_
+ switch (pl->link_an_mode) {
+ case MLO_AN_PHY:
+ link_state = pl->phy_state;
++ phylink_resolve_flow(pl, &link_state);
+ break;
+
+ case MLO_AN_FIXED:
+@@ -265,9 +307,12 @@ static void phylink_resolve(struct work_
+
+ case MLO_AN_SGMII:
+ phylink_get_mac_state(pl, &link_state);
+- if (pl->phydev)
++ if (pl->phydev) {
+ link_state.link = link_state.link &&
+ pl->phy_state.link;
++ link_state.pause |= pl->phy_state.pause;
++ phylink_resolve_flow(pl, &link_state);
++ }
+ break;
+
+ case MLO_AN_8023Z:
+@@ -297,7 +342,7 @@ static void phylink_resolve(struct work_
+ "Link is Up - %s/%s - flow control %s\n",
+ phy_speed_to_str(link_state.speed),
+ link_state.duplex ? "Full" : "Half",
+- link_state.pause ? "rx/tx" : "off");
++ phylink_pause_to_str(link_state.pause));
+ }
+ }
+ mutex_unlock(&pl->state_mutex);
+@@ -326,6 +371,7 @@ struct phylink *phylink_create(struct ne
+ pl->link_interface = iface;
+ pl->link_port_support = SUPPORTED_MII;
+ pl->link_port = PORT_MII;
++ pl->link_config.pause = MLO_PAUSE_AN;
+ pl->ops = ops;
+ __set_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state);
+
+@@ -507,6 +553,7 @@ void phylink_start(struct phylink *pl)
+ * a fixed-link to start with the correct parameters, and also
+ * ensures that we set the appropriate advertisment for Serdes links.
+ */
++ phylink_resolve_flow(pl, &pl->link_config);
+ phylink_mac_config(pl, &pl->link_config);
+
+ clear_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state);
+@@ -701,6 +748,76 @@ int phylink_ethtool_nway_reset(struct ph
+ }
+ EXPORT_SYMBOL_GPL(phylink_ethtool_nway_reset);
+
++void phylink_ethtool_get_pauseparam(struct phylink *pl,
++ struct ethtool_pauseparam *pause)
++{
++ mutex_lock(&pl->config_mutex);
++
++ pause->autoneg = !!(pl->link_config.pause & MLO_PAUSE_AN);
++ pause->rx_pause = !!(pl->link_config.pause & MLO_PAUSE_RX);
++ pause->tx_pause = !!(pl->link_config.pause & MLO_PAUSE_TX);
++
++ mutex_unlock(&pl->config_mutex);
++}
++EXPORT_SYMBOL_GPL(phylink_ethtool_get_pauseparam);
++
++static int __phylink_ethtool_set_pauseparam(struct phylink *pl,
++ struct ethtool_pauseparam *pause)
++{
++ struct phylink_link_state *config = &pl->link_config;
++
++ if (!(config->supported & (SUPPORTED_Pause | SUPPORTED_Asym_Pause)))
++ return -EOPNOTSUPP;
++
++ if (!(config->supported & SUPPORTED_Asym_Pause) &&
++ !pause->autoneg && pause->rx_pause != pause->tx_pause)
++ return -EINVAL;
++
++ config->pause &= ~(MLO_PAUSE_AN | MLO_PAUSE_TXRX_MASK);
++
++ if (pause->autoneg)
++ config->pause |= MLO_PAUSE_AN;
++ if (pause->rx_pause)
++ config->pause |= MLO_PAUSE_RX;
++ if (pause->tx_pause)
++ config->pause |= MLO_PAUSE_TX;
++
++ switch (pl->link_an_mode) {
++ case MLO_AN_PHY:
++ /* Silently mark the carrier down, and then trigger a resolve */
++ netif_carrier_off(pl->netdev);
++ phylink_run_resolve(pl);
++ break;
++
++ case MLO_AN_FIXED:
++ /* Should we allow fixed links to change against the config? */
++ phylink_resolve_flow(pl, config);
++ phylink_mac_config(pl, config);
++ break;
++
++ case MLO_AN_SGMII:
++ case MLO_AN_8023Z:
++ phylink_mac_config(pl, config);
++ phylink_mac_an_restart(pl);
++ break;
++ }
++
++ return 0;
++}
++
++int phylink_ethtool_set_pauseparam(struct phylink *pl,
++ struct ethtool_pauseparam *pause)
++{
++ int ret;
++
++ mutex_lock(&pl->config_mutex);
++ ret = __phylink_ethtool_set_pauseparam(pl, pause);
++ mutex_unlock(&pl->config_mutex);
++
++ return ret;
++}
++EXPORT_SYMBOL_GPL(phylink_ethtool_set_pauseparam);
++
+ /* This emulates MII registers for a fixed-mode phy operating as per the
+ * passed in state. "aneg" defines if we report negotiation is possible.
+ *
+--- a/include/linux/phylink.h
++++ b/include/linux/phylink.h
+@@ -13,6 +13,10 @@ enum {
+ MLO_PAUSE_NONE,
+ MLO_PAUSE_ASYM = BIT(0),
+ MLO_PAUSE_SYM = BIT(1),
++ MLO_PAUSE_RX = BIT(2),
++ MLO_PAUSE_TX = BIT(3),
++ MLO_PAUSE_TXRX_MASK = MLO_PAUSE_TX | MLO_PAUSE_RX,
++ MLO_PAUSE_AN = BIT(4),
+
+ MLO_AN_PHY = 0,
+ MLO_AN_FIXED,
+@@ -66,6 +70,10 @@ void phylink_stop(struct phylink *);
+ int phylink_ethtool_get_settings(struct phylink *, struct ethtool_cmd *);
+ int phylink_ethtool_set_settings(struct phylink *, struct ethtool_cmd *);
+ int phylink_ethtool_nway_reset(struct phylink *);
++void phylink_ethtool_get_pauseparam(struct phylink *,
++ struct ethtool_pauseparam *);
++int phylink_ethtool_set_pauseparam(struct phylink *,
++ struct ethtool_pauseparam *);
+ int phylink_mii_ioctl(struct phylink *, struct ifreq *, int);
+
+ void phylink_set_link_port(struct phylink *pl, u32 support, u8 port);
--- /dev/null
+From 7bd34822b9922beb22a6384d9190646105d259d8 Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Thu, 1 Oct 2015 17:41:44 +0100
+Subject: [PATCH 727/744] net: mvneta: add flow control support via phylink
+
+Add flow control support to mvneta, including the ethtool hooks. This
+uses the phylink code to calculate the result of autonegotiation where
+a phy is attached, and to handle the ethtool settings.
+
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ drivers/net/ethernet/marvell/mvneta.c | 22 +++++++++++++++++++++-
+ 1 file changed, 21 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/ethernet/marvell/mvneta.c
++++ b/drivers/net/ethernet/marvell/mvneta.c
+@@ -3208,6 +3208,8 @@ static void mvneta_mac_config(struct net
+
+ if (state->advertising & ADVERTISED_Pause)
+ new_an |= MVNETA_GMAC_ADVERT_SYM_FLOW_CTRL;
++ if (state->pause & MLO_PAUSE_TXRX_MASK)
++ new_an |= MVNETA_GMAC_CONFIG_FLOW_CTRL;
+
+ switch (mode) {
+ case MLO_AN_SGMII:
+@@ -3232,7 +3234,7 @@ static void mvneta_mac_config(struct net
+ /* The MAC only supports FD mode */
+ MVNETA_GMAC_CONFIG_FULL_DUPLEX;
+
+- if (state->an_enabled)
++ if (state->pause & MLO_PAUSE_AN && state->an_enabled)
+ new_an |= MVNETA_GMAC_AN_FLOW_CTRL_EN;
+ break;
+
+@@ -3685,6 +3687,22 @@ static int mvneta_ethtool_set_ringparam(
+ return 0;
+ }
+
++static void mvneta_ethtool_get_pauseparam(struct net_device *dev,
++ struct ethtool_pauseparam *pause)
++{
++ struct mvneta_port *pp = netdev_priv(dev);
++
++ phylink_ethtool_get_pauseparam(pp->phylink, pause);
++}
++
++static int mvneta_ethtool_set_pauseparam(struct net_device *dev,
++ struct ethtool_pauseparam *pause)
++{
++ struct mvneta_port *pp = netdev_priv(dev);
++
++ return phylink_ethtool_set_pauseparam(pp->phylink, pause);
++}
++
+ static void mvneta_ethtool_get_strings(struct net_device *netdev, u32 sset,
+ u8 *data)
+ {
+@@ -3866,6 +3884,8 @@ const struct ethtool_ops mvneta_eth_tool
+ .get_drvinfo = mvneta_ethtool_get_drvinfo,
+ .get_ringparam = mvneta_ethtool_get_ringparam,
+ .set_ringparam = mvneta_ethtool_set_ringparam,
++ .get_pauseparam = mvneta_ethtool_get_pauseparam,
++ .set_pauseparam = mvneta_ethtool_set_pauseparam,
+ .get_strings = mvneta_ethtool_get_strings,
+ .get_ethtool_stats = mvneta_ethtool_get_stats,
+ .get_sset_count = mvneta_ethtool_get_sset_count,
--- /dev/null
+From 62f8a12044265df11531750a240e516a5f1ff433 Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Thu, 1 Oct 2015 00:34:08 +0100
+Subject: [PATCH 728/744] net: mvneta: enable flow control for PHY connections
+
+Enable flow control support for PHY connections by indicating our
+support via the ethtool capabilities. phylink takes care of the
+appropriate handling.
+
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ drivers/net/ethernet/marvell/mvneta.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+--- a/drivers/net/ethernet/marvell/mvneta.c
++++ b/drivers/net/ethernet/marvell/mvneta.c
+@@ -3127,12 +3127,14 @@ static int mvneta_mac_support(struct net
+ state->supported = PHY_10BT_FEATURES |
+ PHY_100BT_FEATURES |
+ SUPPORTED_1000baseT_Full |
++ SUPPORTED_Pause |
+ SUPPORTED_Autoneg;
+ state->advertising = ADVERTISED_10baseT_Half |
+ ADVERTISED_10baseT_Full |
+ ADVERTISED_100baseT_Half |
+ ADVERTISED_100baseT_Full |
+ ADVERTISED_1000baseT_Full |
++ ADVERTISED_Pause |
+ ADVERTISED_Autoneg;
+ state->an_enabled = 1;
+ break;
--- /dev/null
+From 4c3e2dc08a11fb1273ca62467f1d06e59866bad3 Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@armlinux.org.uk>
+Date: Tue, 12 Jul 2016 00:04:13 +0100
+Subject: [PATCH 729/744] net: mvneta: enable flow control for fixed
+ connections
+
+Allow symetric flow control to be enabled for fixed link connections as
+well as other types of connections by setting the supported and
+advertised capability bits.
+
+Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
+---
+ drivers/net/ethernet/marvell/mvneta.c | 11 +++++++----
+ 1 file changed, 7 insertions(+), 4 deletions(-)
+
+--- a/drivers/net/ethernet/marvell/mvneta.c
++++ b/drivers/net/ethernet/marvell/mvneta.c
+@@ -3114,9 +3114,9 @@ static int mvneta_mac_support(struct net
+ switch (mode) {
+ case MLO_AN_8023Z:
+ state->supported = SUPPORTED_1000baseT_Full |
+- SUPPORTED_Autoneg | SUPPORTED_Pause;
++ SUPPORTED_Autoneg;
+ state->advertising = ADVERTISED_1000baseT_Full |
+- ADVERTISED_Autoneg | ADVERTISED_Pause;
++ ADVERTISED_Autoneg;
+ state->an_enabled = 1;
+ break;
+
+@@ -3127,18 +3127,21 @@ static int mvneta_mac_support(struct net
+ state->supported = PHY_10BT_FEATURES |
+ PHY_100BT_FEATURES |
+ SUPPORTED_1000baseT_Full |
+- SUPPORTED_Pause |
+ SUPPORTED_Autoneg;
+ state->advertising = ADVERTISED_10baseT_Half |
+ ADVERTISED_10baseT_Full |
+ ADVERTISED_100baseT_Half |
+ ADVERTISED_100baseT_Full |
+ ADVERTISED_1000baseT_Full |
+- ADVERTISED_Pause |
+ ADVERTISED_Autoneg;
+ state->an_enabled = 1;
+ break;
+ }
++
++ /* All modes support flow control */
++ state->supported |= SUPPORTED_Pause;
++ state->advertising |= ADVERTISED_Pause;
++
+ return 0;
+ }
+
--- /dev/null
+From ffba226d73a2be262fff12d30aecf76d107b2ace Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Thu, 1 Oct 2015 21:19:53 +0100
+Subject: [PATCH 730/744] phylink: add EEE support
+
+Add EEE hooks to phylink to allow the phylib EEE functions for the
+connected phy to be safely accessed.
+
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ drivers/net/phy/phylink.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++-
+ include/linux/phylink.h | 7 +++++-
+ 2 files changed, 63 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -334,7 +334,8 @@ static void phylink_resolve(struct work_
+ if (pl->phydev)
+ phylink_mac_config(pl, &link_state);
+
+- pl->ops->mac_link_up(ndev, pl->link_an_mode);
++ pl->ops->mac_link_up(ndev, pl->link_an_mode,
++ pl->phydev);
+
+ netif_carrier_on(ndev);
+
+@@ -818,6 +819,61 @@ int phylink_ethtool_set_pauseparam(struc
+ }
+ EXPORT_SYMBOL_GPL(phylink_ethtool_set_pauseparam);
+
++int phylink_init_eee(struct phylink *pl, bool clk_stop_enable)
++{
++ int ret = -EPROTONOSUPPORT;
++
++ mutex_lock(&pl->config_mutex);
++ if (pl->phydev)
++ ret = phy_init_eee(pl->phydev, clk_stop_enable);
++ mutex_unlock(&pl->config_mutex);
++
++ return ret;
++}
++EXPORT_SYMBOL_GPL(phylink_init_eee);
++
++int phylink_get_eee_err(struct phylink *pl)
++{
++ int ret = 0;
++
++ mutex_lock(&pl->config_mutex);
++ if (pl->phydev)
++ ret = phy_get_eee_err(pl->phydev);
++ mutex_unlock(&pl->config_mutex);
++
++ return ret;
++}
++EXPORT_SYMBOL_GPL(phylink_get_eee_err);
++
++int phylink_ethtool_get_eee(struct phylink *pl, struct ethtool_eee *eee)
++{
++ int ret = -EOPNOTSUPP;
++
++ mutex_lock(&pl->config_mutex);
++ if (pl->phydev)
++ ret = phy_ethtool_get_eee(pl->phydev, eee);
++ mutex_unlock(&pl->config_mutex);
++
++ return ret;
++}
++EXPORT_SYMBOL_GPL(phylink_ethtool_get_eee);
++
++int phylink_ethtool_set_eee(struct phylink *pl, struct ethtool_eee *eee)
++{
++ int ret = -EOPNOTSUPP;
++
++ mutex_lock(&pl->config_mutex);
++ if (pl->phydev) {
++ ret = phy_ethtool_set_eee(pl->phydev, eee);
++ if (ret == 0 && eee->eee_enabled)
++ phy_start_aneg(pl->phydev);
++ }
++ mutex_unlock(&pl->config_mutex);
++
++ return ret;
++}
++EXPORT_SYMBOL_GPL(phylink_ethtool_set_eee);
++
+ /* This emulates MII registers for a fixed-mode phy operating as per the
+ * passed in state. "aneg" defines if we report negotiation is possible.
+ *
+--- a/include/linux/phylink.h
++++ b/include/linux/phylink.h
+@@ -51,7 +51,8 @@ struct phylink_mac_ops {
+ void (*mac_an_restart)(struct net_device *, unsigned int mode);
+
+ void (*mac_link_down)(struct net_device *, unsigned int mode);
+- void (*mac_link_up)(struct net_device *, unsigned int mode);
++ void (*mac_link_up)(struct net_device *, unsigned int mode,
++ struct phy_device *);
+ };
+
+ struct phylink *phylink_create(struct net_device *, struct device_node *,
+@@ -74,6 +75,10 @@ void phylink_ethtool_get_pauseparam(stru
+ struct ethtool_pauseparam *);
+ int phylink_ethtool_set_pauseparam(struct phylink *,
+ struct ethtool_pauseparam *);
++int phylink_init_eee(struct phylink *, bool);
++int phylink_get_eee_err(struct phylink *);
++int phylink_ethtool_get_eee(struct phylink *, struct ethtool_eee *);
++int phylink_ethtool_set_eee(struct phylink *, struct ethtool_eee *);
+ int phylink_mii_ioctl(struct phylink *, struct ifreq *, int);
+
+ void phylink_set_link_port(struct phylink *pl, u32 support, u8 port);
--- /dev/null
+From b7dacf514e41d6efff0ccc170f660cc6dc2aeae2 Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Tue, 29 Sep 2015 15:17:39 +0100
+Subject: [PATCH 731/744] net: mvneta: add EEE support
+
+Add EEE support to mvneta. This allows us to enable the low power idle
+support at MAC level if there is a PHY attached through phylink which
+supports LPI. The appropriate ethtool support is provided to allow the
+feature to be controlled, including ethtool statistics for EEE wakeup
+errors.
+
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ drivers/net/ethernet/marvell/mvneta.c | 87 +++++++++++++++++++++++++++++++++++
+ 1 file changed, 87 insertions(+)
+
+--- a/drivers/net/ethernet/marvell/mvneta.c
++++ b/drivers/net/ethernet/marvell/mvneta.c
+@@ -243,6 +243,12 @@
+ #define MVNETA_TXQ_TOKEN_SIZE_REG(q) (0x3e40 + ((q) << 2))
+ #define MVNETA_TXQ_TOKEN_SIZE_MAX 0x7fffffff
+
++#define MVNETA_LPI_CTRL_0 0x2cc0
++#define MVNETA_LPI_CTRL_1 0x2cc4
++#define MVNETA_LPI_REQUEST_ENABLE BIT(0)
++#define MVNETA_LPI_CTRL_2 0x2cc8
++#define MVNETA_LPI_STATUS 0x2ccc
++
+ #define MVNETA_CAUSE_TXQ_SENT_DESC_ALL_MASK 0xff
+
+ /* Descriptor ring Macros */
+@@ -316,6 +322,11 @@
+ #define MVNETA_RX_GET_BM_POOL_ID(rxd) \
+ (((rxd)->status & MVNETA_RXD_BM_POOL_MASK) >> MVNETA_RXD_BM_POOL_SHIFT)
+
++enum {
++ ETHTOOL_STAT_EEE_WAKEUP,
++ ETHTOOL_MAX_STATS,
++};
++
+ struct mvneta_statistic {
+ unsigned short offset;
+ unsigned short type;
+@@ -324,6 +335,7 @@ struct mvneta_statistic {
+
+ #define T_REG_32 32
+ #define T_REG_64 64
++#define T_SW 1
+
+ static const struct mvneta_statistic mvneta_statistics[] = {
+ { 0x3000, T_REG_64, "good_octets_received", },
+@@ -358,6 +370,7 @@ static const struct mvneta_statistic mvn
+ { 0x304c, T_REG_32, "broadcast_frames_sent", },
+ { 0x3054, T_REG_32, "fc_sent", },
+ { 0x300c, T_REG_32, "internal_mac_transmit_err", },
++ { ETHTOOL_STAT_EEE_WAKEUP, T_SW, "eee_wakeup_errors", },
+ };
+
+ struct mvneta_pcpu_stats {
+@@ -413,6 +426,10 @@ struct mvneta_port {
+ struct mvneta_bm_pool *pool_short;
+ int bm_win_id;
+
++ bool eee_enabled;
++ bool eee_active;
++ bool tx_lpi_enabled;
++
+ u64 ethtool_stats[ARRAY_SIZE(mvneta_statistics)];
+
+ u32 indir[MVNETA_RSS_LU_TABLE_SIZE];
+@@ -3276,6 +3293,18 @@ static void mvneta_mac_config(struct net
+ mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, new_an);
+ }
+
++static void mvneta_set_eee(struct mvneta_port *pp, bool enable)
++{
++ u32 lpi_ctl1;
++
++ lpi_ctl1 = mvreg_read(pp, MVNETA_LPI_CTRL_1);
++ if (enable)
++ lpi_ctl1 |= MVNETA_LPI_REQUEST_ENABLE;
++ else
++ lpi_ctl1 &= ~MVNETA_LPI_REQUEST_ENABLE;
++ mvreg_write(pp, MVNETA_LPI_CTRL_1, lpi_ctl1);
++}
++
+ static void mvneta_mac_link_down(struct net_device *ndev, unsigned int mode)
+ {
+ struct mvneta_port *pp = netdev_priv(ndev);
+@@ -3289,6 +3318,9 @@ static void mvneta_mac_link_down(struct
+ val |= MVNETA_GMAC_FORCE_LINK_DOWN;
+ mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val);
+ }
++
++ pp->eee_active = false;
++ mvneta_set_eee(pp, false);
+ }
+
+ static void mvneta_mac_link_up(struct net_device *ndev, unsigned int mode,
+@@ -3305,6 +3337,11 @@ static void mvneta_mac_link_up(struct ne
+ }
+
+ mvneta_port_up(pp);
++
++ if (phy && pp->eee_enabled) {
++ pp->eee_active = phy_init_eee(phy, 0) >= 0;
++ mvneta_set_eee(pp, pp->eee_active && pp->tx_lpi_enabled);
++ }
+ }
+
+ static const struct phylink_mac_ops mvneta_phylink_ops = {
+@@ -3744,6 +3781,13 @@ static void mvneta_ethtool_update_stats(
+ val64 = (u64)high << 32 | low;
+ pp->ethtool_stats[i] += val64;
+ break;
++ case T_SW:
++ switch (s->offset) {
++ case ETHTOOL_STAT_EEE_WAKEUP:
++ val = phylink_get_eee_err(pp->phylink);
++ break;
++ }
++ break;
+ }
+ }
+ }
+@@ -3867,6 +3911,47 @@ static int mvneta_ethtool_get_rxfh(struc
+ return 0;
+ }
+
++static int mvneta_ethtool_get_eee(struct net_device *dev,
++ struct ethtool_eee *eee)
++{
++ struct mvneta_port *pp = netdev_priv(dev);
++ u32 lpi_ctl0;
++
++ lpi_ctl0 = mvreg_read(pp, MVNETA_LPI_CTRL_0);
++
++ eee->eee_enabled = pp->eee_enabled;
++ eee->eee_active = pp->eee_active;
++ eee->tx_lpi_enabled = pp->tx_lpi_enabled;
++ eee->tx_lpi_timer = (lpi_ctl0) >> 8; // * scale;
++
++ return phylink_ethtool_get_eee(pp->phylink, eee);
++}
++
++static int mvneta_ethtool_set_eee(struct net_device *dev,
++ struct ethtool_eee *eee)
++{
++ struct mvneta_port *pp = netdev_priv(dev);
++ u32 lpi_ctl0;
++
++ /* The Armada 37x documents do not give limits for this other than
++ * it being an 8-bit register. */
++ if (eee->tx_lpi_enabled &&
++ (eee->tx_lpi_timer < 0 || eee->tx_lpi_timer > 255))
++ return -EINVAL;
++
++ lpi_ctl0 = mvreg_read(pp, MVNETA_LPI_CTRL_0);
++ lpi_ctl0 &= ~(0xff << 8);
++ lpi_ctl0 |= eee->tx_lpi_timer << 8;
++ mvreg_write(pp, MVNETA_LPI_CTRL_0, lpi_ctl0);
++
++ pp->eee_enabled = eee->eee_enabled;
++ pp->tx_lpi_enabled = eee->tx_lpi_enabled;
++
++ mvneta_set_eee(pp, eee->tx_lpi_enabled && eee->eee_enabled);
++
++ return phylink_ethtool_set_eee(pp->phylink, eee);
++}
++
+ static const struct net_device_ops mvneta_netdev_ops = {
+ .ndo_open = mvneta_open,
+ .ndo_stop = mvneta_stop,
+@@ -3898,6 +3983,8 @@ const struct ethtool_ops mvneta_eth_tool
+ .get_rxnfc = mvneta_ethtool_get_rxnfc,
+ .get_rxfh = mvneta_ethtool_get_rxfh,
+ .set_rxfh = mvneta_ethtool_set_rxfh,
++ .get_eee = mvneta_ethtool_get_eee,
++ .set_eee = mvneta_ethtool_set_eee,
+ };
+
+ /* Initialize hw */
--- /dev/null
+From 5419ccb638aa5c353ea88815e98953d9fc02e6ca Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Thu, 1 Oct 2015 23:10:05 +0100
+Subject: [PATCH 732/744] phylink: add module EEPROM support
+
+Add support for reading module EEPROMs through phylink.
+
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ drivers/net/phy/phylink.c | 66 +++++++++++++++++++++++++++++++++++++++++++++++
+ include/linux/phylink.h | 12 +++++++++
+ 2 files changed, 78 insertions(+)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -60,6 +60,9 @@ struct phylink {
+ struct work_struct resolve;
+
+ bool mac_link_up;
++
++ const struct phylink_module_ops *module_ops;
++ void *module_data;
+ };
+
+ static const char *phylink_an_mode_str(unsigned int mode)
+@@ -819,6 +822,36 @@ int phylink_ethtool_set_pauseparam(struc
+ }
+ EXPORT_SYMBOL_GPL(phylink_ethtool_set_pauseparam);
+
++int phylink_ethtool_get_module_info(struct phylink *pl,
++ struct ethtool_modinfo *modinfo)
++{
++ int ret = -EOPNOTSUPP;
++
++ mutex_lock(&pl->config_mutex);
++ if (pl->module_ops)
++ ret = pl->module_ops->get_module_info(pl->module_data,
++ modinfo);
++ mutex_unlock(&pl->config_mutex);
++
++ return ret;
++}
++EXPORT_SYMBOL_GPL(phylink_ethtool_get_module_info);
++
++int phylink_ethtool_get_module_eeprom(struct phylink *pl,
++ struct ethtool_eeprom *ee, u8 *buf)
++{
++ int ret = -EOPNOTSUPP;
++
++ mutex_lock(&pl->config_mutex);
++ if (pl->module_ops)
++ ret = pl->module_ops->get_module_eeprom(pl->module_data, ee,
++ buf);
++ mutex_unlock(&pl->config_mutex);
++
++ return ret;
++}
++EXPORT_SYMBOL_GPL(phylink_ethtool_get_module_eeprom);
++
+ int phylink_init_eee(struct phylink *pl, bool clk_stop_enable)
+ {
+ int ret = -EPROTONOSUPPORT;
+@@ -1016,6 +1049,39 @@ EXPORT_SYMBOL_GPL(phylink_mii_ioctl);
+
+
+
++int phylink_register_module(struct phylink *pl, void *data,
++ const struct phylink_module_ops *ops)
++{
++ int ret = -EBUSY;
++
++ mutex_lock(&pl->config_mutex);
++ if (!pl->module_ops) {
++ pl->module_ops = ops;
++ pl->module_data = data;
++ ret = 0;
++ }
++ mutex_unlock(&pl->config_mutex);
++
++ return ret;
++}
++EXPORT_SYMBOL_GPL(phylink_register_module);
++
++int phylink_unregister_module(struct phylink *pl, void *data)
++{
++ int ret = -EINVAL;
++
++ mutex_lock(&pl->config_mutex);
++ if (pl->module_data == data) {
++ pl->module_ops = NULL;
++ pl->module_data = NULL;
++ ret = 0;
++ }
++ mutex_unlock(&pl->config_mutex);
++
++ return ret;
++}
++EXPORT_SYMBOL_GPL(phylink_unregister_module);
++
+ void phylink_disable(struct phylink *pl)
+ {
+ set_bit(PHYLINK_DISABLE_LINK, &pl->phylink_disable_state);
+--- a/include/linux/phylink.h
++++ b/include/linux/phylink.h
+@@ -55,6 +55,11 @@ struct phylink_mac_ops {
+ struct phy_device *);
+ };
+
++struct phylink_module_ops {
++ int (*get_module_info)(void *, struct ethtool_modinfo *);
++ int (*get_module_eeprom)(void *, struct ethtool_eeprom *, u8 *);
++};
++
+ struct phylink *phylink_create(struct net_device *, struct device_node *,
+ phy_interface_t iface, const struct phylink_mac_ops *ops);
+ void phylink_destroy(struct phylink *);
+@@ -75,12 +80,19 @@ void phylink_ethtool_get_pauseparam(stru
+ struct ethtool_pauseparam *);
+ int phylink_ethtool_set_pauseparam(struct phylink *,
+ struct ethtool_pauseparam *);
++int phylink_ethtool_get_module_info(struct phylink *, struct ethtool_modinfo *);
++int phylink_ethtool_get_module_eeprom(struct phylink *,
++ struct ethtool_eeprom *, u8 *);
+ int phylink_init_eee(struct phylink *, bool);
+ int phylink_get_eee_err(struct phylink *);
+ int phylink_ethtool_get_eee(struct phylink *, struct ethtool_eee *);
+ int phylink_ethtool_set_eee(struct phylink *, struct ethtool_eee *);
+ int phylink_mii_ioctl(struct phylink *, struct ifreq *, int);
+
++int phylink_register_module(struct phylink *, void *,
++ const struct phylink_module_ops *);
++int phylink_unregister_module(struct phylink *, void *);
++
+ void phylink_set_link_port(struct phylink *pl, u32 support, u8 port);
+ int phylink_set_link_an_mode(struct phylink *pl, unsigned int mode);
+ void phylink_disable(struct phylink *pl);
--- /dev/null
+From 665e1fe77dedcfc6b5669214ebfd252c803290d4 Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Thu, 1 Oct 2015 23:32:39 +0100
+Subject: [PATCH 733/744] net: mvneta: add module EEPROM reading support
+
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ drivers/net/ethernet/marvell/mvneta.c | 18 ++++++++++++++++++
+ 1 file changed, 18 insertions(+)
+
+--- a/drivers/net/ethernet/marvell/mvneta.c
++++ b/drivers/net/ethernet/marvell/mvneta.c
+@@ -3911,6 +3911,22 @@ static int mvneta_ethtool_get_rxfh(struc
+ return 0;
+ }
+
++static int mvneta_ethtool_get_module_info(struct net_device *dev,
++ struct ethtool_modinfo *modinfo)
++{
++ struct mvneta_port *pp = netdev_priv(dev);
++
++ return phylink_ethtool_get_module_info(pp->phylink, modinfo);
++}
++
++static int mvneta_ethtool_get_module_eeprom(struct net_device *dev,
++ struct ethtool_eeprom *ee, u8 *buf)
++{
++ struct mvneta_port *pp = netdev_priv(dev);
++
++ return phylink_ethtool_get_module_eeprom(pp->phylink, ee, buf);
++}
++
+ static int mvneta_ethtool_get_eee(struct net_device *dev,
+ struct ethtool_eee *eee)
+ {
+@@ -3983,6 +3999,8 @@ const struct ethtool_ops mvneta_eth_tool
+ .get_rxnfc = mvneta_ethtool_get_rxnfc,
+ .get_rxfh = mvneta_ethtool_get_rxfh,
+ .set_rxfh = mvneta_ethtool_set_rxfh,
++ .get_module_info = mvneta_ethtool_get_module_info,
++ .get_module_eeprom = mvneta_ethtool_get_module_eeprom,
+ .get_eee = mvneta_ethtool_get_eee,
+ .set_eee = mvneta_ethtool_set_eee,
+ };
--- /dev/null
+From a7091ef24223ed39b39c6b73b77c55c8a607f34a Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Thu, 8 Oct 2015 23:49:47 +0100
+Subject: [PATCH 734/744] sfp/phylink: hook up eeprom functions
+
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ drivers/net/phy/sfp.c | 19 +++++++++++--------
+ 1 file changed, 11 insertions(+), 8 deletions(-)
+
+--- a/drivers/net/phy/sfp.c
++++ b/drivers/net/phy/sfp.c
+@@ -901,11 +901,9 @@ static void sfp_sm_event(struct sfp *sfp
+ mutex_unlock(&sfp->sm_mutex);
+ }
+
+-#if 0
+-static int sfp_phy_module_info(struct phy_device *phy,
+- struct ethtool_modinfo *modinfo)
++static int sfp_module_info(void *priv, struct ethtool_modinfo *modinfo)
+ {
+- struct sfp *sfp = phy->priv;
++ struct sfp *sfp = priv;
+
+ /* locking... and check module is present */
+
+@@ -919,10 +917,9 @@ static int sfp_phy_module_info(struct ph
+ return 0;
+ }
+
+-static int sfp_phy_module_eeprom(struct phy_device *phy,
+- struct ethtool_eeprom *ee, u8 *data)
++static int sfp_module_eeprom(void *priv, struct ethtool_eeprom *ee, u8 *data)
+ {
+- struct sfp *sfp = phy->priv;
++ struct sfp *sfp = priv;
+ unsigned int first, last, len;
+ int ret;
+
+@@ -953,7 +950,11 @@ static int sfp_phy_module_eeprom(struct
+ }
+ return 0;
+ }
+-#endif
++
++static const struct phylink_module_ops sfp_module_ops = {
++ .get_module_info = sfp_module_info,
++ .get_module_eeprom = sfp_module_eeprom,
++};
+
+ static void sfp_timeout(struct work_struct *work)
+ {
+@@ -1029,6 +1030,7 @@ static int sfp_netdev_notify(struct noti
+ case NETDEV_UNREGISTER:
+ if (sfp->mod_phy && sfp->phylink)
+ phylink_disconnect_phy(sfp->phylink);
++ phylink_unregister_module(sfp->phylink, sfp);
+ sfp->phylink = NULL;
+ dev_put(sfp->ndev);
+ sfp->ndev = NULL;
+@@ -1145,6 +1147,7 @@ static int sfp_probe(struct platform_dev
+ }
+
+ phylink_disable(sfp->phylink);
++ phylink_register_module(sfp->phylink, sfp, &sfp_module_ops);
+ }
+
+ sfp->state = sfp_get_state(sfp);
--- /dev/null
+--- a/drivers/net/ethernet/marvell/mvneta.c
++++ b/drivers/net/ethernet/marvell/mvneta.c
+@@ -1695,8 +1695,10 @@ static struct mvneta_tx_queue *mvneta_tx
+
+ /* Free tx queue skbuffs */
+ static void mvneta_txq_bufs_free(struct mvneta_port *pp,
+- struct mvneta_tx_queue *txq, int num)
++ struct mvneta_tx_queue *txq, int num,
++ struct netdev_queue *nq)
+ {
++ unsigned int bytes_compl = 0, pkts_compl = 0;
+ int i;
+
+ for (i = 0; i < num; i++) {
+@@ -1704,6 +1706,11 @@ static void mvneta_txq_bufs_free(struct
+ txq->txq_get_index;
+ struct sk_buff *skb = txq->tx_skb[txq->txq_get_index];
+
++ if (skb) {
++ bytes_compl += skb->len;
++ pkts_compl++;
++ }
++
+ mvneta_txq_inc_get(txq);
+
+ if (!IS_TSO_HEADER(txq, tx_desc->buf_phys_addr))
+@@ -1714,6 +1721,8 @@ static void mvneta_txq_bufs_free(struct
+ continue;
+ dev_kfree_skb_any(skb);
+ }
++
++ netdev_tx_completed_queue(nq, pkts_compl, bytes_compl);
+ }
+
+ /* Handle end of transmission */
+@@ -1727,7 +1736,7 @@ static void mvneta_txq_done(struct mvnet
+ if (!tx_done)
+ return;
+
+- mvneta_txq_bufs_free(pp, txq, tx_done);
++ mvneta_txq_bufs_free(pp, txq, tx_done, nq);
+
+ txq->count -= tx_done;
+
+@@ -2334,6 +2343,8 @@ out:
+ struct mvneta_pcpu_stats *stats = this_cpu_ptr(pp->stats);
+ struct netdev_queue *nq = netdev_get_tx_queue(dev, txq_id);
+
++ netdev_tx_sent_queue(nq, len);
++
+ txq->count += frags;
+ mvneta_txq_pend_desc_add(pp, txq, frags);
+
+@@ -2358,9 +2369,10 @@ static void mvneta_txq_done_force(struct
+ struct mvneta_tx_queue *txq)
+
+ {
++ struct netdev_queue *nq = netdev_get_tx_queue(pp->dev, txq->id);
+ int tx_done = txq->count;
+
+- mvneta_txq_bufs_free(pp, txq, tx_done);
++ mvneta_txq_bufs_free(pp, txq, tx_done, nq);
+
+ /* reset txq */
+ txq->count = 0;
+@@ -2841,6 +2853,8 @@ static int mvneta_txq_init(struct mvneta
+ static void mvneta_txq_deinit(struct mvneta_port *pp,
+ struct mvneta_tx_queue *txq)
+ {
++ struct netdev_queue *nq = netdev_get_tx_queue(pp->dev, txq->id);
++
+ kfree(txq->tx_skb);
+
+ if (txq->tso_hdrs)
+@@ -2852,6 +2866,8 @@ static void mvneta_txq_deinit(struct mvn
+ txq->size * MVNETA_DESC_ALIGNED_SIZE,
+ txq->descs, txq->descs_phys);
+
++ netdev_tx_reset_queue(nq);
++
+ txq->descs = NULL;
+ txq->last_desc = 0;
+ txq->next_desc_to_proc = 0;
--- /dev/null
+Armada 370/XP devices can 'blink' gpio lines with a configurable on
+and off period. This can be modelled as a PWM.
+
+However, there are only two sets of PWM configuration registers for
+all the gpio lines. This driver simply allows a single gpio line per
+gpio chip of 32 lines to be used as a PWM. Attempts to use more return
+EBUSY.
+
+Due to the interleaving of registers it is not simple to separate the
+PWM driver from the gpio driver. Thus the gpio driver has been
+extended with a PWM driver.
+
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+---
+ drivers/gpio/Kconfig | 5 ++
+ drivers/gpio/Makefile | 1 +
+ drivers/gpio/gpio-mvebu-pwm.c | 202 ++++++++++++++++++++++++++++++++++++++++++
+ drivers/gpio/gpio-mvebu.c | 37 +++-----
+ drivers/gpio/gpio-mvebu.h | 79 +++++++++++++++++
+ 5 files changed, 299 insertions(+), 25 deletions(-)
+ create mode 100644 drivers/gpio/gpio-mvebu-pwm.c
+ create mode 100644 drivers/gpio/gpio-mvebu.h
+
+--- a/drivers/gpio/Kconfig
++++ b/drivers/gpio/Kconfig
+@@ -295,6 +295,11 @@ config GPIO_MVEBU
+ depends on OF
+ select GENERIC_IRQ_CHIP
+
++config GPIO_MVEBU_PWM
++ def_bool y
++ depends on GPIO_MVEBU
++ depends on PWM
++
+ config GPIO_MXC
+ def_bool y
+ depends on ARCH_MXC
+--- a/drivers/gpio/Makefile
++++ b/drivers/gpio/Makefile
+@@ -67,6 +67,7 @@ obj-$(CONFIG_GPIO_MPC5200) += gpio-mpc52
+ obj-$(CONFIG_GPIO_MPC8XXX) += gpio-mpc8xxx.o
+ obj-$(CONFIG_GPIO_MSIC) += gpio-msic.o
+ obj-$(CONFIG_GPIO_MVEBU) += gpio-mvebu.o
++obj-$(CONFIG_GPIO_MVEBU_PWM) += gpio-mvebu-pwm.o
+ obj-$(CONFIG_GPIO_MXC) += gpio-mxc.o
+ obj-$(CONFIG_GPIO_MXS) += gpio-mxs.o
+ obj-$(CONFIG_GPIO_OCTEON) += gpio-octeon.o
+--- /dev/null
++++ b/drivers/gpio/gpio-mvebu-pwm.c
+@@ -0,0 +1,202 @@
++#include <linux/err.h>
++#include <linux/module.h>
++#include <linux/gpio.h>
++#include <linux/pwm.h>
++#include <linux/clk.h>
++#include <linux/platform_device.h>
++#include "gpio-mvebu.h"
++#include "gpiolib.h"
++
++static void __iomem *mvebu_gpioreg_blink_select(struct mvebu_gpio_chip *mvchip)
++{
++ return mvchip->membase + GPIO_BLINK_CNT_SELECT;
++}
++
++static inline struct mvebu_pwm *to_mvebu_pwm(struct pwm_chip *chip)
++{
++ return container_of(chip, struct mvebu_pwm, chip);
++}
++
++static inline struct mvebu_gpio_chip *to_mvchip(struct mvebu_pwm *pwm)
++{
++ return container_of(pwm, struct mvebu_gpio_chip, pwm);
++}
++
++static int mvebu_pwm_request(struct pwm_chip *chip, struct pwm_device *pwmd)
++{
++ struct mvebu_pwm *pwm = to_mvebu_pwm(chip);
++ struct mvebu_gpio_chip *mvchip = to_mvchip(pwm);
++ struct gpio_desc *desc = gpio_to_desc(pwmd->pwm);
++ unsigned long flags;
++ int ret = 0;
++
++ spin_lock_irqsave(&pwm->lock, flags);
++ if (pwm->used) {
++ ret = -EBUSY;
++ } else {
++ if (!desc) {
++ ret = -ENODEV;
++ goto out;
++ }
++ ret = gpiod_request(desc, "mvebu-pwm");
++ if (ret)
++ goto out;
++
++ ret = gpiod_direction_output(desc, 0);
++ if (ret) {
++ gpiod_free(desc);
++ goto out;
++ }
++
++ pwm->pin = pwmd->pwm - mvchip->chip.base;
++ pwm->used = true;
++ }
++
++out:
++ spin_unlock_irqrestore(&pwm->lock, flags);
++ return ret;
++}
++
++static void mvebu_pwm_free(struct pwm_chip *chip, struct pwm_device *pwmd)
++{
++ struct mvebu_pwm *pwm = to_mvebu_pwm(chip);
++ struct gpio_desc *desc = gpio_to_desc(pwmd->pwm);
++ unsigned long flags;
++
++ spin_lock_irqsave(&pwm->lock, flags);
++ gpiod_free(desc);
++ pwm->used = false;
++ spin_unlock_irqrestore(&pwm->lock, flags);
++}
++
++static int mvebu_pwm_config(struct pwm_chip *chip, struct pwm_device *pwmd,
++ int duty_ns, int period_ns)
++{
++ struct mvebu_pwm *pwm = to_mvebu_pwm(chip);
++ struct mvebu_gpio_chip *mvchip = to_mvchip(pwm);
++ unsigned int on, off;
++ unsigned long long val;
++ u32 u;
++
++ val = (unsigned long long) pwm->clk_rate * duty_ns;
++ do_div(val, NSEC_PER_SEC);
++ if (val > UINT_MAX)
++ return -EINVAL;
++ if (val)
++ on = val;
++ else
++ on = 1;
++
++ val = (unsigned long long) pwm->clk_rate * (period_ns - duty_ns);
++ do_div(val, NSEC_PER_SEC);
++ if (val > UINT_MAX)
++ return -EINVAL;
++ if (val)
++ off = val;
++ else
++ off = 1;
++
++ u = readl_relaxed(mvebu_gpioreg_blink_select(mvchip));
++ u &= ~(1 << pwm->pin);
++ u |= (pwm->id << pwm->pin);
++ writel_relaxed(u, mvebu_gpioreg_blink_select(mvchip));
++
++ writel_relaxed(on, pwm->membase + BLINK_ON_DURATION);
++ writel_relaxed(off, pwm->membase + BLINK_OFF_DURATION);
++
++ return 0;
++}
++
++static int mvebu_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwmd)
++{
++ struct mvebu_pwm *pwm = to_mvebu_pwm(chip);
++ struct mvebu_gpio_chip *mvchip = to_mvchip(pwm);
++
++ mvebu_gpio_blink(&mvchip->chip, pwm->pin, 1);
++
++ return 0;
++}
++
++static void mvebu_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwmd)
++{
++ struct mvebu_pwm *pwm = to_mvebu_pwm(chip);
++ struct mvebu_gpio_chip *mvchip = to_mvchip(pwm);
++
++ mvebu_gpio_blink(&mvchip->chip, pwm->pin, 0);
++}
++
++static const struct pwm_ops mvebu_pwm_ops = {
++ .request = mvebu_pwm_request,
++ .free = mvebu_pwm_free,
++ .config = mvebu_pwm_config,
++ .enable = mvebu_pwm_enable,
++ .disable = mvebu_pwm_disable,
++ .owner = THIS_MODULE,
++};
++
++void mvebu_pwm_suspend(struct mvebu_gpio_chip *mvchip)
++{
++ struct mvebu_pwm *pwm = &mvchip->pwm;
++
++ pwm->blink_select = readl_relaxed(mvebu_gpioreg_blink_select(mvchip));
++ pwm->blink_on_duration =
++ readl_relaxed(pwm->membase + BLINK_ON_DURATION);
++ pwm->blink_off_duration =
++ readl_relaxed(pwm->membase + BLINK_OFF_DURATION);
++}
++
++void mvebu_pwm_resume(struct mvebu_gpio_chip *mvchip)
++{
++ struct mvebu_pwm *pwm = &mvchip->pwm;
++
++ writel_relaxed(pwm->blink_select, mvebu_gpioreg_blink_select(mvchip));
++ writel_relaxed(pwm->blink_on_duration,
++ pwm->membase + BLINK_ON_DURATION);
++ writel_relaxed(pwm->blink_off_duration,
++ pwm->membase + BLINK_OFF_DURATION);
++}
++
++/*
++ * Armada 370/XP has simple PWM support for gpio lines. Other SoCs
++ * don't have this hardware. So if we don't have the necessary
++ * resource, it is not an error.
++ */
++int mvebu_pwm_probe(struct platform_device *pdev,
++ struct mvebu_gpio_chip *mvchip,
++ int id)
++{
++ struct device *dev = &pdev->dev;
++ struct mvebu_pwm *pwm = &mvchip->pwm;
++ struct resource *res;
++
++ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pwm");
++ if (!res)
++ return 0;
++
++ mvchip->pwm.membase = devm_ioremap_resource(&pdev->dev, res);
++ if (IS_ERR(mvchip->pwm.membase))
++ return PTR_ERR(mvchip->percpu_membase);
++
++ if (id < 0 || id > 1)
++ return -EINVAL;
++ pwm->id = id;
++
++ if (IS_ERR(mvchip->clk))
++ return PTR_ERR(mvchip->clk);
++
++ pwm->clk_rate = clk_get_rate(mvchip->clk);
++ if (!pwm->clk_rate) {
++ dev_err(dev, "failed to get clock rate\n");
++ return -EINVAL;
++ }
++
++ pwm->chip.dev = dev;
++ pwm->chip.ops = &mvebu_pwm_ops;
++ pwm->chip.base = mvchip->chip.base;
++ pwm->chip.npwm = mvchip->chip.ngpio;
++ pwm->chip.can_sleep = false;
++
++ spin_lock_init(&pwm->lock);
++
++ return pwmchip_add(&pwm->chip);
++}
+--- a/drivers/gpio/gpio-mvebu.c
++++ b/drivers/gpio/gpio-mvebu.c
+@@ -42,10 +42,11 @@
+ #include <linux/io.h>
+ #include <linux/of_irq.h>
+ #include <linux/of_device.h>
++#include <linux/pwm.h>
+ #include <linux/clk.h>
+ #include <linux/pinctrl/consumer.h>
+ #include <linux/irqchip/chained_irq.h>
+-
++#include "gpio-mvebu.h"
+ /*
+ * GPIO unit register offsets.
+ */
+@@ -75,24 +76,6 @@
+
+ #define MVEBU_MAX_GPIO_PER_BANK 32
+
+-struct mvebu_gpio_chip {
+- struct gpio_chip chip;
+- spinlock_t lock;
+- void __iomem *membase;
+- void __iomem *percpu_membase;
+- int irqbase;
+- struct irq_domain *domain;
+- int soc_variant;
+-
+- /* Used to preserve GPIO registers across suspend/resume */
+- u32 out_reg;
+- u32 io_conf_reg;
+- u32 blink_en_reg;
+- u32 in_pol_reg;
+- u32 edge_mask_regs[4];
+- u32 level_mask_regs[4];
+-};
+-
+ /*
+ * Functions returning addresses of individual registers for a given
+ * GPIO controller.
+@@ -218,7 +201,7 @@ static int mvebu_gpio_get(struct gpio_ch
+ return (u >> pin) & 1;
+ }
+
+-static void mvebu_gpio_blink(struct gpio_chip *chip, unsigned pin, int value)
++void mvebu_gpio_blink(struct gpio_chip *chip, unsigned pin, int value)
+ {
+ struct mvebu_gpio_chip *mvchip =
+ container_of(chip, struct mvebu_gpio_chip, chip);
+@@ -607,6 +590,8 @@ static int mvebu_gpio_suspend(struct pla
+ BUG();
+ }
+
++ mvebu_pwm_suspend(mvchip);
++
+ return 0;
+ }
+
+@@ -650,6 +635,8 @@ static int mvebu_gpio_resume(struct plat
+ BUG();
+ }
+
++ mvebu_pwm_resume(mvchip);
++
+ return 0;
+ }
+
+@@ -661,7 +648,6 @@ static int mvebu_gpio_probe(struct platf
+ struct resource *res;
+ struct irq_chip_generic *gc;
+ struct irq_chip_type *ct;
+- struct clk *clk;
+ unsigned int ngpios;
+ int soc_variant;
+ int i, cpu, id;
+@@ -691,10 +677,10 @@ static int mvebu_gpio_probe(struct platf
+ return id;
+ }
+
+- clk = devm_clk_get(&pdev->dev, NULL);
++ mvchip->clk = devm_clk_get(&pdev->dev, NULL);
+ /* Not all SoCs require a clock.*/
+- if (!IS_ERR(clk))
+- clk_prepare_enable(clk);
++ if (!IS_ERR(mvchip->clk))
++ clk_prepare_enable(mvchip->clk);
+
+ mvchip->soc_variant = soc_variant;
+ mvchip->chip.label = dev_name(&pdev->dev);
+@@ -828,7 +814,8 @@ static int mvebu_gpio_probe(struct platf
+ goto err_generic_chip;
+ }
+
+- return 0;
++ /* Armada 370/XP has simple PWM support for gpio lines */
++ return mvebu_pwm_probe(pdev, mvchip, id);
+
+ err_generic_chip:
+ irq_remove_generic_chip(gc, IRQ_MSK(ngpios), IRQ_NOREQUEST,
+--- /dev/null
++++ b/drivers/gpio/gpio-mvebu.h
+@@ -0,0 +1,79 @@
++/*
++ * Interface between MVEBU GPIO driver and PWM driver for GPIO pins
++ *
++ * Copyright (C) 2015, Andrew Lunn <andrew@lunn.ch>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2 as
++ * published by the Free Software Foundation.
++ */
++
++#ifndef MVEBU_GPIO_PWM_H
++#define MVEBU_GPIO_PWM_H
++
++#define BLINK_ON_DURATION 0x0
++#define BLINK_OFF_DURATION 0x4
++#define GPIO_BLINK_CNT_SELECT 0x0020
++
++struct mvebu_pwm {
++ void __iomem *membase;
++ unsigned long clk_rate;
++ bool used;
++ unsigned pin;
++ struct pwm_chip chip;
++ int id;
++ spinlock_t lock;
++
++ /* Used to preserve GPIO/PWM registers across suspend /
++ * resume */
++ u32 blink_select;
++ u32 blink_on_duration;
++ u32 blink_off_duration;
++};
++
++struct mvebu_gpio_chip {
++ struct gpio_chip chip;
++ spinlock_t lock;
++ void __iomem *membase;
++ void __iomem *percpu_membase;
++ int irqbase;
++ struct irq_domain *domain;
++ int soc_variant;
++ struct clk *clk;
++#ifdef CONFIG_PWM
++ struct mvebu_pwm pwm;
++#endif
++ /* Used to preserve GPIO registers across suspend/resume */
++ u32 out_reg;
++ u32 io_conf_reg;
++ u32 blink_en_reg;
++ u32 in_pol_reg;
++ u32 edge_mask_regs[4];
++ u32 level_mask_regs[4];
++};
++
++void mvebu_gpio_blink(struct gpio_chip *chip, unsigned pin, int value);
++
++#ifdef CONFIG_PWM
++int mvebu_pwm_probe(struct platform_device *pdev,
++ struct mvebu_gpio_chip *mvchip,
++ int id);
++void mvebu_pwm_suspend(struct mvebu_gpio_chip *mvchip);
++void mvebu_pwm_resume(struct mvebu_gpio_chip *mvchip);
++#else
++int mvebu_pwm_probe(struct platform_device *pdev,
++ struct mvebu_gpio_chip *mvchip,
++ int id)
++{
++ return 0;
++}
++
++void mvebu_pwm_suspend(struct mvebu_gpio_chip *mvchip)
++{
++}
++
++void mvebu_pwm_resume(struct mvebu_gpio_chip *mvchip)
++{
++}
++#endif
++#endif
--- /dev/null
+Document the optional parameters needed for PWM operation of gpio
+lines.
+
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+---
+ .../devicetree/bindings/gpio/gpio-mvebu.txt | 31 ++++++++++++++++++++++
+ 1 file changed, 31 insertions(+)
+
+--- a/Documentation/devicetree/bindings/gpio/gpio-mvebu.txt
++++ b/Documentation/devicetree/bindings/gpio/gpio-mvebu.txt
+@@ -38,6 +38,23 @@ Required properties:
+ - #gpio-cells: Should be two. The first cell is the pin number. The
+ second cell is reserved for flags, unused at the moment.
+
++Optional properties:
++
++In order to use the gpio lines in PWM mode, some additional optional
++properties are required. Only Armada 370 and XP supports these
++properties.
++
++- reg: an additional register set is needed, for the GPIO Blink
++ Counter on/off registers.
++
++- reg-names: Must contain an entry "pwm" corresponding to the
++ additional register range needed for pwm operation.
++
++- #pwm-cells: Should be two. The first cell is the pin number. The
++ second cell is reserved for flags, unused at the moment.
++
++- clocks: Must be a phandle to the clock for the gpio controller.
++
+ Example:
+
+ gpio0: gpio@d0018100 {
+@@ -51,3 +68,17 @@ Example:
+ #interrupt-cells = <2>;
+ interrupts = <16>, <17>, <18>, <19>;
+ };
++
++ gpio1: gpio@18140 {
++ compatible = "marvell,orion-gpio";
++ reg = <0x18140 0x40>, <0x181c8 0x08>;
++ reg-names = "gpio", "pwm";
++ ngpios = <17>;
++ gpio-controller;
++ #gpio-cells = <2>;
++ #pwm-cells = <2>;
++ interrupt-controller;
++ #interrupt-cells = <2>;
++ interrupts = <87>, <88>, <89>;
++ clocks = <&coreclk 0>;
++ };
--- /dev/null
+Add properties to the gpio nodes to allow them to be also used
+as pwm lines.
+
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+---
+ arch/arm/boot/dts/armada-370.dtsi | 10 ++++++++--
+ arch/arm/boot/dts/armada-xp-mv78230.dtsi | 10 ++++++++--
+ arch/arm/boot/dts/armada-xp-mv78260.dtsi | 8 ++++++--
+ arch/arm/boot/dts/armada-xp-mv78460.dtsi | 10 ++++++++--
+ 4 files changed, 30 insertions(+), 8 deletions(-)
+
+--- a/arch/arm/boot/dts/armada-370.dtsi
++++ b/arch/arm/boot/dts/armada-370.dtsi
+@@ -162,24 +162,30 @@
+
+ gpio0: gpio@18100 {
+ compatible = "marvell,orion-gpio";
+- reg = <0x18100 0x40>;
++ reg = <0x18100 0x40>, <0x181c0 0x08>;
++ reg-names = "gpio", "pwm";
+ ngpios = <32>;
+ gpio-controller;
+ #gpio-cells = <2>;
++ #pwm-cells = <2>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ interrupts = <82>, <83>, <84>, <85>;
++ clocks = <&coreclk 0>;
+ };
+
+ gpio1: gpio@18140 {
+ compatible = "marvell,orion-gpio";
+- reg = <0x18140 0x40>;
++ reg = <0x18140 0x40>, <0x181c8 0x08>;
++ reg-names = "gpio", "pwm";
+ ngpios = <32>;
+ gpio-controller;
+ #gpio-cells = <2>;
++ #pwm-cells = <2>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ interrupts = <87>, <88>, <89>, <90>;
++ clocks = <&coreclk 0>;
+ };
+
+ gpio2: gpio@18180 {
+--- a/arch/arm/boot/dts/armada-xp-mv78230.dtsi
++++ b/arch/arm/boot/dts/armada-xp-mv78230.dtsi
+@@ -203,24 +203,30 @@
+ internal-regs {
+ gpio0: gpio@18100 {
+ compatible = "marvell,orion-gpio";
+- reg = <0x18100 0x40>;
++ reg = <0x18100 0x40>, <0x181c0 0x08>;
++ reg-names = "gpio", "pwm";
+ ngpios = <32>;
+ gpio-controller;
+ #gpio-cells = <2>;
++ #pwm-cells = <2>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ interrupts = <82>, <83>, <84>, <85>;
++ clocks = <&coreclk 0>;
+ };
+
+ gpio1: gpio@18140 {
+ compatible = "marvell,orion-gpio";
+- reg = <0x18140 0x40>;
++ reg = <0x18140 0x40>, <0x181c8 0x08>;
++ reg-names = "gpio", "pwm";
+ ngpios = <17>;
+ gpio-controller;
+ #gpio-cells = <2>;
++ #pwm-cells = <2>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ interrupts = <87>, <88>, <89>;
++ clocks = <&coreclk 0>;
+ };
+ };
+ };
+--- a/arch/arm/boot/dts/armada-xp-mv78260.dtsi
++++ b/arch/arm/boot/dts/armada-xp-mv78260.dtsi
+@@ -286,24 +286,28 @@
+ internal-regs {
+ gpio0: gpio@18100 {
+ compatible = "marvell,orion-gpio";
+- reg = <0x18100 0x40>;
++ reg = <0x18100 0x40>, <0x181c0 0x08>;
++ reg-names = "gpio", "pwm";
+ ngpios = <32>;
+ gpio-controller;
+ #gpio-cells = <2>;
++ #pwm-cells = <2>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ interrupts = <82>, <83>, <84>, <85>;
++ clocks = <&coreclk 0>;
+ };
+
+ gpio1: gpio@18140 {
+ compatible = "marvell,orion-gpio";
+- reg = <0x18140 0x40>;
++ reg = <0x18140 0x40>, <0x181c8 0x08>;
+ ngpios = <32>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ interrupts = <87>, <88>, <89>, <90>;
++ clocks = <&coreclk 0>;
+ };
+
+ gpio2: gpio@18180 {
+--- a/arch/arm/boot/dts/armada-xp-mv78460.dtsi
++++ b/arch/arm/boot/dts/armada-xp-mv78460.dtsi
+@@ -324,24 +324,30 @@
+ internal-regs {
+ gpio0: gpio@18100 {
+ compatible = "marvell,orion-gpio";
+- reg = <0x18100 0x40>;
++ reg = <0x18100 0x40>, <0x181c0 0x08>;
++ reg-names = "gpio", "pwm";
+ ngpios = <32>;
+ gpio-controller;
+ #gpio-cells = <2>;
++ #pwm-cells = <2>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ interrupts = <82>, <83>, <84>, <85>;
++ clocks = <&coreclk 0>;
+ };
+
+ gpio1: gpio@18140 {
+ compatible = "marvell,orion-gpio";
+- reg = <0x18140 0x40>;
++ reg = <0x18140 0x40>, <0x181c8 0x08>;
++ reg-names = "gpio", "pwm";
+ ngpios = <32>;
+ gpio-controller;
+ #gpio-cells = <2>;
++ #pwm-cells = <2>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ interrupts = <87>, <88>, <89>, <90>;
++ clocks = <&coreclk 0>;
+ };
+
+ gpio2: gpio@18180 {
--- /dev/null
+Now that the gpio driver also supports PWM operation, enable
+the PWM framework in mvebu_v7_defconfig.
+
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+---
+ arch/arm/configs/mvebu_v7_defconfig | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/arch/arm/configs/mvebu_v7_defconfig
++++ b/arch/arm/configs/mvebu_v7_defconfig
+@@ -131,6 +131,7 @@ CONFIG_DMADEVICES=y
+ CONFIG_MV_XOR=y
+ # CONFIG_IOMMU_SUPPORT is not set
+ CONFIG_MEMORY=y
++CONFIG_PWM=y
+ CONFIG_EXT4_FS=y
+ CONFIG_ISO9660_FS=y
+ CONFIG_JOLIET=y
--- /dev/null
+The mvebu gpio driver can also perform PWM on some pins. Us the
+pwm-fan driver to control the fan of the WRT1900AC, giving us fine
+grain control over its speed and hence noise.
+
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+---
+ arch/arm/boot/dts/armada-xp-wrt1900ac.dts | 8 +++-----
+ 1 file changed, 3 insertions(+), 5 deletions(-)
+
+--- a/arch/arm/boot/dts/armada-xp-linksys-mamba.dts
++++ b/arch/arm/boot/dts/armada-xp-linksys-mamba.dts
+@@ -428,13 +428,11 @@
+ };
+ };
+
+- gpio_fan {
++ pwm_fan {
+ /* SUNON HA4010V4-0000-C99 */
+- compatible = "gpio-fan";
+- gpios = <&gpio0 24 0>;
+
+- gpio-fan,speed-map = <0 0
+- 4500 1>;
++ compatible = "pwm-fan";
++ pwms = <&gpio0 24 4000 0>;
+ };
+
+ dsa@0 {
--- /dev/null
+--- a/arch/arm/boot/dts/armada-388-rd.dts
++++ b/arch/arm/boot/dts/armada-388-rd.dts
+@@ -79,6 +79,16 @@
+ compatible = "st,m25p128", "jedec,spi-nor";
+ reg = <0>; /* Chip select 0 */
+ spi-max-frequency = <108000000>;
++
++ partition@0 {
++ label = "uboot";
++ reg = <0 0x400000>;
++ };
++
++ partition@1 {
++ label = "firmware";
++ reg = <0x400000 0xc00000>;
++ };
+ };
+ };
+
--- /dev/null
+From 9861f93a59142a3131870df2521eb2deb73026d7 Mon Sep 17 00:00:00 2001
+From: Maxime Ripard <maxime.ripard@free-electrons.com>
+Date: Tue, 13 Jan 2015 11:14:09 +0100
+Subject: [PATCH 2/2] ARM: mvebu: 385-ap: Add partitions
+
+Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
+---
+ arch/arm/boot/dts/armada-385-db-ap.dts | 15 +++++++++++++++
+ 1 file changed, 15 insertions(+)
+
+--- a/arch/arm/boot/dts/armada-385-db-ap.dts
++++ b/arch/arm/boot/dts/armada-385-db-ap.dts
+@@ -184,6 +184,21 @@
+ marvell,nand-keep-config;
+ marvell,nand-enable-arbiter;
+ nand-on-flash-bbt;
++
++ mtd0@00000000 {
++ label = "u-boot";
++ reg = <0x00000000 0x00800000>;
++ };
++
++ mtd1@00800000 {
++ label = "kernel";
++ reg = <0x00800000 0x00800000>;
++ };
++
++ mtd2@01000000 {
++ label = "ubi";
++ reg = <0x01000000 0x3f000000>;
++ };
+ };
+
+ usb3@f0000 {
--- /dev/null
+--- a/arch/arm/boot/dts/armada-388-clearfog.dts
++++ b/arch/arm/boot/dts/armada-388-clearfog.dts
+@@ -430,6 +430,18 @@
+ };
+ };
+
++ mvsw61xx {
++ #address-cells = <1>;
++ #size-cells = <0>;
++ compatible = "marvell,88e6176";
++ status = "okay";
++ reg = <0x4>;
++ is-indirect;
++
++ mii-bus = <&mdio>;
++ cpu-port-0 = <5>;
++ };
++
+ gpio-keys {
+ compatible = "gpio-keys";
+ pinctrl-0 = <&rear_button_pins>;
--- /dev/null
+From 63ff73593c2f5d3fc1cba479321d192caaca48aa Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Sat, 12 Sep 2015 18:43:39 +0100
+Subject: [PATCH 738/744] ARM: dts: armada388-clearfog: add SFP module support
+
+Add SFP module support for Clearfog using the SFP phylink support.
+
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ arch/arm/boot/dts/armada-388-clearfog.dts | 44 ++++++++-----------------------
+ 1 file changed, 11 insertions(+), 33 deletions(-)
+
+--- a/arch/arm/boot/dts/armada-388-clearfog.dts
++++ b/arch/arm/boot/dts/armada-388-clearfog.dts
+@@ -90,16 +90,12 @@
+ };
+
+ ethernet@34000 {
++ managed = "in-band-status";
+ phy-mode = "sgmii";
+ buffer-manager = <&bm>;
+ bm,pool-long = <3>;
+ bm,pool-short = <1>;
+ status = "okay";
+-
+- fixed-link {
+- speed = <1000>;
+- full-duplex;
+- };
+ };
+
+ i2c@11000 {
+@@ -183,34 +179,6 @@
+ output-low;
+ line-name = "m.2 devslp";
+ };
+- sfp_los {
+- /* SFP loss of signal */
+- gpio-hog;
+- gpios = <12 GPIO_ACTIVE_HIGH>;
+- input;
+- line-name = "sfp-los";
+- };
+- sfp_tx_fault {
+- /* SFP laser fault */
+- gpio-hog;
+- gpios = <13 GPIO_ACTIVE_HIGH>;
+- input;
+- line-name = "sfp-tx-fault";
+- };
+- sfp_tx_disable {
+- /* SFP transmit disable */
+- gpio-hog;
+- gpios = <14 GPIO_ACTIVE_HIGH>;
+- output-low;
+- line-name = "sfp-tx-disable";
+- };
+- sfp_mod_def0 {
+- /* SFP module present */
+- gpio-hog;
+- gpios = <15 GPIO_ACTIVE_LOW>;
+- input;
+- line-name = "sfp-mod-def0";
+- };
+ };
+
+ /* The MCP3021 is 100kHz clock only */
+@@ -374,6 +342,16 @@
+ };
+ };
+
++ sfp: sfp {
++ compatible = "sff,sfp";
++ i2c-bus = <&i2c1>;
++ los-gpio = <&expander0 12 GPIO_ACTIVE_HIGH>;
++ moddef0-gpio = <&expander0 15 GPIO_ACTIVE_LOW>;
++ sfp,ethernet = <ð2>;
++ tx-disable-gpio = <&expander0 14 GPIO_ACTIVE_HIGH>;
++ tx-fault-gpio = <&expander0 13 GPIO_ACTIVE_HIGH>;
++ };
++
+ dsa@0 {
+ compatible = "marvell,dsa";
+ dsa,ethernet = <ð1>;
--- /dev/null
+From 28baa5e2635285b178326b301f534ed95c65dd01 Mon Sep 17 00:00:00 2001
+From: Jonas Gorski <jonas.gorski@gmail.com>
+Date: Thu, 29 Sep 2016 11:44:39 +0200
+Subject: [PATCH] sfp: retry phy probe if unsuccessful
+
+Some phys seem to take longer than 50 ms to come out of reset, so retry
+until we find a phy.
+
+Signed-off-by: Jonas Gorski <jonas.gorski@gmail.com>
+---
+ drivers/net/phy/sfp.c | 38 +++++++++++++++++++++++++-------------
+ 1 file changed, 25 insertions(+), 13 deletions(-)
+
+--- a/drivers/net/phy/sfp.c
++++ b/drivers/net/phy/sfp.c
+@@ -488,7 +488,7 @@ static void sfp_sm_phy_detach(struct sfp
+ sfp->mod_phy = NULL;
+ }
+
+-static void sfp_sm_probe_phy(struct sfp *sfp)
++static int sfp_sm_probe_phy(struct sfp *sfp)
+ {
+ struct phy_device *phy;
+ int err;
+@@ -498,11 +498,11 @@ static void sfp_sm_probe_phy(struct sfp
+ phy = mdiobus_scan(sfp->i2c_mii, SFP_PHY_ADDR);
+ if (IS_ERR(phy)) {
+ dev_err(sfp->dev, "mdiobus scan returned %ld\n", PTR_ERR(phy));
+- return;
++ return PTR_ERR(phy);
+ }
+ if (!phy) {
+- dev_info(sfp->dev, "no PHY detected\n");
+- return;
++ dev_dbg(sfp->dev, "no PHY detected\n");
++ return -EAGAIN;
+ }
+
+ err = phylink_connect_phy(sfp->phylink, phy);
+@@ -510,11 +510,13 @@ static void sfp_sm_probe_phy(struct sfp
+ phy_device_remove(phy);
+ phy_device_free(phy);
+ dev_err(sfp->dev, "phylink_connect_phy failed: %d\n", err);
+- return;
++ return err;
+ }
+
+ sfp->mod_phy = phy;
+ phy_start(phy);
++
++ return 0;
+ }
+
+ static void sfp_sm_link_up(struct sfp *sfp)
+@@ -565,13 +567,6 @@ static void sfp_sm_mod_init(struct sfp *
+ {
+ sfp_module_tx_enable(sfp);
+
+- /* Wait t_init before indicating that the link is up, provided the
+- * current state indicates no TX_FAULT. If TX_FAULT clears before
+- * this time, that's fine too.
+- */
+- sfp_sm_next(sfp, SFP_S_INIT, T_INIT_JIFFIES);
+- sfp->sm_retries = 5;
+-
+ if (sfp->phylink) {
+ /* Setting the serdes link mode is guesswork: there's no
+ * field in the EEPROM which indicates what mode should
+@@ -587,9 +582,26 @@ static void sfp_sm_mod_init(struct sfp *
+ !sfp->id.base.e100_base_fx) {
+ phylink_set_link_an_mode(sfp->phylink, MLO_AN_8023Z);
+ } else {
++ int ret;
++
+ phylink_set_link_an_mode(sfp->phylink, MLO_AN_SGMII);
+- sfp_sm_probe_phy(sfp);
++
++ ret = sfp_sm_probe_phy(sfp);
++ if (ret) {
++ if (ret == -EAGAIN)
++ sfp_sm_set_timer(sfp, T_PROBE_RETRY);
++ else
++ sfp_sm_next(sfp, SFP_S_TX_DISABLE, 0);
++ return;
++ }
+ }
++
++ /* Wait t_init before indicating that the link is up, provided the
++ * current state indicates no TX_FAULT. If TX_FAULT clears before
++ * this time, that's fine too.
++ */
++ sfp_sm_next(sfp, SFP_S_INIT, T_INIT_JIFFIES);
++ sfp->sm_retries = 5;
+ }
+ }
+
--- /dev/null
+The hardware queue scheduling is apparently configured with fixed
+priorities, which creates a nasty fairness issue where traffic from one
+CPU can starve traffic from all other CPUs.
+
+Work around this issue by forcing all tx packets to go through one CPU,
+until this issue is fixed properly.
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+--- a/drivers/net/ethernet/marvell/mvneta.c
++++ b/drivers/net/ethernet/marvell/mvneta.c
+@@ -3984,6 +3984,16 @@ static int mvneta_ethtool_set_eee(struct
+ return phylink_ethtool_set_eee(pp->phylink, eee);
+ }
+
++static u16 mvneta_select_queue(struct net_device *dev, struct sk_buff *skb,
++ void *accel_priv,
++ select_queue_fallback_t fallback)
++{
++ /* XXX: hardware queue scheduling is broken,
++ * use only one queue until it is fixed */
++ return 0;
++}
++
++
+ static const struct net_device_ops mvneta_netdev_ops = {
+ .ndo_open = mvneta_open,
+ .ndo_stop = mvneta_stop,
+@@ -3994,6 +4004,7 @@ static const struct net_device_ops mvnet
+ .ndo_fix_features = mvneta_fix_features,
+ .ndo_get_stats64 = mvneta_get_stats64,
+ .ndo_do_ioctl = mvneta_ioctl,
++ .ndo_select_queue = mvneta_select_queue,
+ };
+
+ const struct ethtool_ops mvneta_eth_tool_ops = {