WIP: snic10e-5.15
authorStijn Tintel <[email protected]>
Sun, 3 Apr 2022 02:40:09 +0000 (05:40 +0300)
committerStijn Tintel <[email protected]>
Thu, 9 Nov 2023 12:35:03 +0000 (14:35 +0200)
target/linux/octeon/config-5.15
target/linux/octeon/files/arch/mips/boot/dts/cavium-octeon/snic10e.dts
target/linux/octeon/patches-5.15/141-v5.19-MIPS-Octeon-add-SNIC10E-board.patch [new file with mode: 0644]
target/linux/octeon/patches-5.15/150-ubnt_usg_support.patch
target/linux/octeon/patches-5.15/300-v5.19-0001-MIPS-Octeon-fix-CN6640-hang-on-XAUI-init.patch [new file with mode: 0644]
target/linux/octeon/patches-5.15/300-v5.19-0002-MIPS-Octeon-support-all-interfaces-on-CN66XX.patch [new file with mode: 0644]
target/linux/octeon/patches-5.15/710-netdev-phy-of-Handle-nexus-Ethernet-PHY-devices.patch [new file with mode: 0644]
target/linux/octeon/patches-5.15/711-netdev-phy-Add-driver-for-Vitesse-vsc848x-single-dua.patch [new file with mode: 0644]

index 2350e45295f4755e5c9a88ca835554a4fa5a9b63..9f36ebf6e326c97d652b5df74931786d45369067 100644 (file)
@@ -251,6 +251,7 @@ 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
index af983c06564e2c190df692e63a5a389091a24f90..704064ffee01b6a1ae68d399f75edce55efb5acc 100644 (file)
                                         * the PHY in the proper mode for
                                         * copper or optical.
                                         */
-                                       sfp-eeprom = <&sfp0>;
+                                       nvmem = <&sfp0>;
                                };
 
                                phy1: ethernet-phy@1 {
                                         * the PHY in the proper mode for
                                         * copper or optical.
                                         */
-                                       sfp-eeprom = <&sfp1>;
+                                       nvmem = <&sfp0>;
                                };
                        };
                        mphyB: ethernet-phy-nexus@B {
diff --git a/target/linux/octeon/patches-5.15/141-v5.19-MIPS-Octeon-add-SNIC10E-board.patch b/target/linux/octeon/patches-5.15/141-v5.19-MIPS-Octeon-add-SNIC10E-board.patch
new file mode 100644 (file)
index 0000000..855f308
--- /dev/null
@@ -0,0 +1,32 @@
+From 07bdec3cdc92a1f8ede0b904d54f440f69c26704 Mon Sep 17 00:00:00 2001
+From: Stijn Tintel <[email protected]>
+Date: Wed, 18 May 2022 19:48:10 +0300
+Subject: [PATCH] MIPS: Octeon: add SNIC10E board
+
+The CN6640-SNIC10E-G and CN6640-SNIC10E-1.1-G PCIe NICs are based on
+this board.
+
+Signed-off-by: Stijn Tintel <[email protected]>
+Signed-off-by: Thomas Bogendoerfer <[email protected]>
+---
+ arch/mips/include/asm/octeon/cvmx-bootinfo.h | 2 ++
+ 1 file changed, 2 insertions(+)
+
+--- a/arch/mips/include/asm/octeon/cvmx-bootinfo.h
++++ b/arch/mips/include/asm/octeon/cvmx-bootinfo.h
+@@ -253,6 +253,7 @@ enum cvmx_board_types_enum {
+       CVMX_BOARD_TYPE_REDWING = 43,
+       CVMX_BOARD_TYPE_NIC68_4 = 44,
+       CVMX_BOARD_TYPE_NIC10E_66 = 45,
++      CVMX_BOARD_TYPE_SNIC10E = 50,
+       CVMX_BOARD_TYPE_MAX,
+       /*
+@@ -369,6 +370,7 @@ static inline const char *cvmx_board_typ
+               ENUM_BRD_TYPE_CASE(CVMX_BOARD_TYPE_REDWING)
+               ENUM_BRD_TYPE_CASE(CVMX_BOARD_TYPE_NIC68_4)
+               ENUM_BRD_TYPE_CASE(CVMX_BOARD_TYPE_NIC10E_66)
++              ENUM_BRD_TYPE_CASE(CVMX_BOARD_TYPE_SNIC10E)
+               ENUM_BRD_TYPE_CASE(CVMX_BOARD_TYPE_MAX)
+                       /* Customer boards listed here */
index 88aa1c406f9b0b796cd9700d90d610db1aee2dd6..00c268eda86892edaee69f782fd61baeeecf039c 100644 (file)
@@ -1,6 +1,6 @@
 --- a/arch/mips/include/asm/octeon/cvmx-bootinfo.h
 +++ b/arch/mips/include/asm/octeon/cvmx-bootinfo.h
-@@ -296,6 +296,7 @@ enum cvmx_board_types_enum {
+@@ -297,6 +297,7 @@ enum cvmx_board_types_enum {
        CVMX_BOARD_TYPE_CUST_PRIVATE_MIN = 20001,
        CVMX_BOARD_TYPE_UBNT_E100 = 20002,
        CVMX_BOARD_TYPE_UBNT_E200 = 20003,
@@ -8,7 +8,7 @@
        CVMX_BOARD_TYPE_UBNT_E220 = 20005,
        CVMX_BOARD_TYPE_ITUS_SHIELD = 20006,
        CVMX_BOARD_TYPE_UBNT_E300 = 20300,
-@@ -399,6 +400,7 @@ static inline const char *cvmx_board_typ
+@@ -401,6 +402,7 @@ static inline const char *cvmx_board_typ
                    /* Customer private range */
                ENUM_BRD_TYPE_CASE(CVMX_BOARD_TYPE_CUST_PRIVATE_MIN)
                ENUM_BRD_TYPE_CASE(CVMX_BOARD_TYPE_UBNT_E100)
diff --git a/target/linux/octeon/patches-5.15/300-v5.19-0001-MIPS-Octeon-fix-CN6640-hang-on-XAUI-init.patch b/target/linux/octeon/patches-5.15/300-v5.19-0001-MIPS-Octeon-fix-CN6640-hang-on-XAUI-init.patch
new file mode 100644 (file)
index 0000000..aeffdd9
--- /dev/null
@@ -0,0 +1,30 @@
+From 407710a3b52c32db6f212727f06620f1c38ed1d2 Mon Sep 17 00:00:00 2001
+From: Stijn Tintel <[email protected]>
+Date: Sun, 3 Apr 2022 05:59:49 +0300
+Subject: [PATCH] MIPS: Octeon: fix CN6640 hang on XAUI init
+
+Some CN66XX series Octeon II chips seem to hang if a reset is issued on
+XAUI initialization. Avoid the hang by disabling the reset.
+
+Tested on SNIC10E.
+
+Signed-off-by: Stijn Tintel <[email protected]>
+Signed-off-by: Thomas Bogendoerfer <[email protected]>
+---
+ arch/mips/cavium-octeon/executive/cvmx-helper-xaui.c | 5 +++--
+ 1 file changed, 3 insertions(+), 2 deletions(-)
+
+--- a/arch/mips/cavium-octeon/executive/cvmx-helper-xaui.c
++++ b/arch/mips/cavium-octeon/executive/cvmx-helper-xaui.c
+@@ -156,8 +156,9 @@ int __cvmx_helper_xaui_enable(int interf
+       xauiCtl.u64 = cvmx_read_csr(CVMX_PCSXX_CONTROL1_REG(interface));
+       xauiCtl.s.lo_pwr = 0;
+-      /* Issuing a reset here seems to hang some CN68XX chips. */
+-      if (!OCTEON_IS_MODEL(OCTEON_CN68XX_PASS1_X) &&
++      /* Issuing a reset here seems to hang some CN66XX/CN68XX chips. */
++      if (!OCTEON_IS_MODEL(OCTEON_CN66XX) &&
++          !OCTEON_IS_MODEL(OCTEON_CN68XX_PASS1_X) &&
+           !OCTEON_IS_MODEL(OCTEON_CN68XX_PASS2_X))
+               xauiCtl.s.reset = 1;
diff --git a/target/linux/octeon/patches-5.15/300-v5.19-0002-MIPS-Octeon-support-all-interfaces-on-CN66XX.patch b/target/linux/octeon/patches-5.15/300-v5.19-0002-MIPS-Octeon-support-all-interfaces-on-CN66XX.patch
new file mode 100644 (file)
index 0000000..fac93f6
--- /dev/null
@@ -0,0 +1,28 @@
+From aa88b7066a10e2cb5355478f22daa28794ab3835 Mon Sep 17 00:00:00 2001
+From: Stijn Tintel <[email protected]>
+Date: Sun, 3 Apr 2022 05:59:50 +0300
+Subject: [PATCH] MIPS: Octeon: support all interfaces on CN66XX
+
+CN66XX_PASS1_0 has 7 interfaces, other revisions have 8 interfaces.
+
+Signed-off-by: Stijn Tintel <[email protected]>
+Signed-off-by: Thomas Bogendoerfer <[email protected]>
+---
+ arch/mips/cavium-octeon/executive/cvmx-helper.c | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+--- a/arch/mips/cavium-octeon/executive/cvmx-helper.c
++++ b/arch/mips/cavium-octeon/executive/cvmx-helper.c
+@@ -61,6 +61,12 @@ int cvmx_helper_get_number_of_interfaces
+ {
+       if (OCTEON_IS_MODEL(OCTEON_CN68XX))
+               return 9;
++      if (OCTEON_IS_MODEL(OCTEON_CN66XX)) {
++              if (OCTEON_IS_MODEL(OCTEON_CN66XX_PASS1_0))
++                      return 7;
++              else
++                      return 8;
++      }
+       if (OCTEON_IS_MODEL(OCTEON_CN56XX) || OCTEON_IS_MODEL(OCTEON_CN52XX))
+               return 4;
+       if (OCTEON_IS_MODEL(OCTEON_CN7XXX))
diff --git a/target/linux/octeon/patches-5.15/710-netdev-phy-of-Handle-nexus-Ethernet-PHY-devices.patch b/target/linux/octeon/patches-5.15/710-netdev-phy-of-Handle-nexus-Ethernet-PHY-devices.patch
new file mode 100644 (file)
index 0000000..35dac54
--- /dev/null
@@ -0,0 +1,40 @@
+From c59d03f70e739eb7fec976f86fff7dd12968b3fc Mon Sep 17 00:00:00 2001
+From: Leonid Rosenboim <[email protected]>
+Date: Fri, 13 Feb 2015 15:04:26 +0530
+Subject: [PATCH 2/5] netdev/phy/of: Handle nexus Ethernet PHY devices
+
+Some multi-phy devices have resources that are global between all of the
+PHYs on the same device such as the Vitesse vsc8488.  In this case each
+individual PHY is contained within a phy nexus device.
+
+Signed-off-by: Leonid Rosenboim <[email protected]>
+Signed-off-by: Aaron Williams <[email protected]>
+Signed-off-by: Abhishek Paliwal <[email protected]>
+[rebase on 5.10]
+Signed-off-by: Stijn Tintel <[email protected]>
+---
+ drivers/net/mdio/of_mdio.c | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+--- a/drivers/net/mdio/of_mdio.c
++++ b/drivers/net/mdio/of_mdio.c
+@@ -18,6 +18,7 @@
+ #include <linux/of_irq.h>
+ #include <linux/of_mdio.h>
+ #include <linux/of_net.h>
++#include <linux/of_platform.h>
+ #include <linux/phy.h>
+ #include <linux/phy_fixed.h>
+@@ -177,6 +178,11 @@ int of_mdiobus_register(struct mii_bus *
+       /* Loop over the child nodes and register a phy_device for each phy */
+       for_each_available_child_of_node(np, child) {
++              if (of_device_is_compatible(child, "ethernet-phy-nexus")) {
++                      of_platform_device_create(child, NULL, &mdio->dev);
++                      continue;
++              }
++
+               addr = of_mdio_parse_addr(&mdio->dev, child);
+               if (addr < 0) {
+                       scanphys = true;
diff --git a/target/linux/octeon/patches-5.15/711-netdev-phy-Add-driver-for-Vitesse-vsc848x-single-dua.patch b/target/linux/octeon/patches-5.15/711-netdev-phy-Add-driver-for-Vitesse-vsc848x-single-dua.patch
new file mode 100644 (file)
index 0000000..3380bb3
--- /dev/null
@@ -0,0 +1,835 @@
+From f9687bdf31bfb633a2ac615f66fd9cb5ea042a4e Mon Sep 17 00:00:00 2001
+From: David Daney <[email protected]>
+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 <[email protected]>
+Signed-off-by: Aaron Williams <[email protected]>
+Signed-off-by: Leonid Rosenboim <[email protected]>
+Signed-off-by: Abhishek Paliwal <[email protected]>
+[Rebased on kernel 5.4 by Martin Kennedy <[email protected]>]
+Signed-off-by: Martin Kennedy <[email protected]>
+[rebase on 5.15, use nvmem instead of of_memory_accessor]
+Signed-off-by: Stijn Tintel <[email protected]>
+---
+ 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
+
+diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
+index 902495afcb38..554f5b2d51a5 100644
+--- a/drivers/net/phy/Kconfig
++++ b/drivers/net/phy/Kconfig
+@@ -339,6 +339,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
+diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
+index b2728d00fc9a..170096483fa0 100644
+--- a/drivers/net/phy/Makefile
++++ b/drivers/net/phy/Makefile
+@@ -84,4 +84,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
+diff --git a/drivers/net/phy/vsc848x.c b/drivers/net/phy/vsc848x.c
+new file mode 100644
+index 000000000000..cfe8ebd47c65
+--- /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");
+-- 
+2.37.3
+