--- /dev/null
+CONFIG_64BIT=y
+CONFIG_AHCI_OCTEON=y
+CONFIG_ARCH_DMA_ADDR_T_64BIT=y
+CONFIG_ARCH_HIBERNATION_POSSIBLE=y
+CONFIG_ARCH_KEEP_MEMBLOCK=y
+CONFIG_ARCH_MMAP_RND_BITS=12
+CONFIG_ARCH_MMAP_RND_BITS_MAX=18
+CONFIG_ARCH_MMAP_RND_BITS_MIN=12
+CONFIG_ARCH_MMAP_RND_COMPAT_BITS_MAX=15
+CONFIG_ARCH_SPARSEMEM_ENABLE=y
+CONFIG_ARCH_SUSPEND_POSSIBLE=y
+CONFIG_ATA=y
+CONFIG_BLK_DEV_LOOP=y
+CONFIG_BLK_DEV_SD=y
+CONFIG_BLK_MQ_PCI=y
+CONFIG_BUILTIN_DTB=y
+CONFIG_CAVIUM_CN63XXP1=y
+CONFIG_CAVIUM_OCTEON_CVMSEG_SIZE=0
+CONFIG_CAVIUM_OCTEON_LOCK_L2=y
+CONFIG_CAVIUM_OCTEON_LOCK_L2_EXCEPTION=y
+CONFIG_CAVIUM_OCTEON_LOCK_L2_INTERRUPT=y
+CONFIG_CAVIUM_OCTEON_LOCK_L2_LOW_LEVEL_INTERRUPT=y
+CONFIG_CAVIUM_OCTEON_LOCK_L2_MEMCPY=y
+CONFIG_CAVIUM_OCTEON_LOCK_L2_TLB=y
+CONFIG_CAVIUM_OCTEON_SOC=y
+CONFIG_CAVIUM_RESERVE32=0
+CONFIG_CC_IMPLICIT_FALLTHROUGH="-Wimplicit-fallthrough=5"
+CONFIG_CC_NO_ARRAY_BOUNDS=y
+CONFIG_CEVT_R4K=y
+CONFIG_CLONE_BACKWARDS=y
+# CONFIG_COMMON_CLK is not set
+CONFIG_COMPACT_UNEVICTABLE_DEFAULT=1
+CONFIG_COMPAT_32BIT_TIME=y
+CONFIG_CONTEXT_SWITCH_TRACER=y
+CONFIG_CONTEXT_TRACKING=y
+CONFIG_CONTEXT_TRACKING_IDLE=y
+CONFIG_CPU_BIG_ENDIAN=y
+CONFIG_CPU_CAVIUM_OCTEON=y
+CONFIG_CPU_GENERIC_DUMP_TLB=y
+CONFIG_CPU_HAS_DIEI=y
+CONFIG_CPU_HAS_PREFETCH=y
+CONFIG_CPU_HAS_RIXI=y
+CONFIG_CPU_HAS_SYNC=y
+CONFIG_CPU_MIPS64=y
+CONFIG_CPU_MIPSR2=y
+CONFIG_CPU_NEEDS_NO_SMARTMIPS_OR_MICROMIPS=y
+CONFIG_CPU_R4K_FPU=y
+CONFIG_CPU_RMAP=y
+CONFIG_CPU_SUPPORTS_64BIT_KERNEL=y
+CONFIG_CPU_SUPPORTS_HIGHMEM=y
+CONFIG_CPU_SUPPORTS_HUGEPAGES=y
+CONFIG_CRAMFS=y
+CONFIG_CRC16=y
+CONFIG_CRYPTO_CRC32=y
+CONFIG_CRYPTO_CRC32C=y
+CONFIG_CRYPTO_LIB_BLAKE2S_GENERIC=y
+CONFIG_CRYPTO_LIB_POLY1305_RSIZE=2
+CONFIG_CRYPTO_LIB_SHA1=y
+CONFIG_CRYPTO_LIB_UTILS=y
+# CONFIG_CRYPTO_MD5_OCTEON is not set
+CONFIG_CRYPTO_RNG2=y
+# CONFIG_CRYPTO_SHA1_OCTEON is not set
+# CONFIG_CRYPTO_SHA256_OCTEON is not set
+# CONFIG_CRYPTO_SHA512_OCTEON is not set
+CONFIG_DEBUG_INFO=y
+CONFIG_DEBUG_LOCK_ALLOC=y
+CONFIG_DEBUG_MUTEXES=y
+CONFIG_DEBUG_RT_MUTEXES=y
+CONFIG_DEBUG_RWSEMS=y
+CONFIG_DEBUG_SPINLOCK=y
+CONFIG_DEBUG_WW_MUTEX_SLOWPATH=y
+CONFIG_DEPRECATED_IRQ_CPU_ONOFFLINE=y
+CONFIG_DNOTIFY=y
+CONFIG_DTC=y
+CONFIG_EARLY_PRINTK=y
+CONFIG_EDAC=y
+CONFIG_EDAC_ATOMIC_SCRUB=y
+# CONFIG_EDAC_DEBUG is not set
+CONFIG_EDAC_LEGACY_SYSFS=y
+CONFIG_EDAC_OCTEON_L2C=y
+CONFIG_EDAC_OCTEON_LMC=y
+CONFIG_EDAC_OCTEON_PC=y
+CONFIG_EDAC_OCTEON_PCI=y
+CONFIG_EDAC_SUPPORT=y
+CONFIG_EEPROM_AT24=y
+CONFIG_EVENT_TRACING=y
+CONFIG_EXCLUSIVE_SYSTEM_RAM=y
+CONFIG_EXT4_FS=y
+CONFIG_F2FS_FS=y
+CONFIG_FAT_FS=y
+CONFIG_FIXED_PHY=y
+CONFIG_FS_IOMAP=y
+CONFIG_FS_MBCACHE=y
+CONFIG_FWNODE_MDIO=y
+CONFIG_FW_LOADER_PAGED_BUF=y
+CONFIG_FW_LOADER_SYSFS=y
+CONFIG_GCC11_NO_ARRAY_BOUNDS=y
+CONFIG_GENERIC_ALLOCATOR=y
+CONFIG_GENERIC_CLOCKEVENTS=y
+CONFIG_GENERIC_CMOS_UPDATE=y
+CONFIG_GENERIC_CPU_AUTOPROBE=y
+CONFIG_GENERIC_GETTIMEOFDAY=y
+CONFIG_GENERIC_IOMAP=y
+CONFIG_GENERIC_IRQ_SHOW=y
+CONFIG_GENERIC_LIB_ASHLDI3=y
+CONFIG_GENERIC_LIB_ASHRDI3=y
+CONFIG_GENERIC_LIB_CMPDI2=y
+CONFIG_GENERIC_LIB_LSHRDI3=y
+CONFIG_GENERIC_LIB_UCMPDI2=y
+CONFIG_GENERIC_PCI_IOMAP=y
+CONFIG_GENERIC_SMP_IDLE_THREAD=y
+CONFIG_GENERIC_TIME_VSYSCALL=y
+CONFIG_GLOB=y
+CONFIG_GPIOLIB_IRQCHIP=y
+CONFIG_GPIO_CDEV=y
+CONFIG_GPIO_CDEV_V1=y
+CONFIG_GPIO_OCTEON=y
+CONFIG_GPIO_PCA953X=y
+CONFIG_GPIO_PCA953X_IRQ=y
+CONFIG_GRO_CELLS=y
+CONFIG_HARDWARE_WATCHPOINTS=y
+CONFIG_HAS_DMA=y
+CONFIG_HAS_IOMEM=y
+CONFIG_HAS_IOPORT_MAP=y
+CONFIG_HW_RANDOM=y
+CONFIG_HW_RANDOM_OCTEON=y
+CONFIG_HZ_PERIODIC=y
+CONFIG_I2C=y
+CONFIG_I2C_BOARDINFO=y
+CONFIG_I2C_OCTEON=y
+CONFIG_INITRAMFS_SOURCE=""
+CONFIG_IRQCHIP=y
+CONFIG_IRQ_DOMAIN=y
+CONFIG_IRQ_FORCED_THREADING=y
+CONFIG_IRQ_WORK=y
+CONFIG_JBD2=y
+CONFIG_KALLSYMS=y
+CONFIG_KALLSYMS_ALL=y
+CONFIG_LIBFDT=y
+CONFIG_LOCKDEP=y
+CONFIG_LOCK_DEBUGGING_SUPPORT=y
+CONFIG_MDIO_BUS=y
+CONFIG_MDIO_CAVIUM=y
+CONFIG_MDIO_DEVICE=y
+CONFIG_MDIO_DEVRES=y
+CONFIG_MDIO_I2C=y
+CONFIG_MDIO_OCTEON=y
+CONFIG_MEMFD_CREATE=y
+CONFIG_MIGRATION=y
+CONFIG_MIPS=y
+CONFIG_MIPS_ASID_BITS=8
+CONFIG_MIPS_ASID_SHIFT=0
+CONFIG_MIPS_CMDLINE_FROM_BOOTLOADER=y
+CONFIG_MIPS_ELF_APPENDED_DTB=y
+CONFIG_MIPS_FP_SUPPORT=y
+CONFIG_MIPS_L1_CACHE_SHIFT=7
+CONFIG_MIPS_L1_CACHE_SHIFT_7=y
+CONFIG_MIPS_LD_CAN_LINK_VDSO=y
+# CONFIG_MIPS_NO_APPENDED_DTB is not set
+CONFIG_MIPS_NR_CPU_NR_MAP=1024
+CONFIG_MIPS_NR_CPU_NR_MAP_1024=y
+CONFIG_MIPS_PGD_C0_CONTEXT=y
+CONFIG_MIPS_SPRAM=y
+CONFIG_MMC=y
+CONFIG_MMC_BLOCK=y
+CONFIG_MMC_CAVIUM_OCTEON=y
+CONFIG_MODULES_TREE_LOOKUP=y
+CONFIG_MODULES_USE_ELF_REL=y
+CONFIG_MODULES_USE_ELF_RELA=y
+# CONFIG_MTD_CFI_INTELEXT is not set
+CONFIG_MTD_CMDLINE_PARTS=y
+CONFIG_MTD_PHYSMAP=y
+CONFIG_MTD_SPI_NOR=y
+CONFIG_MTD_SPI_NOR_USE_4K_SECTORS=y
+CONFIG_NEED_DMA_MAP_STATE=y
+CONFIG_NET_DEVLINK=y
+CONFIG_NET_DSA=y
+CONFIG_NET_FLOW_LIMIT=y
+CONFIG_NET_SELFTESTS=y
+CONFIG_NET_SWITCHDEV=y
+CONFIG_NLS=y
+CONFIG_NLS_CODEPAGE_437=y
+CONFIG_NLS_ISO8859_1=y
+CONFIG_NOP_TRACER=y
+CONFIG_NO_GENERIC_PCI_IOPORT_MAP=y
+CONFIG_NR_CPUS=16
+CONFIG_NR_CPUS_DEFAULT_64=y
+CONFIG_NVMEM=y
+CONFIG_NVMEM_SYSFS=y
+CONFIG_OCTEON_ETHERNET=y
+CONFIG_OCTEON_ILM=y
+CONFIG_OCTEON_MGMT_ETHERNET=y
+CONFIG_OCTEON_WDT=y
+CONFIG_OF=y
+CONFIG_OF_ADDRESS=y
+CONFIG_OF_EARLY_FLATTREE=y
+CONFIG_OF_FLATTREE=y
+CONFIG_OF_GPIO=y
+CONFIG_OF_IRQ=y
+CONFIG_OF_KOBJ=y
+CONFIG_OF_MDIO=y
+CONFIG_PADATA=y
+CONFIG_PAGE_POOL=y
+CONFIG_PAGE_SIZE_LESS_THAN_256KB=y
+CONFIG_PAGE_SIZE_LESS_THAN_64KB=y
+# CONFIG_PARTITION_ADVANCED is not set
+CONFIG_PATA_OCTEON_CF=y
+CONFIG_PATA_TIMINGS=y
+CONFIG_PCI=y
+CONFIG_PCIEAER=y
+CONFIG_PCIEPORTBUS=y
+CONFIG_PCI_DOMAINS=y
+CONFIG_PCI_DRIVERS_LEGACY=y
+CONFIG_PERF_USE_VMALLOC=y
+CONFIG_PGTABLE_LEVELS=3
+CONFIG_PHYLIB=y
+CONFIG_PHYLINK=y
+CONFIG_PHYS_ADDR_T_64BIT=y
+CONFIG_POSIX_MQUEUE=y
+CONFIG_POSIX_MQUEUE_SYSCTL=y
+CONFIG_PREEMPTIRQ_TRACEPOINTS=y
+CONFIG_PREEMPT_COUNT=y
+CONFIG_PREEMPT_NONE_BUILD=y
+CONFIG_PROVE_LOCKING=y
+CONFIG_PROVE_RCU=y
+CONFIG_PTP_1588_CLOCK_OPTIONAL=y
+CONFIG_QUEUED_RWLOCKS=y
+CONFIG_QUEUED_SPINLOCKS=y
+CONFIG_RANDSTRUCT_NONE=y
+CONFIG_RAS=y
+CONFIG_REGMAP=y
+CONFIG_REGMAP_I2C=y
+CONFIG_RELAY=y
+CONFIG_RFS_ACCEL=y
+CONFIG_RING_BUFFER=y
+CONFIG_RPS=y
+CONFIG_SATA_AHCI_PLATFORM=y
+CONFIG_SATA_HOST=y
+CONFIG_SCSI=y
+CONFIG_SCSI_COMMON=y
+CONFIG_SECCOMP=y
+CONFIG_SECCOMP_FILTER=y
+CONFIG_SERIAL_8250_DW=y
+CONFIG_SERIAL_8250_DWLIB=y
+CONFIG_SERIAL_MCTRL_GPIO=y
+CONFIG_SFP=y
+CONFIG_SG_POOL=y
+CONFIG_SMP=y
+CONFIG_SOCK_RX_QUEUE_MAPPING=y
+CONFIG_SPARSEMEM=y
+CONFIG_SPARSEMEM_EXTREME=y
+CONFIG_SPI=y
+CONFIG_SPI_MASTER=y
+CONFIG_SPI_MEM=y
+CONFIG_SPI_OCTEON=y
+CONFIG_SRCU=y
+CONFIG_STACKTRACE=y
+CONFIG_SWIOTLB=y
+CONFIG_SWPHY=y
+CONFIG_SYSCTL_EXCEPTION_TRACE=y
+CONFIG_SYS_HAS_CPU_CAVIUM_OCTEON=y
+CONFIG_SYS_HAS_EARLY_PRINTK=y
+CONFIG_SYS_SUPPORTS_64BIT_KERNEL=y
+CONFIG_SYS_SUPPORTS_ARBIT_HZ=y
+CONFIG_SYS_SUPPORTS_BIG_ENDIAN=y
+CONFIG_SYS_SUPPORTS_HOTPLUG_CPU=y
+CONFIG_SYS_SUPPORTS_LITTLE_ENDIAN=y
+CONFIG_SYS_SUPPORTS_RELOCATABLE=y
+CONFIG_SYS_SUPPORTS_SMP=y
+CONFIG_TARGET_ISA_REV=2
+CONFIG_TICK_CPU_ACCOUNTING=y
+CONFIG_TRACEPOINTS=y
+CONFIG_TRACE_CLOCK=y
+CONFIG_TRACE_IRQFLAGS=y
+CONFIG_TRACING=y
+CONFIG_TREE_RCU=y
+CONFIG_TREE_SRCU=y
+CONFIG_UNINLINE_SPIN_UNLOCK=y
+CONFIG_USB=y
+CONFIG_USB_COMMON=y
+CONFIG_USB_EHCI_BIG_ENDIAN_MMIO=y
+CONFIG_USB_EHCI_HCD=y
+CONFIG_USB_EHCI_HCD_PLATFORM=y
+# CONFIG_USB_OCTEON_EHCI is not set
+CONFIG_USB_OCTEON_HCD=y
+# CONFIG_USB_OCTEON_OHCI is not set
+CONFIG_USB_OHCI_BIG_ENDIAN_MMIO=y
+CONFIG_USB_OHCI_HCD=y
+CONFIG_USB_OHCI_HCD_PLATFORM=y
+CONFIG_USB_STORAGE=y
+CONFIG_USB_SUPPORT=y
+CONFIG_USB_XHCI_HCD=y
+CONFIG_USB_XHCI_PLATFORM=y
+CONFIG_USE_OF=y
+CONFIG_VFAT_FS=y
+CONFIG_VITESSE_PHY=y
+CONFIG_VM_EVENT_COUNTERS=y
+CONFIG_VSC848X_PHY=y
+CONFIG_WATCHDOG_CORE=y
+CONFIG_WEAK_ORDERING=y
+CONFIG_XPS=y
+CONFIG_ZLIB_INFLATE=y
+CONFIG_ZONE_DMA32=y
--- /dev/null
+From f9687bdf31bfb633a2ac615f66fd9cb5ea042a4e Mon Sep 17 00:00:00 2001
+From: David Daney <david.daney@cavium.com>
+Date: Fri, 13 Feb 2015 15:04:55 +0530
+Subject: [PATCH] netdev/phy: Add driver for Vitesse vsc848x single, dual and
+ quad 10G phys
+
+These phys implement the standard IEEE 802.3 clause 45 registers but
+require additional configuration. Some of these registers in the multi-phy
+devices are shared among all phys such as the GPIO registers.
+
+Additionally, this PHY does not automatically access the SFP+ serial EEPROM so
+it is up to the PHY driver to parse it and change certain parameters in the
+PHY according to the type of module installed and the length of the cable, if
+copper.
+
+This module has support for the vsc8488, vsc8486 and vsc8484 Vitesse devices
+but thus far has only been tested with the vsc8488 dual PHY.
+
+netdev/phy: Clean up structure names in vsc848x.c
+Cut-and-paste snafu left some bad names, no functional change.
+
+Signed-off-by: David Daney <david.daney@cavium.com>
+Signed-off-by: Aaron Williams <aaron.williams@cavium.com>
+Signed-off-by: Leonid Rosenboim <lrosenboim@caviumnetworks.com>
+Signed-off-by: Abhishek Paliwal <abhishek.paliwal@aricent.com>
+[Rebased on kernel 5.4 by Martin Kennedy <hurricos@gmail.com>]
+Signed-off-by: Martin Kennedy <hurricos@gmail.com>
+[rebase on 5.15, use nvmem instead of of_memory_accessor]
+Signed-off-by: Stijn Tintel <stijn@linux-ipv6.be>
+---
+ drivers/net/phy/Kconfig | 7 +
+ drivers/net/phy/Makefile | 1 +
+ drivers/net/phy/vsc848x.c | 762 ++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 770 insertions(+)
+ create mode 100644 drivers/net/phy/vsc848x.c
+
+--- a/drivers/net/phy/Kconfig
++++ b/drivers/net/phy/Kconfig
+@@ -442,6 +442,13 @@ config VITESSE_PHY
+ help
+ Currently supports the vsc8244
+
++config VSC848X_PHY
++ tristate "Drivers for the Vitesse 10G PHYs"
++ depends on NVMEM
++ help
++ Driver for Vitesse vsc848x single, dual and quad 10G PHY devices.
++ Currently supports the vsc8488, vsc8486 and vsc8484 chips
++
+ config XILINX_GMII2RGMII
+ tristate "Xilinx GMII2RGMII converter driver"
+ help
+--- a/drivers/net/phy/Makefile
++++ b/drivers/net/phy/Makefile
+@@ -102,4 +102,5 @@ obj-$(CONFIG_SMSC_PHY) += smsc.o
+ obj-$(CONFIG_STE10XP) += ste10Xp.o
+ obj-$(CONFIG_TERANETICS_PHY) += teranetics.o
+ obj-$(CONFIG_VITESSE_PHY) += vitesse.o
++obj-$(CONFIG_VSC848X_PHY) += vsc848x.o
+ obj-$(CONFIG_XILINX_GMII2RGMII) += xilinx_gmii2rgmii.o
+--- /dev/null
++++ b/drivers/net/phy/vsc848x.c
+@@ -0,0 +1,762 @@
++/*
++ * This file is subject to the terms and conditions of the GNU General Public
++ * License. See the file "COPYING" in the main directory of this archive
++ * for more details.
++ *
++ * Copyright (C) 2012 Cavium, Inc.
++ */
++
++#include <linux/platform_device.h>
++#include <linux/of_mdio.h>
++#include <linux/module.h>
++#include <linux/init.h>
++#include <linux/phy.h>
++#include <linux/memory.h>
++#include <linux/mutex.h>
++#include <linux/nvmem-consumer.h>
++#include <linux/delay.h>
++#include <linux/ctype.h>
++
++#define PMD_RX_SIGNAL_DETECT (MII_ADDR_C45 | 0x01000a)
++#define PMA_TXOUTCTRL2 (MII_ADDR_C45 | 0x018014)
++#define EDC_EYE_QUALITY (MII_ADDR_C45 | 0x018034)
++/* EDC Firmware State Machine Status and Lib Force
++ * 15: library force enable
++ * 14:8 - Library number
++ * 7:5 - N/A
++ * 4 - State force
++ * 3:0 - FW state (1=reset, 2=wait for alarm to clear, 3 = convergence,
++ * 4 = tracking, 5 = freeze)
++ */
++#define EDC_FW_SM_STATUS (MII_ADDR_C45 | 0x018036)
++
++#define BASER_PCS_STATUS (MII_ADDR_C45 | 0x030020)
++#define XGXS_LANE_STATUS (MII_ADDR_C45 | 0x040018)
++#define EWIS_INTR_PEND1 (MII_ADDR_C45 | 0x02EE00)
++#define EWIS_INTR_MASKA_1 (MII_ADDR_C45 | 0x02EE01)
++#define EWIS_INTR_MASKB_1 (MII_ADDR_C45 | 0x02EE02)
++#define EWIS_INTR_STAT2 (MII_ADDR_C45 | 0x02EE03)
++#define EWIS_INTR_PEND2 (MII_ADDR_C45 | 0x02EE04)
++#define EWIS_INTR_MASKA_2 (MII_ADDR_C45 | 0x02EE05)
++#define EWIS_INTR_MASKB_2 (MII_ADDR_C45 | 0x02EE06)
++#define EWIS_FAULT_MASK (MII_ADDR_C45 | 0x02EE07)
++#define EWIS_INTR_PEND3 (MII_ADDR_C45 | 0x02EE08)
++#define EWIS_INTR_MASKA_3 (MII_ADDR_C45 | 0x02EE09)
++#define EWIS_INTR_MASKB_3 (MII_ADDR_C45 | 0x02EE0A)
++
++/* Device ID
++ * 15:0 - device ID
++ */
++#define GBL_DEVICE_ID (MII_ADDR_C45 | 0x1e0000)
++/* Device revision
++ * 15:04 - reserved
++ * 03:00 - revision ID
++ */
++#define GBL_DEVICE_REVISION (MII_ADDR_C45 | 0x1e0001)
++/* Block Level Software Reset
++ * 15:14 - reserved
++ * 13: - software reset EDC 1 (1 = reset, autoclears)
++ * 12: - software reset EDC 0 (1 = reset, autoclears)
++ * 11:10 - reserved
++ * 09: - Software reset channel 1 (1 = reset, autoclears)
++ * 08: - Software reset channel 0 (1 = reset, autoclears)
++ * 07: - Microprocessor reset (0 = normal operation, 1 = reset)
++ * 06: - Software reset BIU (1 = reset, autoclears)
++ * 05: - Software reset TWS slave (1 = reset, autoclears)
++ * 04: - Software reset TWS master (1 = reset, autoclears)
++ * 03: - Software reset MDIO (1 = reset, autoclears)
++ * 02: - Software reset UART (1 = reset, autoclears)
++ * 01: - Global register reset (1 = reset, autoclears)
++ * 00: - Software reset chip (1 = reset, autoclears)
++ */
++#define GBL_BLOCK_LVL_SW_RESET (MII_ADDR_C45 | 0x1e0002)
++#define GBL_GPIO_0_CONFIG1_STATUS (MII_ADDR_C45 | 0x1e0100)
++#define GBL_GPIO_0_CONFIG2 (MII_ADDR_C45 | 0x1e0101)
++#define GBL_DEVICE_ID (MII_ADDR_C45 | 0x1e0000)
++#define GBL_FW_CHECKSUM (MII_ADDR_C45 | 0x1e7fe0)
++#define GBL_FW_WATCHDOG (MII_ADDR_C45 | 0x1e7fe1)
++#define GBL_FW_VERSION (MII_ADDR_C45 | 0x1e7fe2)
++#define GBL_FW_VAR_ACC_CTRL (MII_ADDR_C45 | 0x1e7fe3)
++#define GBL_FW_VAR_ACC_DATA (MII_ADDR_C45 | 0x1e7fe4)
++
++/* The Vitesse VSC848X series are 10G PHYs.
++ *
++ * Some of these devices contain multiple PHYs in a single package and
++ * some features are controlled by a global set of registers shared between
++ * all of the PHY devices. Because of this a nexus is used to handle all
++ * of the PHYs on the same device.
++ *
++ * Unlike some PHY devices, it is up to the driver to read the SFP module
++ * serial EEPROM in order to put the PHY into the right mode. The VSC848X
++ * does not provide an I2C interface so the PHY driver relies on the
++ * external AT24 I2C EEPROM driver to read the module whenever it is inserted.
++ *
++ */
++
++/* Enable LOPC detection (see 0x5B for target state)
++ * 15:12 - channel 3
++ * 11:08 - channel 2
++ * 07:04 - channel 1
++ * 03:00 - channel 0
++ * 1 = enable (default), 0 = disable
++ */
++#define FW_VAR_ENABLE_LOPC 0x58
++/* While in tracking mode, go to this state in response to LOPC assertion
++ * 1 = reset, 2 = wait (default), 3 = converging, 4 = tracking, 5 = freeze
++ */
++#define FW_VAR_LOPC_ASSERT_MODE 0x5B
++/* While in freeze mode, enable state transition upon deassertion of LOPC (see
++ * 0x61 for target state)
++ * 1 - reset, 2 = wait, 3 = converging, 4 = tracking (default), 5 = freeze
++ */
++#define FW_VAR_FREEZE_DEASSERT_MODE 0x61
++/* Current functional mode
++ * See VITESSE_FUNC_MODE_XXX below for values
++ * NOTE: When the firmware is done servicing the mode change request, bit 4
++ * will be set to 1.
++ */
++#define FW_VAR_FUNCTIONAL_MODE 0x94
++/* Current state of graded SPSA process
++ * 3: channel 3
++ * 2: channel 2
++ * 1: channel 1
++ * 0: channel 0
++ * 1 = busy, 2 = done
++ */
++#define FW_VAR_GRADED_SPSA_STATE 0x95
++/* BerScore at start of SPSA cycle */
++#define FW_VAR_BERSCORE_START 0x96
++/* BerScore at end of SPSA cycle */
++#define FW_VAR_BERSCORE_END 0x97
++/* Enable/Disable aggressive track phase on entering tracking state
++ * 15:12 - channel 3
++ * 11:08 - channel 2
++ * 07:04 - channel 1
++ * 03:00 - channel 0
++ * 0 = disable, 1 = enable (default)
++ */
++#define FW_VAR_AGG_TRACKING 0xAF
++
++/* Modes for the PHY firmware */
++#define VITESSE_FUNC_MODE_LIMITING 2 /* Optical */
++#define VITESSE_FUNC_MODE_COPPER 3 /* Copper */
++#define VITESSE_FUNC_MODE_LINEAR 4
++#define VITESSE_FUNC_MODE_KR 5
++#define VITESSE_FUNC_MODE_ZR 7
++#define VITESSE_FUNC_MODE_1G 8
++
++
++struct vsc848x_nexus_mdiobus {
++ struct mii_bus *mii_bus;
++ struct mii_bus *parent_mii_bus;
++ int reg_offset;
++ struct mutex lock; /* Lock used for global register sequences */
++ int phy_irq[PHY_MAX_ADDR];
++};
++
++struct vsc848x_phy_info {
++ int sfp_conn; /* Module connected? */
++ int tx_en_gpio; /* GPIO that enables transmit */
++ int mod_abs_gpio; /* Module Absent GPIO line */
++ int tx_fault_gpio; /* TX Fault GPIO line */
++ int inta_gpio, intb_gpio; /* Interrupt GPIO line (output) */
++ uint8_t mode; /* Mode for module */
++ uint8_t channel; /* channel in multi-phy devices */
++ struct vsc848x_nexus_mdiobus *nexus; /* Nexus for lock */
++};
++
++/**
++ * Maps GPIO lines to the global GPIO config registers.
++ *
++ * Please see the data sheet since the configuration for each GPIO line is
++ * different.
++ */
++static const struct {
++ uint32_t config1_status_reg;
++ uint32_t config2_reg;
++} vcs848x_gpio_to_reg[12] = {
++ { (MII_ADDR_C45 | 0x1e0100), (MII_ADDR_C45 | 0x1e0101) }, /* 0 */
++ { (MII_ADDR_C45 | 0x1e0102), (MII_ADDR_C45 | 0x1e0103) }, /* 1 */
++ { (MII_ADDR_C45 | 0x1e0104), (MII_ADDR_C45 | 0x1e0105) }, /* 2 */
++ { (MII_ADDR_C45 | 0x1e0106), (MII_ADDR_C45 | 0x1e0107) }, /* 3 */
++ { (MII_ADDR_C45 | 0x1e0108), (MII_ADDR_C45 | 0x1e0109) }, /* 4 */
++ { (MII_ADDR_C45 | 0x1e010A), (MII_ADDR_C45 | 0x1e010B) }, /* 5 */
++ { (MII_ADDR_C45 | 0x1e0124), (MII_ADDR_C45 | 0x1e0125) }, /* 6 */
++ { (MII_ADDR_C45 | 0x1e0126), (MII_ADDR_C45 | 0x1e0127) }, /* 7 */
++ { (MII_ADDR_C45 | 0x1e0128), (MII_ADDR_C45 | 0x1e0129) }, /* 8 */
++ { (MII_ADDR_C45 | 0x1e012a), (MII_ADDR_C45 | 0x1e012b) }, /* 9 */
++ { (MII_ADDR_C45 | 0x1e012c), (MII_ADDR_C45 | 0x1e012d) }, /* 10 */
++ { (MII_ADDR_C45 | 0x1e012e), (MII_ADDR_C45 | 0x1e012f) }, /* 11 */
++};
++
++static int vsc848x_probe(struct phy_device *phydev)
++{
++ struct vsc848x_phy_info *dev_info;
++ int ret;
++
++ dev_info = devm_kzalloc(&phydev->mdio.dev, sizeof(*dev_info), GFP_KERNEL);
++ if (dev_info == NULL)
++ return -ENOMEM;
++
++ phydev->priv = dev_info;
++ dev_info->mode = VITESSE_FUNC_MODE_LIMITING; /* Default to optical */
++ phydev->priv = dev_info;
++ dev_info->nexus = phydev->mdio.bus->priv;
++
++ ret = of_property_read_u32(phydev->mdio.dev.of_node, "mod_abs",
++ &dev_info->mod_abs_gpio);
++ if (ret) {
++ dev_err(&phydev->mdio.dev, "%s has invalid mod_abs address\n",
++ phydev->mdio.dev.of_node->full_name);
++ return ret;
++ }
++
++ ret = of_property_read_u32(phydev->mdio.dev.of_node, "tx_fault",
++ &dev_info->tx_fault_gpio);
++ if (ret) {
++ dev_err(&phydev->mdio.dev, "%s has invalid tx_fault address\n",
++ phydev->mdio.dev.of_node->full_name);
++ return ret;
++ }
++
++ ret = of_property_read_u32(phydev->mdio.dev.of_node, "inta",
++ &dev_info->inta_gpio);
++ if (ret)
++ dev_info->inta_gpio = -1;
++
++ ret = of_property_read_u32(phydev->mdio.dev.of_node, "intb",
++ &dev_info->intb_gpio);
++ if (ret)
++ dev_info->intb_gpio = -1;
++
++ ret = of_property_read_u32(phydev->mdio.dev.of_node, "txon",
++ &dev_info->tx_en_gpio);
++ if (ret) {
++ dev_err(&phydev->mdio.dev, "%s has invalid txon gpio address\n",
++ phydev->mdio.dev.of_node->full_name);
++ return -ENXIO;
++ }
++
++ ret = phy_read(phydev, GBL_DEVICE_ID);
++ if (ret < 0) {
++ dev_err(&phydev->mdio.dev, "%s error reading PHY\n",
++ phydev->mdio.dev.of_node->full_name);
++ return ret;
++ }
++
++ /* Check how many devices are in the package to figure out the channel
++ * number.
++ */
++ switch (ret) {
++ case 0x8487: /* Single */
++ case 0x8486:
++ dev_info->channel = 0;
++ break;
++ case 0x8488: /* Dual */
++ dev_info->channel = phydev->mdio.addr & 1;
++ break;
++ case 0x8484: /* Quad */
++ dev_info->channel = phydev->mdio.addr & 3;
++ break;
++ default:
++ dev_err(&phydev->mdio.dev, "%s Unknown Vitesse PHY model %04x\n",
++ phydev->mdio.dev.of_node->full_name, ret);
++ return -EINVAL;
++ }
++
++ return 0;
++}
++
++static void vsc848x_remove(struct phy_device *phydev)
++{
++ struct vsc848x_phy_info *dev_info = phydev->priv;
++
++ dev_info(&phydev->mdio.dev, "%s Exiting\n", phydev->mdio.dev.of_node->full_name);
++
++ kfree(dev_info);
++}
++
++static int vsc848x_config_init(struct phy_device *phydev)
++{
++ linkmode_set_bit(ETHTOOL_LINK_MODE_10000baseR_FEC_BIT,
++ phydev->advertising);
++
++ linkmode_set_bit(ETHTOOL_LINK_MODE_10000baseR_FEC_BIT,
++ phydev->supported);
++
++ phydev->autoneg = 0;
++ phydev->is_c45 = 1;
++ phydev->state = PHY_READY;
++
++ return 0;
++}
++
++static int vsc848x_config_aneg(struct phy_device *phydev)
++{
++ return 0;
++}
++
++static int vsc848x_write_global_var(struct phy_device *phydev, uint8_t channel,
++ uint8_t addr, uint16_t value)
++{
++ struct vsc848x_phy_info *dev_info = phydev->priv;
++ int timeout = 1000;
++ int ret = 0;
++
++ mutex_lock(&(dev_info->nexus->lock));
++
++ /* Wait for firmware download to complete */
++ timeout = 100000;
++ do {
++ ret = phy_read(phydev, MII_ADDR_C45 | 0x1e7fe0);
++ if (ret < 0)
++ goto error;
++ if (ret == 3)
++ break;
++ udelay(100);
++ } while (timeout-- > 0);
++ if (timeout <= 0) {
++ dev_err(&phydev->mdio.dev, "%s Timeout waiting for PHY firmware to load\n",
++ phydev->mdio.dev.of_node->full_name);
++ ret = -EIO;
++ goto error;
++ }
++
++ do {
++ ret = phy_read(phydev, (MII_ADDR_C45 | 0x1e7fe3));
++ if (ret < 0)
++ return ret;
++ if (ret == 0)
++ break;
++ mdelay(1);
++ } while (timeout-- > 0);
++ if (timeout <= 0) {
++ dev_err(&phydev->mdio.dev, "%s timed out waiting to write global\n",
++ phydev->mdio.dev.of_node->full_name);
++ ret = -EIO;
++ goto error;
++ }
++ ret = phy_write(phydev, (MII_ADDR_C45 | 0x1e7fe4), value);
++ if (ret < 0)
++ goto error;
++
++ ret = phy_write(phydev, (MII_ADDR_C45 | 0x1e7fe3),
++ 0x8000 | ((channel & 3) << 8) | addr);
++ if (ret < 0)
++ goto error;
++
++ /* Wait for value to be written */
++ do {
++ ret = phy_read(phydev, (MII_ADDR_C45 | 0x1e7fe3));
++ if (ret < 0)
++ return ret;
++ if (ret == 0)
++ break;
++ mdelay(1);
++ } while (timeout-- > 0);
++ if (timeout <= 0) {
++ dev_err(&phydev->mdio.dev, "%s timed out waiting to write global\n",
++ phydev->mdio.dev.of_node->full_name);
++ ret = -EIO;
++ goto error;
++ }
++ ret = 0;
++
++error:
++ mutex_unlock(&(dev_info->nexus->lock));
++
++ return ret;
++}
++
++/**
++ * Dumps out the contents of the SFP EEPROM when errors are detected
++ *
++ * @param eeprom - contents of SFP+ EEPROM
++ */
++static void dump_sfp_eeprom(const uint8_t eeprom[64])
++{
++ int addr = 0;
++ int i;
++ char line[17];
++ line[16] = '\0';
++
++ pr_info("SFP+ EEPROM contents:\n");
++ while (addr < 64) {
++ pr_info(" %02x: ", addr);
++ for (i = 0; i < 16; i++)
++ pr_cont("%02x ", eeprom[addr + i]);
++ for (i = 0; i < 16; i++) {
++ if (!isprint(eeprom[addr + i]) ||
++ eeprom[addr + i] >= 0x80)
++ line[i] = '.';
++ else
++ line[i] = eeprom[addr + i];
++ }
++ pr_cont(" %s\n", line);
++ addr += 16;
++ }
++ pr_info("\n");
++}
++
++/**
++ * Read the SFP+ module EEPROM and program the Vitesse PHY accordingly.
++ *
++ * @param phydev - Phy device
++ *
++ * @returns 0 for success, error otherwise.
++ */
++static int vsc848x_read_sfp(struct phy_device *phydev)
++{
++ struct vsc848x_phy_info *dev_info = phydev->priv;
++ struct nvmem_device *nvmem;
++ uint8_t sfp_buffer[64];
++ uint8_t csum;
++ uint8_t mode = VITESSE_FUNC_MODE_LIMITING;
++ const char *mode_str = "Unknown";
++ int i;
++ int ret = 0;
++
++ nvmem = of_nvmem_device_get(phydev->mdio.dev.of_node, NULL);
++ if (IS_ERR(nvmem))
++ return PTR_ERR(nvmem);
++
++ /* For details on the SFP+ EEPROM contents see the SFF-8472
++ * Diagnostic Monitoring Interface for Optical Transceivers.
++ *
++ * This is based on revision 11.1, October 26, 2012.
++ */
++
++ ret = nvmem_device_read(nvmem, 0, 64, sfp_buffer);
++ nvmem_device_put(nvmem);
++ if (ret < 0)
++ return ret;
++
++ /* Validate SFP checksum */
++ csum = 0;
++ for (i = 0; i < 63; i++)
++ csum += sfp_buffer[i];
++ if (csum != sfp_buffer[63]) {
++ dev_err(&phydev->mdio.dev, "%s SFP EEPROM checksum bad, calculated 0x%02x, should be 0x%02x\n",
++ phydev->mdio.dev.of_node->full_name, csum, sfp_buffer[63]);
++ dump_sfp_eeprom(sfp_buffer);
++ return -ENXIO;
++ }
++
++ /* Make sure it's a SFP or SFP+ module */
++ if (sfp_buffer[0] != 3) {
++ dev_err(&phydev->mdio.dev, "%s module is not SFP or SFP+\n",
++ phydev->mdio.dev.of_node->full_name);
++ dump_sfp_eeprom(sfp_buffer);
++ return -ENXIO;
++ }
++
++ /* Check connector type */
++ switch (sfp_buffer[2]) {
++ case 0x01: /* SC */
++ mode = VITESSE_FUNC_MODE_LIMITING;
++ break;
++ case 0x07: /* LC */
++ mode = VITESSE_FUNC_MODE_LIMITING;
++ break;
++ case 0x0B: /* Optical pigtail */
++ mode = VITESSE_FUNC_MODE_LIMITING;
++ break;
++ case 0x21: /* Copper pigtail */
++ case 0x22: /* RJ45 */
++ mode = VITESSE_FUNC_MODE_COPPER;
++ break;
++ default:
++ dev_err(&phydev->mdio.dev, "%s Unknown Connector Type 0x%x\n",
++ phydev->mdio.dev.of_node->full_name, sfp_buffer[2]);
++ dump_sfp_eeprom(sfp_buffer);
++ return -EINVAL;
++ }
++
++ if (mode == VITESSE_FUNC_MODE_LIMITING) {
++ if (mode_str[3] & 0x10)
++ mode_str = "10GBase-SR";
++ else if (mode_str[3] & 0x20)
++ mode_str = "10GBase-LR";
++ else if (mode_str[3] & 0x40)
++ mode_str = "10GBase-LRM";
++ else if (mode_str[3] & 0x80)
++ mode_str = "10GBase-ER";
++ else
++ dev_err(&phydev->mdio.dev, "%s unknown SFP compatibility\n"
++ "type ID: 0x%02x, extended ID: 0x%02x, Connector type code: 0x%02x\n"
++ "Transceiver compatibility code: (%02x) %02x %02x %02x %02x %02x %02x %02x %02x\n",
++ phydev->mdio.dev.of_node->full_name, sfp_buffer[0],
++ sfp_buffer[1], sfp_buffer[2], sfp_buffer[36],
++ sfp_buffer[3], sfp_buffer[4], sfp_buffer[5],
++ sfp_buffer[6], sfp_buffer[7], sfp_buffer[8],
++ sfp_buffer[9], sfp_buffer[10]);
++ } else if (mode == VITESSE_FUNC_MODE_COPPER) {
++ if (sfp_buffer[8] & 0x4) {
++ mode_str = "10G Passive Copper";
++ } else if (sfp_buffer[8] & 0x8) {
++ mode_str = "10G Active Copper";
++ mode = VITESSE_FUNC_MODE_LIMITING;
++ } else {
++ dev_err(&phydev->mdio.dev, "%s Unknown SFP+ copper cable capability 0x%02x\n"
++ "Transceiver compatibility code: (%02x) %02x %02x %02x %02x %02x %02x %02x %02x\n",
++ phydev->mdio.dev.of_node->full_name, sfp_buffer[8],
++ sfp_buffer[36], sfp_buffer[3], sfp_buffer[4],
++ sfp_buffer[5], sfp_buffer[6], sfp_buffer[7],
++ sfp_buffer[8], sfp_buffer[9], sfp_buffer[10]);
++ return -EINVAL;
++ }
++ } else {
++ dev_err(&phydev->mdio.dev, "%s Unsupported phy mode %d\n",
++ phydev->mdio.dev.of_node->full_name, mode);
++ dump_sfp_eeprom(sfp_buffer);
++ }
++
++ vsc848x_write_global_var(phydev, dev_info->channel, 0x94, mode);
++
++ /* Adjust PMA_TXOUTCTRL2 based on cable length. Vitesse recommends
++ * 0x1606 for copper cable lengths 5M and longer.
++ *
++ * The default value is 0x1300.
++ */
++ if (mode == VITESSE_FUNC_MODE_COPPER) {
++ if (sfp_buffer[18] >= 5)
++ ret = phy_write(phydev, PMA_TXOUTCTRL2, 0x1606);
++ else
++ ret = phy_write(phydev, PMA_TXOUTCTRL2, 0x1300);
++ if (ret)
++ return ret;
++ }
++
++ /* Reset the state machine */
++ ret = phy_write(phydev, MII_ADDR_C45 | 0x18034, 0x11);
++
++ dev_info(&phydev->mdio.dev, "%s configured for %s\n",
++ phydev->mdio.dev.of_node->full_name, mode_str);
++
++ return ret;
++}
++
++static int vsc848x_read_status(struct phy_device *phydev)
++{
++ struct vsc848x_phy_info *dev_info = phydev->priv;
++ int rx_signal_detect;
++ int pcs_status;
++ int xgxs_lane_status;
++ int value;
++ int sfp_conn;
++ int ret;
++
++ /* Check if a module is plugged in */
++ value = phy_read(phydev, vcs848x_gpio_to_reg[dev_info->mod_abs_gpio]
++ .config1_status_reg);
++ if (value < 0)
++ return value;
++
++ sfp_conn = !(value & 0x400);
++ if (sfp_conn != dev_info->sfp_conn) {
++ /* We detect a module being plugged in */
++ if (sfp_conn) {
++ ret = vsc848x_read_sfp(phydev);
++ if (ret < 0)
++ goto no_link;
++ dev_info->sfp_conn = sfp_conn;
++ } else {
++ dev_info(&phydev->mdio.dev, "%s module unplugged\n",
++ phydev->mdio.dev.of_node->full_name);
++ dev_info->sfp_conn = sfp_conn;
++ goto no_link;
++ }
++ }
++
++ rx_signal_detect = phy_read(phydev, PMD_RX_SIGNAL_DETECT);
++ if (rx_signal_detect < 0)
++ return rx_signal_detect;
++
++ if ((rx_signal_detect & 1) == 0)
++ goto no_link;
++
++ pcs_status = phy_read(phydev, BASER_PCS_STATUS);
++ if (pcs_status < 0)
++ return pcs_status;
++
++ if ((pcs_status & 1) == 0)
++ goto no_link;
++
++ xgxs_lane_status = phy_read(phydev, XGXS_LANE_STATUS);
++ if (xgxs_lane_status < 0)
++ return xgxs_lane_status;
++
++ if ((xgxs_lane_status & 0x1000) == 0)
++ goto no_link;
++
++ phydev->speed = 10000;
++ phydev->link = 1;
++ phydev->duplex = 1;
++ return 0;
++no_link:
++ phydev->link = 0;
++ return 0;
++}
++
++static struct of_device_id vsc848x_match[] = {
++ {
++ .compatible = "vitesse,vsc8488",
++ },
++ {
++ .compatible = "vitesse,vsc8486",
++ },
++ {
++ .compatible = "vitesse,vsc8484",
++ },
++ {},
++};
++MODULE_DEVICE_TABLE(of, vsc848x_match);
++
++static struct phy_driver vsc848x_phy_driver = {
++ .phy_id = 0x00070400,
++ .phy_id_mask = 0xfffffff0,
++ .name = "Vitesse VSC848X",
++ .config_init = vsc848x_config_init,
++ .probe = vsc848x_probe,
++ .remove = vsc848x_remove,
++ .config_aneg = vsc848x_config_aneg,
++ .read_status = vsc848x_read_status,
++/*
++ .driver = {
++ .owner = THIS_MODULE,
++ .of_match_table = vsc848x_match,
++ },
++*/
++};
++
++/* Phy nexus support below. */
++
++static int vsc848x_nexus_read(struct mii_bus *bus, int phy_id, int regnum)
++{
++ struct vsc848x_nexus_mdiobus *p = bus->priv;
++ return p->parent_mii_bus->read(p->parent_mii_bus,
++ phy_id + p->reg_offset,
++ regnum);
++}
++
++static int vsc848x_nexus_write(struct mii_bus *bus, int phy_id,
++ int regnum, u16 val)
++{
++ struct vsc848x_nexus_mdiobus *p = bus->priv;
++ return p->parent_mii_bus->write(p->parent_mii_bus,
++ phy_id + p->reg_offset,
++ regnum, val);
++}
++
++static int vsc848x_nexus_probe(struct platform_device *pdev)
++{
++ struct vsc848x_nexus_mdiobus *bus;
++ const char *bus_id;
++ int len;
++ int err = 0;
++
++ bus = devm_kzalloc(&pdev->dev, sizeof(*bus), GFP_KERNEL);
++ if (!bus)
++ return -ENOMEM;
++
++ bus->parent_mii_bus = container_of(pdev->dev.parent,
++ struct mii_bus, dev);
++
++ /* The PHY nexux must have a reg property in the range [0-31] */
++ err = of_property_read_u32(pdev->dev.of_node, "reg", &bus->reg_offset);
++ if (err) {
++ dev_err(&pdev->dev, "%s has invalid PHY address\n",
++ pdev->dev.of_node->full_name);
++ return err;
++ }
++
++ bus->mii_bus = mdiobus_alloc();
++ if (!bus->mii_bus)
++ return -ENOMEM;
++
++ bus->mii_bus->priv = bus;
++#if 0
++ bus->mii_bus->irq = bus->phy_irq;
++#endif
++ bus->mii_bus->name = "vsc848x_nexus";
++ bus_id = bus->parent_mii_bus->id;
++ len = strlen(bus_id);
++ if (len > MII_BUS_ID_SIZE - 4)
++ bus_id += len - (MII_BUS_ID_SIZE - 4);
++ snprintf(bus->mii_bus->id, MII_BUS_ID_SIZE, "%s:%02x",
++ bus_id, bus->reg_offset);
++ bus->mii_bus->parent = &pdev->dev;
++
++ bus->mii_bus->read = vsc848x_nexus_read;
++ bus->mii_bus->write = vsc848x_nexus_write;
++ mutex_init(&bus->lock);
++
++ dev_set_drvdata(&pdev->dev, bus);
++
++ err = of_mdiobus_register(bus->mii_bus, pdev->dev.of_node);
++ if (err) {
++ dev_err(&pdev->dev, "Error registering with device tree\n");
++ goto fail_register;
++ }
++
++ return 0;
++
++fail_register:
++ dev_err(&pdev->dev, "Failed to register\n");
++ mdiobus_free(bus->mii_bus);
++ return err;
++}
++
++static int vsc848x_nexus_remove(struct platform_device *pdev)
++{
++ return 0;
++}
++
++static struct of_device_id vsc848x_nexus_match[] = {
++ {
++ .compatible = "vitesse,vsc8488-nexus",
++ },
++ {
++ .compatible = "vitesse,vsc8486-nexus",
++ },
++ {
++ .compatible = "vitesse,vsc8484-nexus",
++ },
++ {},
++};
++MODULE_DEVICE_TABLE(of, vsc848x_nexus_match);
++
++static struct platform_driver vsc848x_nexus_driver = {
++ .driver = {
++ .name = "vsc848x-nexus",
++ .owner = THIS_MODULE,
++ .of_match_table = vsc848x_nexus_match,
++ },
++ .probe = vsc848x_nexus_probe,
++ .remove = vsc848x_nexus_remove,
++};
++
++static int __init vsc848x_mod_init(void)
++{
++ int rv;
++
++ rv = platform_driver_register(&vsc848x_nexus_driver);
++ if (rv)
++ return rv;
++
++ rv = phy_driver_register(&vsc848x_phy_driver, THIS_MODULE);
++
++ return rv;
++}
++module_init(vsc848x_mod_init);
++
++static void __exit vsc848x_mod_exit(void)
++{
++ phy_driver_unregister(&vsc848x_phy_driver);
++ platform_driver_unregister(&vsc848x_nexus_driver);
++}
++module_exit(vsc848x_mod_exit);
++
++MODULE_DESCRIPTION("Driver for Vitesse VSC848X PHY");
++MODULE_AUTHOR("David Daney and Aaron Williams");
++MODULE_LICENSE("GPL");