Add a DSA driver for the MV88E6xxx compatible Ethernet switches.

Cc: Marek BehĂșn <marek.be...@nic.cz>
Cc: Vladimir Oltean <vladimir.olt...@nxp.com>
Signed-off-by: Tim Harvey <thar...@gateworks.com>
Reviewed-by: Vladimir Oltean <vladimir.olt...@nxp.com>
Reviewed-by: Fabio Estevam <feste...@denx.de>
---
v8:
- remove energy-detect low-power mode config

v7:
 - rebase on master
 - update commit short msg (s/MV88E61xx/MV88E6xxx)
 - replace inline smi_cmd* with macros using logical operators
 - replace bitfield_replace with logical operators for readibility
 - removed some unused register definitions
 - config switch based SERDES mode once in probe vs port_enable
 - rework port_enable to clean up and:
  - enable energy-detect for all non fixed PHY ports
  - configure RGMII delays based on interface type

v6:
 - update commit msg (s/MV88E61xx/MV88E6xxx)
 - remove GbE from commit msg and Kconfig desc
 - squash accidental change from patch 7 into patch 6
 - added error print on failure to read switch id
 - mv88e6xxx_probe:
  - check for switch enabled
  - remove unused variable enabled
  - remove unnecessary call to dsa_set_tagging
 - port_probe:
  - new function to configure phy features by calling phy_config
 - port_enable:
  - only enable energy-detect sensing on phy ports
  - add phy/cmode verification mistmatch warning
  - remove mv88e6xxx_fixed_port_setup()
  - always force link up for fixed ports
  - always set SERDES mode regardless of cpu port
  - remove unnecessary setting of CPUDEST
 - port_disable:
  - remove pointless error check
 - removed unnecessary priv data for mdio controller
 - fix indentation
 - change variable name for clarity

v5:
 - added support for MV88E6320
 - added Fabio's rb tag

v4:
 - rename to mv88e6xxx
 - sort includes alphabetically
 - remove dsa term from function names
 - reduce indentation level and remove unecessary code in of probe_mdio function
 - rename pdev to mdev to represent mdio device

v3:
 - Added Vladimir's rb tag

v2:
 - rebase on v2022.07-rc1 (use ofnode_get_phy_node)
 - remove unused commented out fields from struct
 - remove unused PORT_MASK macro
 - remove phy from priv struct name
 - refactor code from original drivers/net/phy/mv88e61xx with
   suggestions from review to consolidate some functions
   into mv88e61xx_dsa_port_enable
 - remove unecessary skiping of disabling of CPU port
 - remove unecessary dev_set_parent_priv
 - remove unnecessary static init flag
 - replace debug with a dev_warn if switch device-id unsupported
 - remove unnecessary xmit/recv functions as we rely on the fact that
   only a single port is active instead of mangling packets
---
 drivers/net/Kconfig     |   7 +
 drivers/net/Makefile    |   1 +
 drivers/net/mv88e6xxx.c | 755 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 763 insertions(+)
 create mode 100644 drivers/net/mv88e6xxx.c

diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index 6bbbadc5eef3..67f23917f42c 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -438,6 +438,13 @@ config KSZ9477
          This driver implements a DSA switch driver for the KSZ9477 family
          of GbE switches using the I2C interface.
 
+config MV88E6XXX
+       bool "Marvell MV88E6xxx Ethernet switch DSA driver"
+       depends on DM_DSA && DM_MDIO
+       help
+         This driver implements a DSA switch driver for the MV88E6xxx family
+         of Ethernet switches using the MDIO interface
+
 config MVGBE
        bool "Marvell Orion5x/Kirkwood network interface support"
        depends on ARCH_KIRKWOOD || ARCH_ORION5X
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index 96b7678e9882..f05fd58e3fa7 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -59,6 +59,7 @@ obj-$(CONFIG_MEDIATEK_ETH) += mtk_eth.o
 obj-$(CONFIG_MPC8XX_FEC) += mpc8xx_fec.o
 obj-$(CONFIG_MT7620_ETH) += mt7620-eth.o
 obj-$(CONFIG_MT7628_ETH) += mt7628-eth.o
+obj-$(CONFIG_MV88E6XXX) += mv88e6xxx.o
 obj-$(CONFIG_MVGBE) += mvgbe.o
 obj-$(CONFIG_MVMDIO) += mvmdio.o
 obj-$(CONFIG_MVNETA) += mvneta.o
diff --git a/drivers/net/mv88e6xxx.c b/drivers/net/mv88e6xxx.c
new file mode 100644
index 000000000000..64e860e324dc
--- /dev/null
+++ b/drivers/net/mv88e6xxx.c
@@ -0,0 +1,755 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * (C) Copyright 2022
+ * Gateworks Corporation <www.gateworks.com>
+ * Tim Harvey <thar...@gateworks.com>
+ *
+ * (C) Copyright 2015
+ * Elecsys Corporation <www.elecsyscorp.com>
+ * Kevin Smith <kevin.sm...@elecsyscorp.com>
+ *
+ * Original driver:
+ * (C) Copyright 2009
+ * Marvell Semiconductor <www.marvell.com>
+ * Prafulla Wadaskar <prafu...@marvell.com>
+ */
+
+/*
+ * DSA driver for mv88e6xxx ethernet switches.
+ *
+ * This driver configures the mv88e6xxx for basic use as a DSA switch.
+ *
+ * This driver was adapted from drivers/net/phy/mv88e61xx and tested
+ * on the mv88e6176 via an SGMII interface.
+ */
+
+#include <common.h>
+#include <dm/device.h>
+#include <dm/device_compat.h>
+#include <dm/device-internal.h>
+#include <dm/lists.h>
+#include <dm/of_extra.h>
+#include <linux/delay.h>
+#include <miiphy.h>
+#include <net/dsa.h>
+
+/* Device addresses */
+#define DEVADDR_PHY(p)                 (p)
+#define DEVADDR_SERDES                 0x0F
+
+/* SMI indirection registers for multichip addressing mode */
+#define SMI_CMD_REG                    0x00
+#define SMI_DATA_REG                   0x01
+
+/* Global registers */
+#define GLOBAL1_STATUS                 0x00
+#define GLOBAL1_CTRL                   0x04
+
+/* Global 2 registers */
+#define GLOBAL2_REG_PHY_CMD            0x18
+#define GLOBAL2_REG_PHY_DATA           0x19
+#define GLOBAL2_REG_SCRATCH            0x1A
+
+/* Port registers */
+#define PORT_REG_STATUS                        0x00
+#define PORT_REG_PHYS_CTRL             0x01
+#define PORT_REG_SWITCH_ID             0x03
+#define PORT_REG_CTRL                  0x04
+
+/* Phy registers */
+#define PHY_REG_PAGE                   0x16
+
+/* Phy page numbers */
+#define PHY_PAGE_COPPER                        0
+#define PHY_PAGE_SERDES                        1
+
+/* Register fields */
+#define GLOBAL1_CTRL_SWRESET           BIT(15)
+
+#define PORT_REG_STATUS_SPEED_SHIFT    8
+#define PORT_REG_STATUS_SPEED_10       0
+#define PORT_REG_STATUS_SPEED_100      1
+#define PORT_REG_STATUS_SPEED_1000     2
+
+#define PORT_REG_STATUS_CMODE_MASK             0xF
+#define PORT_REG_STATUS_CMODE_SGMII            0xa
+#define PORT_REG_STATUS_CMODE_1000BASE_X       0x9
+#define PORT_REG_STATUS_CMODE_100BASE_X                0x8
+#define PORT_REG_STATUS_CMODE_RGMII            0x7
+#define PORT_REG_STATUS_CMODE_RMII             0x5
+#define PORT_REG_STATUS_CMODE_RMII_PHY         0x4
+#define PORT_REG_STATUS_CMODE_GMII             0x3
+#define PORT_REG_STATUS_CMODE_MII              0x2
+#define PORT_REG_STATUS_CMODE_MIIPHY           0x1
+
+#define PORT_REG_PHYS_CTRL_RGMII_DELAY_RXCLK   BIT(15)
+#define PORT_REG_PHYS_CTRL_RGMII_DELAY_TXCLK   BIT(14)
+#define PORT_REG_PHYS_CTRL_PCS_AN_EN   BIT(10)
+#define PORT_REG_PHYS_CTRL_PCS_AN_RST  BIT(9)
+#define PORT_REG_PHYS_CTRL_FC_VALUE    BIT(7)
+#define PORT_REG_PHYS_CTRL_FC_FORCE    BIT(6)
+#define PORT_REG_PHYS_CTRL_LINK_VALUE  BIT(5)
+#define PORT_REG_PHYS_CTRL_LINK_FORCE  BIT(4)
+#define PORT_REG_PHYS_CTRL_DUPLEX_VALUE        BIT(3)
+#define PORT_REG_PHYS_CTRL_DUPLEX_FORCE        BIT(2)
+#define PORT_REG_PHYS_CTRL_SPD1000     BIT(1)
+#define PORT_REG_PHYS_CTRL_SPD100      BIT(0)
+#define PORT_REG_PHYS_CTRL_SPD_MASK    (BIT(1) | BIT(0))
+
+#define PORT_REG_CTRL_PSTATE_SHIFT     0
+#define PORT_REG_CTRL_PSTATE_MASK      3
+
+/* Field values */
+#define PORT_REG_CTRL_PSTATE_DISABLED  0
+#define PORT_REG_CTRL_PSTATE_FORWARD   3
+
+/*
+ * Macros for building commands for indirect addressing modes.  These are valid
+ * for both the indirect multichip addressing mode and the PHY indirection
+ * required for the writes to any PHY register.
+ */
+#define SMI_BUSY                       BIT(15)
+#define SMI_CMD_CLAUSE_22              BIT(12)
+#define SMI_CMD_CLAUSE_22_OP_READ      (2 << 10)
+#define SMI_CMD_CLAUSE_22_OP_WRITE     (1 << 10)
+#define SMI_CMD_ADDR_SHIFT             5
+#define SMI_CMD_ADDR_MASK              0x1f
+#define SMI_CMD_REG_SHIFT              0
+#define SMI_CMD_REG_MASK               0x1f
+#define SMI_CMD_READ(addr, reg) \
+       (SMI_BUSY | SMI_CMD_CLAUSE_22 | SMI_CMD_CLAUSE_22_OP_READ) | \
+       (((addr) & SMI_CMD_ADDR_MASK) << SMI_CMD_ADDR_SHIFT) | \
+       (((reg) & SMI_CMD_REG_MASK) << SMI_CMD_REG_SHIFT)
+#define SMI_CMD_WRITE(addr, reg) \
+       (SMI_BUSY | SMI_CMD_CLAUSE_22 | SMI_CMD_CLAUSE_22_OP_WRITE) | \
+       (((addr) & SMI_CMD_ADDR_MASK) << SMI_CMD_ADDR_SHIFT) | \
+       (((reg) & SMI_CMD_REG_MASK) << SMI_CMD_REG_SHIFT)
+
+/* ID register values for different switch models */
+#define PORT_SWITCH_ID_6020            0x0200
+#define PORT_SWITCH_ID_6070            0x0700
+#define PORT_SWITCH_ID_6071            0x0710
+#define PORT_SWITCH_ID_6096            0x0980
+#define PORT_SWITCH_ID_6097            0x0990
+#define PORT_SWITCH_ID_6172            0x1720
+#define PORT_SWITCH_ID_6176            0x1760
+#define PORT_SWITCH_ID_6220            0x2200
+#define PORT_SWITCH_ID_6240            0x2400
+#define PORT_SWITCH_ID_6250            0x2500
+#define PORT_SWITCH_ID_6320            0x1150
+#define PORT_SWITCH_ID_6352            0x3520
+
+struct mv88e6xxx_priv {
+       int smi_addr;
+       int id;
+       int port_count;         /* Number of switch ports */
+       int port_reg_base;      /* Base of the switch port registers */
+       u8 global1;     /* Offset of Switch Global 1 registers */
+       u8 global2;     /* Offset of Switch Global 2 registers */
+};
+
+/* Wait for the current SMI indirect command to complete */
+static int mv88e6xxx_smi_wait(struct udevice *dev, int smi_addr)
+{
+       int val;
+       u32 timeout = 100;
+
+       do {
+               val = dm_mdio_read(dev->parent, smi_addr, MDIO_DEVAD_NONE, 
SMI_CMD_REG);
+               if (val >= 0 && (val & SMI_BUSY) == 0)
+                       return 0;
+
+               mdelay(1);
+       } while (--timeout);
+
+       dev_err(dev, "SMI busy timeout\n");
+       return -ETIMEDOUT;
+}
+
+/*
+ * The mv88e6xxx has three types of addresses: the smi bus address, the device
+ * address, and the register address.  The smi bus address distinguishes it on
+ * the smi bus from other PHYs or switches.  The device address determines
+ * which on-chip register set you are reading/writing (the various PHYs, their
+ * associated ports, or global configuration registers).  The register address
+ * is the offset of the register you are reading/writing.
+ *
+ * When the mv88e6xxx is hardware configured to have address zero, it behaves 
in
+ * single-chip addressing mode, where it responds to all SMI addresses, using
+ * the smi address as its device address.  This obviously only works when this
+ * is the only chip on the SMI bus.  This allows the driver to access device
+ * registers without using indirection.  When the chip is configured to a
+ * non-zero address, it only responds to that SMI address and requires indirect
+ * writes to access the different device addresses.
+ */
+static int mv88e6xxx_reg_read(struct udevice *dev, int addr, int reg)
+{
+       struct mv88e6xxx_priv *priv = dev_get_priv(dev);
+       int smi_addr = priv->smi_addr;
+       int res;
+
+       /* In single-chip mode, the device can be addressed directly */
+       if (smi_addr == 0)
+               return dm_mdio_read(dev->parent, addr, MDIO_DEVAD_NONE, reg);
+
+       /* Wait for the bus to become free */
+       res = mv88e6xxx_smi_wait(dev, smi_addr);
+       if (res < 0)
+               return res;
+
+       /* Issue the read command */
+       res = dm_mdio_write(dev->parent, smi_addr, MDIO_DEVAD_NONE, SMI_CMD_REG,
+                           SMI_CMD_READ(addr, reg));
+       if (res < 0)
+               return res;
+
+       /* Wait for the read command to complete */
+       res = mv88e6xxx_smi_wait(dev, smi_addr);
+       if (res < 0)
+               return res;
+
+       /* Read the data */
+       res = dm_mdio_read(dev->parent, smi_addr, MDIO_DEVAD_NONE, 
SMI_DATA_REG);
+       if (res < 0)
+               return res;
+
+       return res & 0xffff;
+}
+
+/* See the comment above mv88e6xxx_reg_read */
+static int mv88e6xxx_reg_write(struct udevice *dev, int addr, int reg, u16 val)
+{
+       struct mv88e6xxx_priv *priv = dev_get_priv(dev);
+       int smi_addr = priv->smi_addr;
+       int res;
+
+       /* In single-chip mode, the device can be addressed directly */
+       if (smi_addr == 0)
+               return dm_mdio_write(dev->parent, addr, MDIO_DEVAD_NONE, reg, 
val);
+
+       /* Wait for the bus to become free */
+       res = mv88e6xxx_smi_wait(dev, smi_addr);
+       if (res < 0)
+               return res;
+
+       /* Set the data to write */
+       res = dm_mdio_write(dev->parent, smi_addr, MDIO_DEVAD_NONE,
+                           SMI_DATA_REG, val);
+       if (res < 0)
+               return res;
+
+       /* Issue the write command */
+       res = dm_mdio_write(dev->parent, smi_addr, MDIO_DEVAD_NONE, SMI_CMD_REG,
+                           SMI_CMD_WRITE(addr, reg));
+       if (res < 0)
+               return res;
+
+       /* Wait for the write command to complete */
+       res = mv88e6xxx_smi_wait(dev, smi_addr);
+       if (res < 0)
+               return res;
+
+       return 0;
+}
+
+static int mv88e6xxx_phy_wait(struct udevice *dev)
+{
+       struct mv88e6xxx_priv *priv = dev_get_priv(dev);
+       int val;
+       u32 timeout = 100;
+
+       do {
+               val = mv88e6xxx_reg_read(dev, priv->global2, 
GLOBAL2_REG_PHY_CMD);
+               if (val >= 0 && (val & SMI_BUSY) == 0)
+                       return 0;
+
+               mdelay(1);
+       } while (--timeout);
+
+       return -ETIMEDOUT;
+}
+
+static int mv88e6xxx_phy_read_indirect(struct udevice *dev, int phyad, int 
devad, int reg)
+{
+       struct mv88e6xxx_priv *priv = dev_get_priv(dev);
+       int res;
+
+       /* Issue command to read */
+       res = mv88e6xxx_reg_write(dev, priv->global2,
+                                 GLOBAL2_REG_PHY_CMD,
+                                 SMI_CMD_READ(phyad, reg));
+
+       /* Wait for data to be read */
+       res = mv88e6xxx_phy_wait(dev);
+       if (res < 0)
+               return res;
+
+       /* Read retrieved data */
+       return mv88e6xxx_reg_read(dev, priv->global2,
+                                 GLOBAL2_REG_PHY_DATA);
+}
+
+static int mv88e6xxx_phy_write_indirect(struct udevice *dev, int phyad,
+                                       int devad, int reg, u16 data)
+{
+       struct mv88e6xxx_priv *priv = dev_get_priv(dev);
+       int res;
+
+       /* Set the data to write */
+       res = mv88e6xxx_reg_write(dev, priv->global2,
+                                 GLOBAL2_REG_PHY_DATA, data);
+       if (res < 0)
+               return res;
+       /* Issue the write command */
+       res = mv88e6xxx_reg_write(dev, priv->global2,
+                                 GLOBAL2_REG_PHY_CMD,
+                                 SMI_CMD_WRITE(phyad, reg));
+       if (res < 0)
+               return res;
+
+       /* Wait for command to complete */
+       return mv88e6xxx_phy_wait(dev);
+}
+
+/* Wrapper function to make calls to phy_read_indirect simpler */
+static int mv88e6xxx_phy_read(struct udevice *dev, int phy, int reg)
+{
+       return mv88e6xxx_phy_read_indirect(dev, DEVADDR_PHY(phy),
+                                          MDIO_DEVAD_NONE, reg);
+}
+
+/* Wrapper function to make calls to phy_write_indirect simpler */
+static int mv88e6xxx_phy_write(struct udevice *dev, int phy, int reg, u16 val)
+{
+       return mv88e6xxx_phy_write_indirect(dev, DEVADDR_PHY(phy),
+                                           MDIO_DEVAD_NONE, reg, val);
+}
+
+static int mv88e6xxx_port_read(struct udevice *dev, u8 port, u8 reg)
+{
+       struct mv88e6xxx_priv *priv = dev_get_priv(dev);
+
+       return mv88e6xxx_reg_read(dev, priv->port_reg_base + port, reg);
+}
+
+static int mv88e6xxx_port_write(struct udevice *dev, u8 port, u8 reg, u16 val)
+{
+       struct mv88e6xxx_priv *priv = dev_get_priv(dev);
+
+       return mv88e6xxx_reg_write(dev, priv->port_reg_base + port, reg, val);
+}
+
+static int mv88e6xxx_set_page(struct udevice *dev, u8 phy, u8 page)
+{
+       return mv88e6xxx_phy_write(dev, phy, PHY_REG_PAGE, page);
+}
+
+static int mv88e6xxx_get_switch_id(struct udevice *dev)
+{
+       int res;
+
+       res = mv88e6xxx_port_read(dev, 0, PORT_REG_SWITCH_ID);
+       if (res < 0) {
+               dev_err(dev, "Failed to read switch ID: %d\n", res);
+               return res;
+       }
+       return res & 0xfff0;
+}
+
+static bool mv88e6xxx_6352_family(struct udevice *dev)
+{
+       struct mv88e6xxx_priv *priv = dev_get_priv(dev);
+
+       switch (priv->id) {
+       case PORT_SWITCH_ID_6172:
+       case PORT_SWITCH_ID_6176:
+       case PORT_SWITCH_ID_6240:
+       case PORT_SWITCH_ID_6352:
+               return true;
+       }
+       return false;
+}
+
+static int mv88e6xxx_get_cmode(struct udevice *dev, u8 port)
+{
+       int res;
+
+       res = mv88e6xxx_port_read(dev, port, PORT_REG_STATUS);
+       if (res < 0)
+               return res;
+       return res & PORT_REG_STATUS_CMODE_MASK;
+}
+
+static int mv88e6xxx_switch_reset(struct udevice *dev)
+{
+       struct mv88e6xxx_priv *priv = dev_get_priv(dev);
+       int time_ms;
+       int val;
+       u8 port;
+
+       /* Disable all ports */
+       for (port = 0; port < priv->port_count; port++) {
+               val = mv88e6xxx_port_read(dev, port, PORT_REG_CTRL);
+               if (val < 0)
+                       return val;
+               val &= ~(PORT_REG_CTRL_PSTATE_MASK << 
PORT_REG_CTRL_PSTATE_SHIFT);
+               val |= (PORT_REG_CTRL_PSTATE_DISABLED << 
PORT_REG_CTRL_PSTATE_SHIFT);
+               val = mv88e6xxx_port_write(dev, port, PORT_REG_CTRL, val);
+               if (val < 0)
+                       return val;
+       }
+
+       /* Wait 2 ms for queues to drain */
+       udelay(2000);
+
+       /* Reset switch */
+       val = mv88e6xxx_reg_read(dev, priv->global1, GLOBAL1_CTRL);
+       if (val < 0)
+               return val;
+       val |= GLOBAL1_CTRL_SWRESET;
+       val = mv88e6xxx_reg_write(dev, priv->global1, GLOBAL1_CTRL, val);
+       if (val < 0)
+               return val;
+
+       /* Wait up to 1 second for switch to reset complete */
+       for (time_ms = 1000; time_ms; time_ms--) {
+               val = mv88e6xxx_reg_read(dev, priv->global1, GLOBAL1_CTRL);
+               if (val >= 0 && ((val & GLOBAL1_CTRL_SWRESET) == 0))
+                       break;
+               udelay(1000);
+       }
+       if (!time_ms)
+               return -ETIMEDOUT;
+
+       return 0;
+}
+
+static int mv88e6xxx_serdes_init(struct udevice *dev)
+{
+       int val;
+
+       val = mv88e6xxx_set_page(dev, DEVADDR_SERDES, PHY_PAGE_SERDES);
+       if (val < 0)
+               return val;
+
+       /* Power up serdes module */
+       val = mv88e6xxx_phy_read(dev, DEVADDR_SERDES, MII_BMCR);
+       if (val < 0)
+               return val;
+       val &= ~(BMCR_PDOWN);
+       val = mv88e6xxx_phy_write(dev, DEVADDR_SERDES, MII_BMCR, val);
+       if (val < 0)
+               return val;
+
+       return 0;
+}
+
+/*
+ * This function is used to pre-configure the required register
+ * offsets, so that the indirect register access to the PHY registers
+ * is possible. This is necessary to be able to read the PHY ID
+ * while driver probing or in get_phy_id(). The globalN register
+ * offsets must be initialized correctly for a detected switch,
+ * otherwise detection of the PHY ID won't work!
+ */
+static int mv88e6xxx_priv_reg_offs_pre_init(struct udevice *dev)
+{
+       struct mv88e6xxx_priv *priv = dev_get_priv(dev);
+
+       /*
+        * Initial 'port_reg_base' value must be an offset of existing
+        * port register, then reading the ID should succeed. First, try
+        * to read via port registers with device address 0x10 (88E6096
+        * and compatible switches).
+        */
+       priv->port_reg_base = 0x10;
+       priv->id = mv88e6xxx_get_switch_id(dev);
+       if (priv->id != 0xfff0) {
+               priv->global1 = 0x1B;
+               priv->global2 = 0x1C;
+               return 0;
+       }
+
+       /*
+        * Now try via port registers with device address 0x08
+        * (88E6020 and compatible switches).
+        */
+       priv->port_reg_base = 0x08;
+       priv->id = mv88e6xxx_get_switch_id(dev);
+       if (priv->id != 0xfff0) {
+               priv->global1 = 0x0F;
+               priv->global2 = 0x07;
+               return 0;
+       }
+
+       dev_warn(dev, "%s Unknown ID 0x%x\n", __func__, priv->id);
+
+       return -ENODEV;
+}
+
+static int mv88e6xxx_mdio_read(struct udevice *dev, int addr, int devad, int 
reg)
+{
+       return mv88e6xxx_phy_read_indirect(dev->parent, DEVADDR_PHY(addr),
+                                          MDIO_DEVAD_NONE, reg);
+}
+
+static int mv88e6xxx_mdio_write(struct udevice *dev, int addr, int devad,
+                               int reg, u16 val)
+{
+       return mv88e6xxx_phy_write_indirect(dev->parent, DEVADDR_PHY(addr),
+                                           MDIO_DEVAD_NONE, reg, val);
+}
+
+static const struct mdio_ops mv88e6xxx_mdio_ops = {
+       .read = mv88e6xxx_mdio_read,
+       .write = mv88e6xxx_mdio_write,
+};
+
+static int mv88e6xxx_mdio_bind(struct udevice *dev)
+{
+       char name[32];
+       static int num_devices;
+
+       sprintf(name, "mv88e6xxx-mdio-%d", num_devices++);
+       device_set_name(dev, name);
+
+       return 0;
+}
+
+U_BOOT_DRIVER(mv88e6xxx_mdio) = {
+       .name           = "mv88e6xxx_mdio",
+       .id             = UCLASS_MDIO,
+       .ops            = &mv88e6xxx_mdio_ops,
+       .bind           = mv88e6xxx_mdio_bind,
+       .plat_auto      = sizeof(struct mdio_perdev_priv),
+};
+
+static int mv88e6xxx_port_probe(struct udevice *dev, int port, struct 
phy_device *phy)
+{
+       struct mv88e6xxx_priv *priv = dev_get_priv(dev);
+       int supported;
+
+       switch (priv->id) {
+       case PORT_SWITCH_ID_6020:
+       case PORT_SWITCH_ID_6070:
+       case PORT_SWITCH_ID_6071:
+               supported = PHY_BASIC_FEATURES | SUPPORTED_MII;
+               break;
+       default:
+               supported = PHY_GBIT_FEATURES;
+               break;
+       }
+
+       phy->supported &= supported;
+       phy->advertising &= supported;
+
+       return phy_config(phy);
+}
+
+static int mv88e6xxx_port_enable(struct udevice *dev, int port, struct 
phy_device *phy)
+{
+       int val, ret;
+
+       dev_dbg(dev, "%s P%d phy:0x%08x %s\n", __func__, port,
+               phy->phy_id, phy_string_for_interface(phy->interface));
+
+       if (phy->phy_id == PHY_FIXED_ID) {
+               /* Physical Control register: Table 62 */
+               val = mv88e6xxx_port_read(dev, port, PORT_REG_PHYS_CTRL);
+
+               /* configure RGMII delays for fixed link */
+               switch (phy->interface) {
+               case PHY_INTERFACE_MODE_RGMII:
+               case PHY_INTERFACE_MODE_RGMII_ID:
+               case PHY_INTERFACE_MODE_RGMII_RXID:
+               case PHY_INTERFACE_MODE_RGMII_TXID:
+                       dev_dbg(dev, "configure internal RGMII delays\n");
+
+                       /* RGMII delays */
+                       val &= ~(PORT_REG_PHYS_CTRL_RGMII_DELAY_RXCLK ||
+                                PORT_REG_PHYS_CTRL_RGMII_DELAY_TXCLK);
+                       if (phy->interface == PHY_INTERFACE_MODE_RGMII_ID ||
+                           phy->interface == PHY_INTERFACE_MODE_RGMII_RXID)
+                               val |= PORT_REG_PHYS_CTRL_RGMII_DELAY_RXCLK;
+                       if (phy->interface == PHY_INTERFACE_MODE_RGMII_ID ||
+                           phy->interface == PHY_INTERFACE_MODE_RGMII_TXID)
+                               val |= PORT_REG_PHYS_CTRL_RGMII_DELAY_TXCLK;
+                       break;
+               default:
+                       break;
+               }
+
+               /* Force Link */
+               val |= PORT_REG_PHYS_CTRL_LINK_VALUE |
+                      PORT_REG_PHYS_CTRL_LINK_FORCE;
+
+               ret = mv88e6xxx_port_write(dev, port, PORT_REG_PHYS_CTRL, val);
+               if (ret < 0)
+                       return ret;
+
+               if (mv88e6xxx_6352_family(dev)) {
+                       /* validate interface type */
+                       dev_dbg(dev, "validate interface type\n");
+                       val = mv88e6xxx_get_cmode(dev, port);
+                       if (val < 0)
+                               return val;
+                       switch (phy->interface) {
+                       case PHY_INTERFACE_MODE_RGMII:
+                       case PHY_INTERFACE_MODE_RGMII_RXID:
+                       case PHY_INTERFACE_MODE_RGMII_TXID:
+                       case PHY_INTERFACE_MODE_RGMII_ID:
+                               if (val != PORT_REG_STATUS_CMODE_RGMII)
+                                       goto mismatch;
+                               break;
+                       case PHY_INTERFACE_MODE_1000BASEX:
+                               if (val != PORT_REG_STATUS_CMODE_1000BASE_X)
+                                       goto mismatch;
+                               break;
+mismatch:
+                       default:
+                               dev_err(dev, "Mismatched PHY mode %s on port 
%d!\n",
+                                       
phy_string_for_interface(phy->interface), port);
+                               break;
+                       }
+               }
+       }
+
+       /* enable port */
+       val = mv88e6xxx_port_read(dev, port, PORT_REG_CTRL);
+       if (val < 0)
+               return val;
+       val &= ~(PORT_REG_CTRL_PSTATE_MASK << PORT_REG_CTRL_PSTATE_SHIFT);
+       val |= (PORT_REG_CTRL_PSTATE_FORWARD << PORT_REG_CTRL_PSTATE_SHIFT);
+       val = mv88e6xxx_port_write(dev, port, PORT_REG_CTRL, val);
+       if (val < 0)
+               return val;
+
+       return phy_startup(phy);
+}
+
+static void mv88e6xxx_port_disable(struct udevice *dev, int port, struct 
phy_device *phy)
+{
+       int val;
+
+       dev_dbg(dev, "%s P%d phy:0x%08x %s\n", __func__, port,
+               phy->phy_id, phy_string_for_interface(phy->interface));
+
+       val = mv88e6xxx_port_read(dev, port, PORT_REG_CTRL);
+       val &= ~(PORT_REG_CTRL_PSTATE_MASK << PORT_REG_CTRL_PSTATE_SHIFT);
+       val |= (PORT_REG_CTRL_PSTATE_DISABLED << PORT_REG_CTRL_PSTATE_SHIFT);
+       mv88e6xxx_port_write(dev, port, PORT_REG_CTRL, val);
+}
+
+static const struct dsa_ops mv88e6xxx_dsa_ops = {
+       .port_probe = mv88e6xxx_port_probe,
+       .port_enable = mv88e6xxx_port_enable,
+       .port_disable = mv88e6xxx_port_disable,
+};
+
+/* bind and probe the switch mdios */
+static int mv88e6xxx_probe_mdio(struct udevice *dev)
+{
+       struct udevice *mdev;
+       const char *name;
+       ofnode node;
+       int ret;
+
+       /* bind phy ports of mdio child node to mv88e6xxx_mdio device */
+       node = dev_read_subnode(dev, "mdio");
+       if (!ofnode_valid(node))
+               return 0;
+
+       name = ofnode_get_name(node);
+       ret = device_bind_driver_to_node(dev,
+                                        "mv88e6xxx_mdio",
+                                        name, node, NULL);
+       if (ret) {
+               dev_err(dev, "failed to bind %s: %d\n", name, ret);
+       } else {
+               /* need to probe it as there is no compatible to do so */
+               ret = uclass_get_device_by_ofnode(UCLASS_MDIO, node, &mdev);
+               if (ret)
+                       dev_err(dev, "failed to probe %s: %d\n", name, ret);
+       }
+
+       return ret;
+}
+
+static int mv88e6xxx_probe(struct udevice *dev)
+{
+       struct dsa_pdata *dsa_pdata = dev_get_uclass_plat(dev);
+       struct mv88e6xxx_priv *priv = dev_get_priv(dev);
+       int val, ret;
+
+       if (ofnode_valid(dev_ofnode(dev)) &&
+           !ofnode_is_enabled(dev_ofnode(dev))) {
+               dev_dbg(dev, "switch disabled\n");
+               return -ENODEV;
+       }
+
+       /* probe internal mdio bus */
+       ret = mv88e6xxx_probe_mdio(dev);
+       if (ret)
+               return ret;
+
+       ret = mv88e6xxx_priv_reg_offs_pre_init(dev);
+       if (ret)
+               return ret;
+
+       dev_dbg(dev, "ID=0x%x PORT_BASE=0x%02x GLOBAL1=0x%02x GLOBAL2=0x%02x\n",
+               priv->id, priv->port_reg_base, priv->global1, priv->global2);
+       switch (priv->id) {
+       case PORT_SWITCH_ID_6096:
+       case PORT_SWITCH_ID_6097:
+       case PORT_SWITCH_ID_6172:
+       case PORT_SWITCH_ID_6176:
+       case PORT_SWITCH_ID_6240:
+       case PORT_SWITCH_ID_6352:
+               priv->port_count = 11;
+               break;
+       case PORT_SWITCH_ID_6020:
+       case PORT_SWITCH_ID_6070:
+       case PORT_SWITCH_ID_6071:
+       case PORT_SWITCH_ID_6220:
+       case PORT_SWITCH_ID_6250:
+       case PORT_SWITCH_ID_6320:
+               priv->port_count = 7;
+               break;
+       default:
+               return -ENODEV;
+       }
+
+       ret = mv88e6xxx_switch_reset(dev);
+       if (ret < 0)
+               return ret;
+
+       if (mv88e6xxx_6352_family(dev)) {
+               val = mv88e6xxx_get_cmode(dev, dsa_pdata->cpu_port);
+               if (val < 0)
+                       return val;
+               /* initialize serdes */
+               if (val == PORT_REG_STATUS_CMODE_100BASE_X ||
+                   val == PORT_REG_STATUS_CMODE_1000BASE_X ||
+                   val == PORT_REG_STATUS_CMODE_SGMII) {
+                       ret = mv88e6xxx_serdes_init(dev);
+                       if (ret < 0)
+                               return ret;
+               }
+       }
+
+       return 0;
+}
+
+static const struct udevice_id mv88e6xxx_ids[] = {
+       { .compatible = "marvell,mv88e6085" },
+       { }
+};
+
+U_BOOT_DRIVER(mv88e6xxx) = {
+       .name           = "mv88e6xxx",
+       .id             = UCLASS_DSA,
+       .of_match       = mv88e6xxx_ids,
+       .probe          = mv88e6xxx_probe,
+       .ops            = &mv88e6xxx_dsa_ops,
+       .priv_auto      = sizeof(struct mv88e6xxx_priv),
+};
-- 
2.25.1

Reply via email to