Add patches for linux-5.4. The patches are from NXP LSDK-20.04 release which was tagged LSDK-20.04-V5.4. https://source.codeaurora.org/external/qoriq/qoriq-components/linux/ For boards LS1021A-IOT, and Traverse-LS1043 which are not involved in LSDK, port the dts patches from 4.14. The patches are sorted into the following categories: 301-arch-xxxx 302-dts-xxxx 303-core-xxxx 701-net-xxxx 801-audio-xxxx 802-can-xxxx 803-clock-xxxx 804-crypto-xxxx 805-display-xxxx 806-dma-xxxx 807-gpio-xxxx 808-i2c-xxxx 809-jailhouse-xxxx 810-keys-xxxx 811-kvm-xxxx 812-pcie-xxxx 813-pm-xxxx 814-qe-xxxx 815-sata-xxxx 816-sdhc-xxxx 817-spi-xxxx 818-thermal-xxxx 819-uart-xxxx 820-usb-xxxx 821-vfio-xxxx Signed-off-by: Yangbo Lu <yangbo.lu@nxp.com>
		
			
				
	
	
		
			1088 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
			
		
		
	
	
			1088 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
From 78d77114e35a491232c50e054f43c980bbf9d434 Mon Sep 17 00:00:00 2001
 | 
						|
From: Vladimir Oltean <vladimir.oltean@nxp.com>
 | 
						|
Date: Mon, 6 Jan 2020 03:34:17 +0200
 | 
						|
Subject: [PATCH] net: dsa: felix: Add PCS operations for PHYLINK
 | 
						|
 | 
						|
Layerscape SoCs traditionally expose the SerDes configuration/status for
 | 
						|
Ethernet protocols (PCS for SGMII/USXGMII/10GBase-R etc etc) in a register
 | 
						|
format that is compatible with clause 22 or clause 45 (depending on
 | 
						|
SerDes protocol). Each MAC has its own internal MDIO bus on which there
 | 
						|
is one or more of these PCS's, responding to commands at a configurable
 | 
						|
PHY address. The per-port internal MDIO bus (which is just for PCSs) is
 | 
						|
totally separate and has nothing to do with the dedicated external MDIO
 | 
						|
controller (which is just for PHYs), but the register map for the MDIO
 | 
						|
controller is the same.
 | 
						|
 | 
						|
The VSC9959 (Felix) switch instantiated in the LS1028A is integrated
 | 
						|
in hardware with the ENETC PCS of its DSA master, and reuses its MDIO
 | 
						|
controller driver, so Felix has been made to depend on it in Kconfig.
 | 
						|
 | 
						|
 +------------------------------------------------------------------------+
 | 
						|
 |                   +--------+ GMII (typically disabled via RCW)         |
 | 
						|
 | ENETC PCI         |  ENETC |--------------------------+                |
 | 
						|
 | Root Complex      | port 3 |-----------------------+  |                |
 | 
						|
 | Integrated        +--------+                       |  |                |
 | 
						|
 | Endpoint                                           |  |                |
 | 
						|
 |                   +--------+ 2.5G GMII             |  |                |
 | 
						|
 |                   |  ENETC |--------------+        |  |                |
 | 
						|
 |                   | port 2 |-----------+  |        |  |                |
 | 
						|
 |                   +--------+           |  |        |  |                |
 | 
						|
 |                                     +--------+  +--------+             |
 | 
						|
 |                                     |  Felix |  |  Felix |             |
 | 
						|
 |                                     | port 4 |  | port 5 |             |
 | 
						|
 |                                     +--------+  +--------+             |
 | 
						|
 |                                                                        |
 | 
						|
 | +--------+  +--------+  +--------+  +--------+  +--------+  +--------+ |
 | 
						|
 | |  ENETC |  |  ENETC |  |  Felix |  |  Felix |  |  Felix |  |  Felix | |
 | 
						|
 | | port 0 |  | port 1 |  | port 0 |  | port 1 |  | port 2 |  | port 3 | |
 | 
						|
 +------------------------------------------------------------------------+
 | 
						|
 |    ||||  SerDes |          ||||        ||||        ||||        ||||    |
 | 
						|
 | +--------+block |       +--------------------------------------------+ |
 | 
						|
 | |  ENETC |      |       |       ENETC port 2 internal MDIO bus       | |
 | 
						|
 | | port 0 |      |       |  PCS         PCS          PCS        PCS   | |
 | 
						|
 | |   PCS  |      |       |   0           1            2          3    | |
 | 
						|
 +-----------------|------------------------------------------------------+
 | 
						|
        v          v           v           v            v          v
 | 
						|
     SGMII/      RGMII    QSGMII/QSXGMII/4xSGMII/4x1000Base-X/4x2500Base-X
 | 
						|
    USXGMII/   (bypasses
 | 
						|
  1000Base-X/   SerDes)
 | 
						|
  2500Base-X
 | 
						|
 | 
						|
In the LS1028A SoC described above, the VSC9959 Felix switch is PF5 of
 | 
						|
the ENETC root complex, and has 2 BARs:
 | 
						|
- BAR 4: the switch's effective registers
 | 
						|
- BAR 0: the MDIO controller register map lended from ENETC port 2
 | 
						|
         (PF2), for accessing its associated PCS's.
 | 
						|
 | 
						|
This explanation is necessary because the patch does some renaming
 | 
						|
"pci_bar" -> "switch_pci_bar" for clarity, which would otherwise appear
 | 
						|
a bit obtuse.
 | 
						|
 | 
						|
The fact that the internal MDIO bus is "borrowed" is relevant because
 | 
						|
the register map is found in PF5 (the switch) but it triggers an access
 | 
						|
fault if PF2 (the ENETC DSA master) is not enabled. This is not treated
 | 
						|
in any way (and I don't think it can be treated).
 | 
						|
 | 
						|
All of this is so SoC-specific, that it was contained as much as
 | 
						|
possible in the platform-integration file felix_vsc9959.c.
 | 
						|
 | 
						|
We need to parse and pre-validate the device tree because of 2 reasons:
 | 
						|
- The PHY mode (SerDes protocol) cannot change at runtime due to SoC
 | 
						|
  design.
 | 
						|
- There is a circular dependency in that we need to know what clause the
 | 
						|
  PCS speaks in order to find it on the internal MDIO bus. But the
 | 
						|
  clause of the PCS depends on what phy-mode it is configured for.
 | 
						|
 | 
						|
The goal of this patch is to make steps towards removing the bootloader
 | 
						|
dependency for SGMII PCS pre-configuration, as well as to add support
 | 
						|
for monitoring the in-band SGMII AN between the PCS and the system-side
 | 
						|
link partner (PHY or other MAC).
 | 
						|
 | 
						|
In practice the bootloader dependency is not completely removed. U-Boot
 | 
						|
pre-programs the PHY address at which each PCS can be found on the
 | 
						|
internal MDIO bus (MDEV_PORT). This is needed because the PCS of each
 | 
						|
port has the same out-of-reset PHY address of zero. The SerDes register
 | 
						|
for changing MDEV_PORT is pretty deep in the SoC (outside the addresses
 | 
						|
of the ENETC PCI BARs) and therefore inaccessible to us from here.
 | 
						|
 | 
						|
Felix VSC9959 and Ocelot VSC7514 are integrated very differently in
 | 
						|
their respective SoCs, and for that reason Felix does not use the Ocelot
 | 
						|
core library for PHYLINK. On one hand we don't want to impose the
 | 
						|
fixed phy-mode limitation to Ocelot, and on the other hand Felix doesn't
 | 
						|
need to force the MAC link speed the way Ocelot does, since the MAC is
 | 
						|
connected to the PCS through a fixed GMII, and the PCS is the one who
 | 
						|
does the rate adaptation at lower link speeds, which the MAC does not
 | 
						|
even need to know about. In fact changing the GMII speed for Felix
 | 
						|
irrecoverably breaks transmission through that port until a reset.
 | 
						|
 | 
						|
The pair with ENETC port 3 and Felix port 5 is optional and doesn't
 | 
						|
support tagging. When we enable it, swp5 is a regular slave port, albeit
 | 
						|
an internal one. The trouble is that it doesn't work, and that is
 | 
						|
because the DSA PHYLIB adaptation layer doesn't treat fixed-link slave
 | 
						|
ports. So that is yet another reason for wanting to convert Felix to the
 | 
						|
native PHYLINK API.
 | 
						|
 | 
						|
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
 | 
						|
 | 
						|
Conflicts:
 | 
						|
	drivers/net/dsa/ocelot/felix.c
 | 
						|
 | 
						|
with the upstream API change for of_get_phy_mode() introduced in
 | 
						|
0c65b2b90d13 ("net: of_get_phy_mode: Change API to solve int/unit
 | 
						|
warnings") and merged in v5.4-rc5.
 | 
						|
---
 | 
						|
 drivers/net/dsa/ocelot/Kconfig         |   2 +
 | 
						|
 drivers/net/dsa/ocelot/felix.c         | 267 +++++++++++++++++-
 | 
						|
 drivers/net/dsa/ocelot/felix.h         |  16 +-
 | 
						|
 drivers/net/dsa/ocelot/felix_vsc9959.c | 501 ++++++++++++++++++++++++++++++++-
 | 
						|
 4 files changed, 769 insertions(+), 17 deletions(-)
 | 
						|
 | 
						|
--- a/drivers/net/dsa/ocelot/Kconfig
 | 
						|
+++ b/drivers/net/dsa/ocelot/Kconfig
 | 
						|
@@ -3,8 +3,10 @@ config NET_DSA_MSCC_FELIX
 | 
						|
 	tristate "Ocelot / Felix Ethernet switch support"
 | 
						|
 	depends on NET_DSA && PCI
 | 
						|
 	depends on NET_VENDOR_MICROSEMI
 | 
						|
+	depends on NET_VENDOR_FREESCALE
 | 
						|
 	select MSCC_OCELOT_SWITCH
 | 
						|
 	select NET_DSA_TAG_OCELOT
 | 
						|
+	select FSL_ENETC_MDIO
 | 
						|
 	help
 | 
						|
 	  This driver supports the VSC9959 network switch, which is a member of
 | 
						|
 	  the Vitesse / Microsemi / Microchip Ocelot family of switching cores.
 | 
						|
--- a/drivers/net/dsa/ocelot/felix.c
 | 
						|
+++ b/drivers/net/dsa/ocelot/felix.c
 | 
						|
@@ -2,9 +2,14 @@
 | 
						|
 /* Copyright 2019 NXP Semiconductors
 | 
						|
  */
 | 
						|
 #include <uapi/linux/if_bridge.h>
 | 
						|
+#include <soc/mscc/ocelot_qsys.h>
 | 
						|
+#include <soc/mscc/ocelot_sys.h>
 | 
						|
+#include <soc/mscc/ocelot_dev.h>
 | 
						|
+#include <soc/mscc/ocelot_ana.h>
 | 
						|
 #include <soc/mscc/ocelot.h>
 | 
						|
 #include <linux/packing.h>
 | 
						|
 #include <linux/module.h>
 | 
						|
+#include <linux/of_net.h>
 | 
						|
 #include <linux/pci.h>
 | 
						|
 #include <linux/of.h>
 | 
						|
 #include <net/dsa.h>
 | 
						|
@@ -58,14 +63,6 @@ static int felix_set_ageing_time(struct
 | 
						|
 	return 0;
 | 
						|
 }
 | 
						|
 
 | 
						|
-static void felix_adjust_link(struct dsa_switch *ds, int port,
 | 
						|
-			      struct phy_device *phydev)
 | 
						|
-{
 | 
						|
-	struct ocelot *ocelot = ds->priv;
 | 
						|
-
 | 
						|
-	ocelot_adjust_link(ocelot, port, phydev);
 | 
						|
-}
 | 
						|
-
 | 
						|
 static int felix_fdb_dump(struct dsa_switch *ds, int port,
 | 
						|
 			  dsa_fdb_dump_cb_t *cb, void *data)
 | 
						|
 {
 | 
						|
@@ -202,6 +199,138 @@ static void felix_port_disable(struct ds
 | 
						|
 	return ocelot_port_disable(ocelot, port);
 | 
						|
 }
 | 
						|
 
 | 
						|
+static void felix_phylink_validate(struct dsa_switch *ds, int port,
 | 
						|
+				   unsigned long *supported,
 | 
						|
+				   struct phylink_link_state *state)
 | 
						|
+{
 | 
						|
+	struct ocelot *ocelot = ds->priv;
 | 
						|
+	struct ocelot_port *ocelot_port = ocelot->ports[port];
 | 
						|
+	__ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = { 0, };
 | 
						|
+
 | 
						|
+	if (state->interface != PHY_INTERFACE_MODE_NA &&
 | 
						|
+	    state->interface != ocelot_port->phy_mode) {
 | 
						|
+		bitmap_zero(supported, __ETHTOOL_LINK_MODE_MASK_NBITS);
 | 
						|
+		return;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	/* No half-duplex. */
 | 
						|
+	phylink_set_port_modes(mask);
 | 
						|
+	phylink_set(mask, Autoneg);
 | 
						|
+	phylink_set(mask, Pause);
 | 
						|
+	phylink_set(mask, Asym_Pause);
 | 
						|
+	if (state->interface != PHY_INTERFACE_MODE_2500BASEX) {
 | 
						|
+		phylink_set(mask, 10baseT_Full);
 | 
						|
+		phylink_set(mask, 100baseT_Full);
 | 
						|
+		phylink_set(mask, 1000baseT_Full);
 | 
						|
+	}
 | 
						|
+	/* The internal ports that run at 2.5G are overclocked GMII */
 | 
						|
+	if (state->interface == PHY_INTERFACE_MODE_GMII ||
 | 
						|
+	    state->interface == PHY_INTERFACE_MODE_2500BASEX ||
 | 
						|
+	    state->interface == PHY_INTERFACE_MODE_USXGMII) {
 | 
						|
+		phylink_set(mask, 2500baseT_Full);
 | 
						|
+		phylink_set(mask, 2500baseX_Full);
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	bitmap_and(supported, supported, mask,
 | 
						|
+		   __ETHTOOL_LINK_MODE_MASK_NBITS);
 | 
						|
+	bitmap_and(state->advertising, state->advertising, mask,
 | 
						|
+		   __ETHTOOL_LINK_MODE_MASK_NBITS);
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int felix_phylink_mac_pcs_get_state(struct dsa_switch *ds, int port,
 | 
						|
+					   struct phylink_link_state *state)
 | 
						|
+{
 | 
						|
+	struct ocelot *ocelot = ds->priv;
 | 
						|
+	struct felix *felix = ocelot_to_felix(ocelot);
 | 
						|
+
 | 
						|
+	if (felix->info->pcs_link_state)
 | 
						|
+		felix->info->pcs_link_state(ocelot, port, state);
 | 
						|
+
 | 
						|
+	return 0;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static void felix_phylink_mac_config(struct dsa_switch *ds, int port,
 | 
						|
+				     unsigned int link_an_mode,
 | 
						|
+				     const struct phylink_link_state *state)
 | 
						|
+{
 | 
						|
+	struct ocelot *ocelot = ds->priv;
 | 
						|
+	struct ocelot_port *ocelot_port = ocelot->ports[port];
 | 
						|
+	struct felix *felix = ocelot_to_felix(ocelot);
 | 
						|
+	u32 mac_fc_cfg;
 | 
						|
+
 | 
						|
+	/* Take port out of reset by clearing the MAC_TX_RST, MAC_RX_RST and
 | 
						|
+	 * PORT_RST bits in CLOCK_CFG
 | 
						|
+	 */
 | 
						|
+	ocelot_port_writel(ocelot_port, DEV_CLOCK_CFG_LINK_SPEED(state->speed),
 | 
						|
+			   DEV_CLOCK_CFG);
 | 
						|
+
 | 
						|
+	/* Flow control. Link speed is only used here to evaluate the time
 | 
						|
+	 * specification in incoming pause frames.
 | 
						|
+	 */
 | 
						|
+	mac_fc_cfg = SYS_MAC_FC_CFG_FC_LINK_SPEED(state->speed);
 | 
						|
+	if (state->pause & MLO_PAUSE_RX)
 | 
						|
+		mac_fc_cfg |= SYS_MAC_FC_CFG_RX_FC_ENA;
 | 
						|
+	if (state->pause & MLO_PAUSE_TX)
 | 
						|
+		mac_fc_cfg |= SYS_MAC_FC_CFG_TX_FC_ENA |
 | 
						|
+			      SYS_MAC_FC_CFG_PAUSE_VAL_CFG(0xffff) |
 | 
						|
+			      SYS_MAC_FC_CFG_FC_LATENCY_CFG(0x7) |
 | 
						|
+			      SYS_MAC_FC_CFG_ZERO_PAUSE_ENA;
 | 
						|
+	ocelot_write_rix(ocelot, mac_fc_cfg, SYS_MAC_FC_CFG, port);
 | 
						|
+
 | 
						|
+	ocelot_write_rix(ocelot, 0, ANA_POL_FLOWC, port);
 | 
						|
+
 | 
						|
+	if (felix->info->pcs_init)
 | 
						|
+		felix->info->pcs_init(ocelot, port, link_an_mode, state);
 | 
						|
+}
 | 
						|
+
 | 
						|
+static void felix_phylink_mac_an_restart(struct dsa_switch *ds, int port)
 | 
						|
+{
 | 
						|
+	struct ocelot *ocelot = ds->priv;
 | 
						|
+	struct felix *felix = ocelot_to_felix(ocelot);
 | 
						|
+
 | 
						|
+	if (felix->info->pcs_an_restart)
 | 
						|
+		felix->info->pcs_an_restart(ocelot, port);
 | 
						|
+}
 | 
						|
+
 | 
						|
+static void felix_phylink_mac_link_down(struct dsa_switch *ds, int port,
 | 
						|
+					unsigned int link_an_mode,
 | 
						|
+					phy_interface_t interface)
 | 
						|
+{
 | 
						|
+	struct ocelot *ocelot = ds->priv;
 | 
						|
+	struct ocelot_port *ocelot_port = ocelot->ports[port];
 | 
						|
+
 | 
						|
+	ocelot_port_writel(ocelot_port, 0, DEV_MAC_ENA_CFG);
 | 
						|
+	ocelot_rmw_rix(ocelot, 0, QSYS_SWITCH_PORT_MODE_PORT_ENA,
 | 
						|
+		       QSYS_SWITCH_PORT_MODE, port);
 | 
						|
+}
 | 
						|
+
 | 
						|
+static void felix_phylink_mac_link_up(struct dsa_switch *ds, int port,
 | 
						|
+				      unsigned int link_an_mode,
 | 
						|
+				      phy_interface_t interface,
 | 
						|
+				      struct phy_device *phydev)
 | 
						|
+{
 | 
						|
+	struct ocelot *ocelot = ds->priv;
 | 
						|
+	struct ocelot_port *ocelot_port = ocelot->ports[port];
 | 
						|
+
 | 
						|
+	/* Enable MAC module */
 | 
						|
+	ocelot_port_writel(ocelot_port, DEV_MAC_ENA_CFG_RX_ENA |
 | 
						|
+			   DEV_MAC_ENA_CFG_TX_ENA, DEV_MAC_ENA_CFG);
 | 
						|
+
 | 
						|
+	/* Enable receiving frames on the port, and activate auto-learning of
 | 
						|
+	 * MAC addresses.
 | 
						|
+	 */
 | 
						|
+	ocelot_write_gix(ocelot, ANA_PORT_PORT_CFG_LEARNAUTO |
 | 
						|
+			 ANA_PORT_PORT_CFG_RECV_ENA |
 | 
						|
+			 ANA_PORT_PORT_CFG_PORTID_VAL(port),
 | 
						|
+			 ANA_PORT_PORT_CFG, port);
 | 
						|
+
 | 
						|
+	/* Core: Enable port for frame transfer */
 | 
						|
+	ocelot_write_rix(ocelot, QSYS_SWITCH_PORT_MODE_INGRESS_DROP_MODE |
 | 
						|
+			 QSYS_SWITCH_PORT_MODE_SCH_NEXT_CFG(1) |
 | 
						|
+			 QSYS_SWITCH_PORT_MODE_PORT_ENA,
 | 
						|
+			 QSYS_SWITCH_PORT_MODE, port);
 | 
						|
+}
 | 
						|
+
 | 
						|
 static void felix_get_strings(struct dsa_switch *ds, int port,
 | 
						|
 			      u32 stringset, u8 *data)
 | 
						|
 {
 | 
						|
@@ -232,10 +361,78 @@ static int felix_get_ts_info(struct dsa_
 | 
						|
 	return ocelot_get_ts_info(ocelot, port, info);
 | 
						|
 }
 | 
						|
 
 | 
						|
+static int felix_parse_ports_node(struct felix *felix,
 | 
						|
+				  struct device_node *ports_node,
 | 
						|
+				  phy_interface_t *port_phy_modes)
 | 
						|
+{
 | 
						|
+	struct ocelot *ocelot = &felix->ocelot;
 | 
						|
+	struct device *dev = felix->ocelot.dev;
 | 
						|
+	struct device_node *child;
 | 
						|
+
 | 
						|
+	for_each_child_of_node(ports_node, child) {
 | 
						|
+		phy_interface_t phy_mode;
 | 
						|
+		u32 port;
 | 
						|
+		int err;
 | 
						|
+
 | 
						|
+		/* Get switch port number from DT */
 | 
						|
+		if (of_property_read_u32(child, "reg", &port) < 0) {
 | 
						|
+			dev_err(dev, "Port number not defined in device tree "
 | 
						|
+				"(property \"reg\")\n");
 | 
						|
+			of_node_put(child);
 | 
						|
+			return -ENODEV;
 | 
						|
+		}
 | 
						|
+
 | 
						|
+		/* Get PHY mode from DT */
 | 
						|
+		err = of_get_phy_mode(child);
 | 
						|
+		if (err < 0) {
 | 
						|
+			dev_err(dev, "Failed to read phy-mode or "
 | 
						|
+				"phy-interface-type property for port %d\n",
 | 
						|
+				port);
 | 
						|
+			of_node_put(child);
 | 
						|
+			return -ENODEV;
 | 
						|
+		}
 | 
						|
+
 | 
						|
+		phy_mode = err;
 | 
						|
+
 | 
						|
+		err = felix->info->prevalidate_phy_mode(ocelot, port, phy_mode);
 | 
						|
+		if (err < 0) {
 | 
						|
+			dev_err(dev, "Unsupported PHY mode %s on port %d\n",
 | 
						|
+				phy_modes(phy_mode), port);
 | 
						|
+			return err;
 | 
						|
+		}
 | 
						|
+
 | 
						|
+		port_phy_modes[port] = phy_mode;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	return 0;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int felix_parse_dt(struct felix *felix, phy_interface_t *port_phy_modes)
 | 
						|
+{
 | 
						|
+	struct device *dev = felix->ocelot.dev;
 | 
						|
+	struct device_node *switch_node;
 | 
						|
+	struct device_node *ports_node;
 | 
						|
+	int err;
 | 
						|
+
 | 
						|
+	switch_node = dev->of_node;
 | 
						|
+
 | 
						|
+	ports_node = of_get_child_by_name(switch_node, "ports");
 | 
						|
+	if (!ports_node) {
 | 
						|
+		dev_err(dev, "Incorrect bindings: absent \"ports\" node\n");
 | 
						|
+		return -ENODEV;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	err = felix_parse_ports_node(felix, ports_node, port_phy_modes);
 | 
						|
+	of_node_put(ports_node);
 | 
						|
+
 | 
						|
+	return err;
 | 
						|
+}
 | 
						|
+
 | 
						|
 static int felix_init_structs(struct felix *felix, int num_phys_ports)
 | 
						|
 {
 | 
						|
 	struct ocelot *ocelot = &felix->ocelot;
 | 
						|
-	resource_size_t base;
 | 
						|
+	phy_interface_t *port_phy_modes;
 | 
						|
+	resource_size_t switch_base;
 | 
						|
 	int port, i, err;
 | 
						|
 
 | 
						|
 	ocelot->num_phys_ports = num_phys_ports;
 | 
						|
@@ -250,7 +447,19 @@ static int felix_init_structs(struct fel
 | 
						|
 	ocelot->shared_queue_sz	= felix->info->shared_queue_sz;
 | 
						|
 	ocelot->ops		= felix->info->ops;
 | 
						|
 
 | 
						|
-	base = pci_resource_start(felix->pdev, felix->info->pci_bar);
 | 
						|
+	port_phy_modes = kcalloc(num_phys_ports, sizeof(phy_interface_t),
 | 
						|
+				 GFP_KERNEL);
 | 
						|
+	if (!port_phy_modes)
 | 
						|
+		return -ENOMEM;
 | 
						|
+
 | 
						|
+	err = felix_parse_dt(felix, port_phy_modes);
 | 
						|
+	if (err) {
 | 
						|
+		kfree(port_phy_modes);
 | 
						|
+		return err;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	switch_base = pci_resource_start(felix->pdev,
 | 
						|
+					 felix->info->switch_pci_bar);
 | 
						|
 
 | 
						|
 	for (i = 0; i < TARGET_MAX; i++) {
 | 
						|
 		struct regmap *target;
 | 
						|
@@ -261,13 +470,14 @@ static int felix_init_structs(struct fel
 | 
						|
 
 | 
						|
 		res = &felix->info->target_io_res[i];
 | 
						|
 		res->flags = IORESOURCE_MEM;
 | 
						|
-		res->start += base;
 | 
						|
-		res->end += base;
 | 
						|
+		res->start += switch_base;
 | 
						|
+		res->end += switch_base;
 | 
						|
 
 | 
						|
 		target = ocelot_regmap_init(ocelot, res);
 | 
						|
 		if (IS_ERR(target)) {
 | 
						|
 			dev_err(ocelot->dev,
 | 
						|
 				"Failed to map device memory space\n");
 | 
						|
+			kfree(port_phy_modes);
 | 
						|
 			return PTR_ERR(target);
 | 
						|
 		}
 | 
						|
 
 | 
						|
@@ -277,6 +487,7 @@ static int felix_init_structs(struct fel
 | 
						|
 	err = ocelot_regfields_init(ocelot, felix->info->regfields);
 | 
						|
 	if (err) {
 | 
						|
 		dev_err(ocelot->dev, "failed to init reg fields map\n");
 | 
						|
+		kfree(port_phy_modes);
 | 
						|
 		return err;
 | 
						|
 	}
 | 
						|
 
 | 
						|
@@ -291,26 +502,37 @@ static int felix_init_structs(struct fel
 | 
						|
 		if (!ocelot_port) {
 | 
						|
 			dev_err(ocelot->dev,
 | 
						|
 				"failed to allocate port memory\n");
 | 
						|
+			kfree(port_phy_modes);
 | 
						|
 			return -ENOMEM;
 | 
						|
 		}
 | 
						|
 
 | 
						|
 		res = &felix->info->port_io_res[port];
 | 
						|
 		res->flags = IORESOURCE_MEM;
 | 
						|
-		res->start += base;
 | 
						|
-		res->end += base;
 | 
						|
+		res->start += switch_base;
 | 
						|
+		res->end += switch_base;
 | 
						|
 
 | 
						|
 		port_regs = devm_ioremap_resource(ocelot->dev, res);
 | 
						|
 		if (IS_ERR(port_regs)) {
 | 
						|
 			dev_err(ocelot->dev,
 | 
						|
 				"failed to map registers for port %d\n", port);
 | 
						|
+			kfree(port_phy_modes);
 | 
						|
 			return PTR_ERR(port_regs);
 | 
						|
 		}
 | 
						|
 
 | 
						|
+		ocelot_port->phy_mode = port_phy_modes[port];
 | 
						|
 		ocelot_port->ocelot = ocelot;
 | 
						|
 		ocelot_port->regs = port_regs;
 | 
						|
 		ocelot->ports[port] = ocelot_port;
 | 
						|
 	}
 | 
						|
 
 | 
						|
+	kfree(port_phy_modes);
 | 
						|
+
 | 
						|
+	if (felix->info->mdio_bus_alloc) {
 | 
						|
+		err = felix->info->mdio_bus_alloc(ocelot);
 | 
						|
+		if (err < 0)
 | 
						|
+			return err;
 | 
						|
+	}
 | 
						|
+
 | 
						|
 	return 0;
 | 
						|
 }
 | 
						|
 
 | 
						|
@@ -340,12 +562,22 @@ static int felix_setup(struct dsa_switch
 | 
						|
 					    OCELOT_TAG_PREFIX_LONG);
 | 
						|
 	}
 | 
						|
 
 | 
						|
+	/* It looks like the MAC/PCS interrupt register - PM0_IEVENT (0x8040)
 | 
						|
+	 * isn't instantiated for the Felix PF.
 | 
						|
+	 * In-band AN may take a few ms to complete, so we need to poll.
 | 
						|
+	 */
 | 
						|
+	ds->pcs_poll = true;
 | 
						|
+
 | 
						|
 	return 0;
 | 
						|
 }
 | 
						|
 
 | 
						|
 static void felix_teardown(struct dsa_switch *ds)
 | 
						|
 {
 | 
						|
 	struct ocelot *ocelot = ds->priv;
 | 
						|
+	struct felix *felix = ocelot_to_felix(ocelot);
 | 
						|
+
 | 
						|
+	if (felix->info->mdio_bus_free)
 | 
						|
+		felix->info->mdio_bus_free(ocelot);
 | 
						|
 
 | 
						|
 	/* stop workqueue thread */
 | 
						|
 	ocelot_deinit(ocelot);
 | 
						|
@@ -416,7 +648,12 @@ static const struct dsa_switch_ops felix
 | 
						|
 	.get_ethtool_stats	= felix_get_ethtool_stats,
 | 
						|
 	.get_sset_count		= felix_get_sset_count,
 | 
						|
 	.get_ts_info		= felix_get_ts_info,
 | 
						|
-	.adjust_link		= felix_adjust_link,
 | 
						|
+	.phylink_validate	= felix_phylink_validate,
 | 
						|
+	.phylink_mac_link_state	= felix_phylink_mac_pcs_get_state,
 | 
						|
+	.phylink_mac_config	= felix_phylink_mac_config,
 | 
						|
+	.phylink_mac_an_restart	= felix_phylink_mac_an_restart,
 | 
						|
+	.phylink_mac_link_down	= felix_phylink_mac_link_down,
 | 
						|
+	.phylink_mac_link_up	= felix_phylink_mac_link_up,
 | 
						|
 	.port_enable		= felix_port_enable,
 | 
						|
 	.port_disable		= felix_port_disable,
 | 
						|
 	.port_fdb_dump		= felix_fdb_dump,
 | 
						|
--- a/drivers/net/dsa/ocelot/felix.h
 | 
						|
+++ b/drivers/net/dsa/ocelot/felix.h
 | 
						|
@@ -10,6 +10,7 @@
 | 
						|
 struct felix_info {
 | 
						|
 	struct resource			*target_io_res;
 | 
						|
 	struct resource			*port_io_res;
 | 
						|
+	struct resource			*imdio_res;
 | 
						|
 	const struct reg_field		*regfields;
 | 
						|
 	const u32 *const		*map;
 | 
						|
 	const struct ocelot_ops		*ops;
 | 
						|
@@ -17,7 +18,18 @@ struct felix_info {
 | 
						|
 	const struct ocelot_stat_layout	*stats_layout;
 | 
						|
 	unsigned int			num_stats;
 | 
						|
 	int				num_ports;
 | 
						|
-	int				pci_bar;
 | 
						|
+	int				switch_pci_bar;
 | 
						|
+	int				imdio_pci_bar;
 | 
						|
+	int	(*mdio_bus_alloc)(struct ocelot *ocelot);
 | 
						|
+	void	(*mdio_bus_free)(struct ocelot *ocelot);
 | 
						|
+	void	(*pcs_init)(struct ocelot *ocelot, int port,
 | 
						|
+			    unsigned int link_an_mode,
 | 
						|
+			    const struct phylink_link_state *state);
 | 
						|
+	void	(*pcs_an_restart)(struct ocelot *ocelot, int port);
 | 
						|
+	void	(*pcs_link_state)(struct ocelot *ocelot, int port,
 | 
						|
+				  struct phylink_link_state *state);
 | 
						|
+	int	(*prevalidate_phy_mode)(struct ocelot *ocelot, int port,
 | 
						|
+					phy_interface_t phy_mode);
 | 
						|
 };
 | 
						|
 
 | 
						|
 extern struct felix_info		felix_info_vsc9959;
 | 
						|
@@ -32,6 +44,8 @@ struct felix {
 | 
						|
 	struct pci_dev			*pdev;
 | 
						|
 	struct felix_info		*info;
 | 
						|
 	struct ocelot			ocelot;
 | 
						|
+	struct mii_bus			*imdio;
 | 
						|
+	struct phy_device		**pcs;
 | 
						|
 };
 | 
						|
 
 | 
						|
 #endif
 | 
						|
--- a/drivers/net/dsa/ocelot/felix_vsc9959.c
 | 
						|
+++ b/drivers/net/dsa/ocelot/felix_vsc9959.c
 | 
						|
@@ -2,12 +2,33 @@
 | 
						|
 /* Copyright 2017 Microsemi Corporation
 | 
						|
  * Copyright 2018-2019 NXP Semiconductors
 | 
						|
  */
 | 
						|
+#include <linux/fsl/enetc_mdio.h>
 | 
						|
 #include <soc/mscc/ocelot_sys.h>
 | 
						|
 #include <soc/mscc/ocelot.h>
 | 
						|
 #include <linux/iopoll.h>
 | 
						|
 #include <linux/pci.h>
 | 
						|
 #include "felix.h"
 | 
						|
 
 | 
						|
+/* TODO: should find a better place for these */
 | 
						|
+#define USXGMII_BMCR_RESET		BIT(15)
 | 
						|
+#define USXGMII_BMCR_AN_EN		BIT(12)
 | 
						|
+#define USXGMII_BMCR_RST_AN		BIT(9)
 | 
						|
+#define USXGMII_BMSR_LNKS(status)	(((status) & GENMASK(2, 2)) >> 2)
 | 
						|
+#define USXGMII_BMSR_AN_CMPL(status)	(((status) & GENMASK(5, 5)) >> 5)
 | 
						|
+#define USXGMII_ADVERTISE_LNKS(x)	(((x) << 15) & BIT(15))
 | 
						|
+#define USXGMII_ADVERTISE_FDX		BIT(12)
 | 
						|
+#define USXGMII_ADVERTISE_SPEED(x)	(((x) << 9) & GENMASK(11, 9))
 | 
						|
+#define USXGMII_LPA_LNKS(lpa)		((lpa) >> 15)
 | 
						|
+#define USXGMII_LPA_DUPLEX(lpa)		(((lpa) & GENMASK(12, 12)) >> 12)
 | 
						|
+#define USXGMII_LPA_SPEED(lpa)		(((lpa) & GENMASK(11, 9)) >> 9)
 | 
						|
+
 | 
						|
+enum usxgmii_speed {
 | 
						|
+	USXGMII_SPEED_10	= 0,
 | 
						|
+	USXGMII_SPEED_100	= 1,
 | 
						|
+	USXGMII_SPEED_1000	= 2,
 | 
						|
+	USXGMII_SPEED_2500	= 4,
 | 
						|
+};
 | 
						|
+
 | 
						|
 static const u32 vsc9959_ana_regmap[] = {
 | 
						|
 	REG(ANA_ADVLEARN,			0x0089a0),
 | 
						|
 	REG(ANA_VLANMASK,			0x0089a4),
 | 
						|
@@ -390,6 +411,15 @@ static struct resource vsc9959_port_io_r
 | 
						|
 	},
 | 
						|
 };
 | 
						|
 
 | 
						|
+/* Port MAC 0 Internal MDIO bus through which the SerDes acting as an
 | 
						|
+ * SGMII/QSGMII MAC PCS can be found.
 | 
						|
+ */
 | 
						|
+static struct resource vsc9959_imdio_res = {
 | 
						|
+	.start		= 0x8030,
 | 
						|
+	.end		= 0x8040,
 | 
						|
+	.name		= "imdio",
 | 
						|
+};
 | 
						|
+
 | 
						|
 static const struct reg_field vsc9959_regfields[] = {
 | 
						|
 	[ANA_ADVLEARN_VLAN_CHK] = REG_FIELD(ANA_ADVLEARN, 6, 6),
 | 
						|
 	[ANA_ADVLEARN_LEARN_MIRROR] = REG_FIELD(ANA_ADVLEARN, 0, 5),
 | 
						|
@@ -569,13 +599,475 @@ static int vsc9959_reset(struct ocelot *
 | 
						|
 	return 0;
 | 
						|
 }
 | 
						|
 
 | 
						|
+static void vsc9959_pcs_an_restart_sgmii(struct phy_device *pcs)
 | 
						|
+{
 | 
						|
+	phy_set_bits(pcs, MII_BMCR, BMCR_ANRESTART);
 | 
						|
+}
 | 
						|
+
 | 
						|
+static void vsc9959_pcs_an_restart_usxgmii(struct phy_device *pcs)
 | 
						|
+{
 | 
						|
+	phy_write_mmd(pcs, MDIO_MMD_VEND2, MII_BMCR,
 | 
						|
+		      USXGMII_BMCR_RESET |
 | 
						|
+		      USXGMII_BMCR_AN_EN |
 | 
						|
+		      USXGMII_BMCR_RST_AN);
 | 
						|
+}
 | 
						|
+
 | 
						|
+static void vsc9959_pcs_an_restart(struct ocelot *ocelot, int port)
 | 
						|
+{
 | 
						|
+	struct felix *felix = ocelot_to_felix(ocelot);
 | 
						|
+	struct phy_device *pcs = felix->pcs[port];
 | 
						|
+
 | 
						|
+	if (!pcs)
 | 
						|
+		return;
 | 
						|
+
 | 
						|
+	switch (pcs->interface) {
 | 
						|
+	case PHY_INTERFACE_MODE_SGMII:
 | 
						|
+	case PHY_INTERFACE_MODE_QSGMII:
 | 
						|
+		vsc9959_pcs_an_restart_sgmii(pcs);
 | 
						|
+		break;
 | 
						|
+	case PHY_INTERFACE_MODE_USXGMII:
 | 
						|
+		vsc9959_pcs_an_restart_usxgmii(pcs);
 | 
						|
+		break;
 | 
						|
+	default:
 | 
						|
+		dev_err(ocelot->dev, "Invalid PCS interface type %s\n",
 | 
						|
+			phy_modes(pcs->interface));
 | 
						|
+		break;
 | 
						|
+	}
 | 
						|
+}
 | 
						|
+
 | 
						|
+/* We enable SGMII AN only when the PHY has managed = "in-band-status" in the
 | 
						|
+ * device tree. If we are in MLO_AN_PHY mode, we program directly state->speed
 | 
						|
+ * into the PCS, which is retrieved out-of-band over MDIO. This also has the
 | 
						|
+ * benefit of working with SGMII fixed-links, like downstream switches, where
 | 
						|
+ * both link partners attempt to operate as AN slaves and therefore AN never
 | 
						|
+ * completes.  But it also has the disadvantage that some PHY chips don't pass
 | 
						|
+ * traffic if SGMII AN is enabled but not completed (acknowledged by us), so
 | 
						|
+ * setting MLO_AN_INBAND is actually required for those.
 | 
						|
+ */
 | 
						|
+static void vsc9959_pcs_init_sgmii(struct phy_device *pcs,
 | 
						|
+				   unsigned int link_an_mode,
 | 
						|
+				   const struct phylink_link_state *state)
 | 
						|
+{
 | 
						|
+	if (link_an_mode == MLO_AN_INBAND) {
 | 
						|
+		/* SGMII spec requires tx_config_Reg[15:0] to be exactly 0x4001
 | 
						|
+		 * for the MAC PCS in order to acknowledge the AN.
 | 
						|
+		 */
 | 
						|
+		phy_write(pcs, MII_ADVERTISE, ADVERTISE_SGMII |
 | 
						|
+					      ADVERTISE_LPACK);
 | 
						|
+
 | 
						|
+		phy_write(pcs, ENETC_PCS_IF_MODE,
 | 
						|
+			  ENETC_PCS_IF_MODE_SGMII_EN |
 | 
						|
+			  ENETC_PCS_IF_MODE_USE_SGMII_AN);
 | 
						|
+
 | 
						|
+		/* Adjust link timer for SGMII */
 | 
						|
+		phy_write(pcs, ENETC_PCS_LINK_TIMER1,
 | 
						|
+			  ENETC_PCS_LINK_TIMER1_VAL);
 | 
						|
+		phy_write(pcs, ENETC_PCS_LINK_TIMER2,
 | 
						|
+			  ENETC_PCS_LINK_TIMER2_VAL);
 | 
						|
+
 | 
						|
+		phy_write(pcs, MII_BMCR, BMCR_ANRESTART | BMCR_ANENABLE);
 | 
						|
+	} else {
 | 
						|
+		int speed;
 | 
						|
+
 | 
						|
+		if (state->duplex == DUPLEX_HALF) {
 | 
						|
+			phydev_err(pcs, "Half duplex not supported\n");
 | 
						|
+			return;
 | 
						|
+		}
 | 
						|
+		switch (state->speed) {
 | 
						|
+		case SPEED_1000:
 | 
						|
+			speed = ENETC_PCS_SPEED_1000;
 | 
						|
+			break;
 | 
						|
+		case SPEED_100:
 | 
						|
+			speed = ENETC_PCS_SPEED_100;
 | 
						|
+			break;
 | 
						|
+		case SPEED_10:
 | 
						|
+			speed = ENETC_PCS_SPEED_10;
 | 
						|
+			break;
 | 
						|
+		case SPEED_UNKNOWN:
 | 
						|
+			/* Silently don't do anything */
 | 
						|
+			return;
 | 
						|
+		default:
 | 
						|
+			phydev_err(pcs, "Invalid PCS speed %d\n", state->speed);
 | 
						|
+			return;
 | 
						|
+		}
 | 
						|
+
 | 
						|
+		phy_write(pcs, ENETC_PCS_IF_MODE,
 | 
						|
+			  ENETC_PCS_IF_MODE_SGMII_EN |
 | 
						|
+			  ENETC_PCS_IF_MODE_SGMII_SPEED(speed));
 | 
						|
+
 | 
						|
+		/* Yes, not a mistake: speed is given by IF_MODE. */
 | 
						|
+		phy_write(pcs, MII_BMCR, BMCR_RESET |
 | 
						|
+					 BMCR_SPEED1000 |
 | 
						|
+					 BMCR_FULLDPLX);
 | 
						|
+	}
 | 
						|
+}
 | 
						|
+
 | 
						|
+/* 2500Base-X is SerDes protocol 7 on Felix and 6 on ENETC. It is a SerDes lane
 | 
						|
+ * clocked at 3.125 GHz which encodes symbols with 8b/10b and does not have
 | 
						|
+ * auto-negotiation of any link parameters. Electrically it is compatible with
 | 
						|
+ * a single lane of XAUI.
 | 
						|
+ * The hardware reference manual wants to call this mode SGMII, but it isn't
 | 
						|
+ * really, since the fundamental features of SGMII:
 | 
						|
+ * - Downgrading the link speed by duplicating symbols
 | 
						|
+ * - Auto-negotiation
 | 
						|
+ * are not there.
 | 
						|
+ * The speed is configured at 1000 in the IF_MODE and BMCR MDIO registers
 | 
						|
+ * because the clock frequency is actually given by a PLL configured in the
 | 
						|
+ * Reset Configuration Word (RCW).
 | 
						|
+ * Since there is no difference between fixed speed SGMII w/o AN and 802.3z w/o
 | 
						|
+ * AN, we call this PHY interface type 2500Base-X. In case a PHY negotiates a
 | 
						|
+ * lower link speed on line side, the system-side interface remains fixed at
 | 
						|
+ * 2500 Mbps and we do rate adaptation through pause frames.
 | 
						|
+ */
 | 
						|
+static void vsc9959_pcs_init_2500basex(struct phy_device *pcs,
 | 
						|
+				       unsigned int link_an_mode,
 | 
						|
+				       const struct phylink_link_state *state)
 | 
						|
+{
 | 
						|
+	if (link_an_mode == MLO_AN_INBAND) {
 | 
						|
+		phydev_err(pcs, "AN not supported on 3.125GHz SerDes lane\n");
 | 
						|
+		return;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	phy_write(pcs, ENETC_PCS_IF_MODE,
 | 
						|
+		  ENETC_PCS_IF_MODE_SGMII_EN |
 | 
						|
+		  ENETC_PCS_IF_MODE_SGMII_SPEED(ENETC_PCS_SPEED_2500));
 | 
						|
+
 | 
						|
+	phy_write(pcs, MII_BMCR, BMCR_SPEED1000 |
 | 
						|
+				 BMCR_FULLDPLX |
 | 
						|
+				 BMCR_RESET);
 | 
						|
+}
 | 
						|
+
 | 
						|
+static void vsc9959_pcs_init_usxgmii(struct phy_device *pcs,
 | 
						|
+				     unsigned int link_an_mode,
 | 
						|
+				     const struct phylink_link_state *state)
 | 
						|
+{
 | 
						|
+	if (link_an_mode != MLO_AN_INBAND) {
 | 
						|
+		phydev_err(pcs, "USXGMII only supports in-band AN for now\n");
 | 
						|
+		return;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	/* Configure device ability for the USXGMII Replicator */
 | 
						|
+	phy_write_mmd(pcs, MDIO_MMD_VEND2, MII_ADVERTISE,
 | 
						|
+		      USXGMII_ADVERTISE_SPEED(USXGMII_SPEED_2500) |
 | 
						|
+		      USXGMII_ADVERTISE_LNKS(1) |
 | 
						|
+		      ADVERTISE_SGMII |
 | 
						|
+		      ADVERTISE_LPACK |
 | 
						|
+		      USXGMII_ADVERTISE_FDX);
 | 
						|
+}
 | 
						|
+
 | 
						|
+static void vsc9959_pcs_init(struct ocelot *ocelot, int port,
 | 
						|
+			     unsigned int link_an_mode,
 | 
						|
+			     const struct phylink_link_state *state)
 | 
						|
+{
 | 
						|
+	struct felix *felix = ocelot_to_felix(ocelot);
 | 
						|
+	struct phy_device *pcs = felix->pcs[port];
 | 
						|
+
 | 
						|
+	if (!pcs)
 | 
						|
+		return;
 | 
						|
+
 | 
						|
+	/* The PCS does not implement the BMSR register fully, so capability
 | 
						|
+	 * detection via genphy_read_abilities does not work. Since we can get
 | 
						|
+	 * the PHY config word from the LPA register though, there is still
 | 
						|
+	 * value in using the generic phy_resolve_aneg_linkmode function. So
 | 
						|
+	 * populate the supported and advertising link modes manually here.
 | 
						|
+	 */
 | 
						|
+	linkmode_set_bit_array(phy_basic_ports_array,
 | 
						|
+			       ARRAY_SIZE(phy_basic_ports_array),
 | 
						|
+			       pcs->supported);
 | 
						|
+	linkmode_set_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT, pcs->supported);
 | 
						|
+	linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, pcs->supported);
 | 
						|
+	linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, pcs->supported);
 | 
						|
+	if (pcs->interface == PHY_INTERFACE_MODE_2500BASEX ||
 | 
						|
+	    pcs->interface == PHY_INTERFACE_MODE_USXGMII)
 | 
						|
+		linkmode_set_bit(ETHTOOL_LINK_MODE_2500baseX_Full_BIT,
 | 
						|
+				 pcs->supported);
 | 
						|
+	if (pcs->interface != PHY_INTERFACE_MODE_2500BASEX)
 | 
						|
+		linkmode_set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
 | 
						|
+				 pcs->supported);
 | 
						|
+	phy_advertise_supported(pcs);
 | 
						|
+
 | 
						|
+	switch (pcs->interface) {
 | 
						|
+	case PHY_INTERFACE_MODE_SGMII:
 | 
						|
+	case PHY_INTERFACE_MODE_QSGMII:
 | 
						|
+		vsc9959_pcs_init_sgmii(pcs, link_an_mode, state);
 | 
						|
+		break;
 | 
						|
+	case PHY_INTERFACE_MODE_2500BASEX:
 | 
						|
+		vsc9959_pcs_init_2500basex(pcs, link_an_mode, state);
 | 
						|
+		break;
 | 
						|
+	case PHY_INTERFACE_MODE_USXGMII:
 | 
						|
+		vsc9959_pcs_init_usxgmii(pcs, link_an_mode, state);
 | 
						|
+		break;
 | 
						|
+	default:
 | 
						|
+		dev_err(ocelot->dev, "Unsupported link mode %s\n",
 | 
						|
+			phy_modes(pcs->interface));
 | 
						|
+	}
 | 
						|
+}
 | 
						|
+
 | 
						|
+static void vsc9959_pcs_link_state_resolve(struct phy_device *pcs,
 | 
						|
+					   struct phylink_link_state *state)
 | 
						|
+{
 | 
						|
+	state->an_complete = pcs->autoneg_complete;
 | 
						|
+	state->an_enabled = pcs->autoneg;
 | 
						|
+	state->link = pcs->link;
 | 
						|
+	state->duplex = pcs->duplex;
 | 
						|
+	state->speed = pcs->speed;
 | 
						|
+	/* SGMII AN does not negotiate flow control, but that's ok,
 | 
						|
+	 * since phylink already knows that, and does:
 | 
						|
+	 *	link_state.pause |= pl->phy_state.pause;
 | 
						|
+	 */
 | 
						|
+	state->pause = MLO_PAUSE_NONE;
 | 
						|
+
 | 
						|
+	phydev_dbg(pcs,
 | 
						|
+		   "mode=%s/%s/%s adv=%*pb lpa=%*pb link=%u an_enabled=%u an_complete=%u\n",
 | 
						|
+		   phy_modes(pcs->interface),
 | 
						|
+		   phy_speed_to_str(pcs->speed),
 | 
						|
+		   phy_duplex_to_str(pcs->duplex),
 | 
						|
+		   __ETHTOOL_LINK_MODE_MASK_NBITS, pcs->advertising,
 | 
						|
+		   __ETHTOOL_LINK_MODE_MASK_NBITS, pcs->lp_advertising,
 | 
						|
+		   pcs->link, pcs->autoneg, pcs->autoneg_complete);
 | 
						|
+}
 | 
						|
+
 | 
						|
+static void vsc9959_pcs_link_state_sgmii(struct phy_device *pcs,
 | 
						|
+					 struct phylink_link_state *state)
 | 
						|
+{
 | 
						|
+	int err;
 | 
						|
+
 | 
						|
+	err = genphy_update_link(pcs);
 | 
						|
+	if (err < 0)
 | 
						|
+		return;
 | 
						|
+
 | 
						|
+	if (pcs->autoneg_complete) {
 | 
						|
+		u16 lpa = phy_read(pcs, MII_LPA);
 | 
						|
+
 | 
						|
+		mii_lpa_to_linkmode_lpa_sgmii(pcs->lp_advertising, lpa);
 | 
						|
+
 | 
						|
+		phy_resolve_aneg_linkmode(pcs);
 | 
						|
+	}
 | 
						|
+}
 | 
						|
+
 | 
						|
+static void vsc9959_pcs_link_state_2500basex(struct phy_device *pcs,
 | 
						|
+					     struct phylink_link_state *state)
 | 
						|
+{
 | 
						|
+	int err;
 | 
						|
+
 | 
						|
+	err = genphy_update_link(pcs);
 | 
						|
+	if (err < 0)
 | 
						|
+		return;
 | 
						|
+
 | 
						|
+	pcs->speed = SPEED_2500;
 | 
						|
+	pcs->asym_pause = true;
 | 
						|
+	pcs->pause = true;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static void vsc9959_pcs_link_state_usxgmii(struct phy_device *pcs,
 | 
						|
+					   struct phylink_link_state *state)
 | 
						|
+{
 | 
						|
+	int status, lpa;
 | 
						|
+
 | 
						|
+	status = phy_read_mmd(pcs, MDIO_MMD_VEND2, MII_BMSR);
 | 
						|
+	if (status < 0)
 | 
						|
+		return;
 | 
						|
+
 | 
						|
+	pcs->autoneg = true;
 | 
						|
+	pcs->autoneg_complete = USXGMII_BMSR_AN_CMPL(status);
 | 
						|
+	pcs->link = USXGMII_BMSR_LNKS(status);
 | 
						|
+
 | 
						|
+	if (!pcs->link || !pcs->autoneg_complete)
 | 
						|
+		return;
 | 
						|
+
 | 
						|
+	lpa = phy_read_mmd(pcs, MDIO_MMD_VEND2, MII_LPA);
 | 
						|
+	if (lpa < 0)
 | 
						|
+		return;
 | 
						|
+
 | 
						|
+	switch (USXGMII_LPA_SPEED(lpa)) {
 | 
						|
+	case USXGMII_SPEED_10:
 | 
						|
+		pcs->speed = SPEED_10;
 | 
						|
+		break;
 | 
						|
+	case USXGMII_SPEED_100:
 | 
						|
+		pcs->speed = SPEED_100;
 | 
						|
+		break;
 | 
						|
+	case USXGMII_SPEED_1000:
 | 
						|
+		pcs->speed = SPEED_1000;
 | 
						|
+		break;
 | 
						|
+	case USXGMII_SPEED_2500:
 | 
						|
+		pcs->speed = SPEED_2500;
 | 
						|
+		break;
 | 
						|
+	default:
 | 
						|
+		break;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	pcs->link = USXGMII_LPA_LNKS(lpa);
 | 
						|
+	if (USXGMII_LPA_DUPLEX(lpa))
 | 
						|
+		pcs->duplex = DUPLEX_FULL;
 | 
						|
+	else
 | 
						|
+		pcs->duplex = DUPLEX_HALF;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static void vsc9959_pcs_link_state(struct ocelot *ocelot, int port,
 | 
						|
+				   struct phylink_link_state *state)
 | 
						|
+{
 | 
						|
+	struct felix *felix = ocelot_to_felix(ocelot);
 | 
						|
+	struct phy_device *pcs = felix->pcs[port];
 | 
						|
+
 | 
						|
+	if (!pcs)
 | 
						|
+		return;
 | 
						|
+
 | 
						|
+	pcs->speed = SPEED_UNKNOWN;
 | 
						|
+	pcs->duplex = DUPLEX_UNKNOWN;
 | 
						|
+	pcs->pause = 0;
 | 
						|
+	pcs->asym_pause = 0;
 | 
						|
+
 | 
						|
+	switch (pcs->interface) {
 | 
						|
+	case PHY_INTERFACE_MODE_SGMII:
 | 
						|
+	case PHY_INTERFACE_MODE_QSGMII:
 | 
						|
+		vsc9959_pcs_link_state_sgmii(pcs, state);
 | 
						|
+		break;
 | 
						|
+	case PHY_INTERFACE_MODE_2500BASEX:
 | 
						|
+		vsc9959_pcs_link_state_2500basex(pcs, state);
 | 
						|
+		break;
 | 
						|
+	case PHY_INTERFACE_MODE_USXGMII:
 | 
						|
+		vsc9959_pcs_link_state_usxgmii(pcs, state);
 | 
						|
+		break;
 | 
						|
+	default:
 | 
						|
+		return;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	vsc9959_pcs_link_state_resolve(pcs, state);
 | 
						|
+}
 | 
						|
+
 | 
						|
+static int vsc9959_prevalidate_phy_mode(struct ocelot *ocelot, int port,
 | 
						|
+					phy_interface_t phy_mode)
 | 
						|
+{
 | 
						|
+	switch (phy_mode) {
 | 
						|
+	case PHY_INTERFACE_MODE_GMII:
 | 
						|
+		/* Only supported on internal to-CPU ports */
 | 
						|
+		if (port != 4 && port != 5)
 | 
						|
+			return -ENOTSUPP;
 | 
						|
+		return 0;
 | 
						|
+	case PHY_INTERFACE_MODE_SGMII:
 | 
						|
+	case PHY_INTERFACE_MODE_QSGMII:
 | 
						|
+	case PHY_INTERFACE_MODE_USXGMII:
 | 
						|
+	case PHY_INTERFACE_MODE_2500BASEX:
 | 
						|
+		/* Not supported on internal to-CPU ports */
 | 
						|
+		if (port == 4 || port == 5)
 | 
						|
+			return -ENOTSUPP;
 | 
						|
+		return 0;
 | 
						|
+	default:
 | 
						|
+		return -ENOTSUPP;
 | 
						|
+	}
 | 
						|
+}
 | 
						|
+
 | 
						|
 static const struct ocelot_ops vsc9959_ops = {
 | 
						|
 	.reset			= vsc9959_reset,
 | 
						|
 };
 | 
						|
 
 | 
						|
+static int vsc9959_mdio_bus_alloc(struct ocelot *ocelot)
 | 
						|
+{
 | 
						|
+	struct felix *felix = ocelot_to_felix(ocelot);
 | 
						|
+	struct enetc_mdio_priv *mdio_priv;
 | 
						|
+	struct device *dev = ocelot->dev;
 | 
						|
+	resource_size_t imdio_base;
 | 
						|
+	void __iomem *imdio_regs;
 | 
						|
+	struct resource *res;
 | 
						|
+	struct enetc_hw *hw;
 | 
						|
+	struct mii_bus *bus;
 | 
						|
+	int port;
 | 
						|
+	int rc;
 | 
						|
+
 | 
						|
+	felix->pcs = devm_kcalloc(dev, felix->info->num_ports,
 | 
						|
+				  sizeof(struct phy_device *),
 | 
						|
+				  GFP_KERNEL);
 | 
						|
+	if (!felix->pcs) {
 | 
						|
+		dev_err(dev, "failed to allocate array for PCS PHYs\n");
 | 
						|
+		return -ENOMEM;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	imdio_base = pci_resource_start(felix->pdev,
 | 
						|
+					felix->info->imdio_pci_bar);
 | 
						|
+
 | 
						|
+	res = felix->info->imdio_res;
 | 
						|
+	res->flags = IORESOURCE_MEM;
 | 
						|
+	res->start += imdio_base;
 | 
						|
+	res->end += imdio_base;
 | 
						|
+
 | 
						|
+	imdio_regs = devm_ioremap_resource(dev, res);
 | 
						|
+	if (IS_ERR(imdio_regs)) {
 | 
						|
+		dev_err(dev, "failed to map internal MDIO registers\n");
 | 
						|
+		return PTR_ERR(imdio_regs);
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	hw = enetc_hw_alloc(dev, imdio_regs);
 | 
						|
+	if (IS_ERR(hw)) {
 | 
						|
+		dev_err(dev, "failed to allocate ENETC HW structure\n");
 | 
						|
+		return PTR_ERR(hw);
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	bus = devm_mdiobus_alloc_size(dev, sizeof(*mdio_priv));
 | 
						|
+	if (!bus)
 | 
						|
+		return -ENOMEM;
 | 
						|
+
 | 
						|
+	bus->name = "VSC9959 internal MDIO bus";
 | 
						|
+	bus->read = enetc_mdio_read;
 | 
						|
+	bus->write = enetc_mdio_write;
 | 
						|
+	bus->parent = dev;
 | 
						|
+	mdio_priv = bus->priv;
 | 
						|
+	mdio_priv->hw = hw;
 | 
						|
+	/* This gets added to imdio_regs, which already maps addresses
 | 
						|
+	 * starting with the proper offset.
 | 
						|
+	 */
 | 
						|
+	mdio_priv->mdio_base = 0;
 | 
						|
+	snprintf(bus->id, MII_BUS_ID_SIZE, "%s-imdio", dev_name(dev));
 | 
						|
+
 | 
						|
+	/* Needed in order to initialize the bus mutex lock */
 | 
						|
+	rc = mdiobus_register(bus);
 | 
						|
+	if (rc < 0) {
 | 
						|
+		dev_err(dev, "failed to register MDIO bus\n");
 | 
						|
+		return rc;
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	felix->imdio = bus;
 | 
						|
+
 | 
						|
+	for (port = 0; port < felix->info->num_ports; port++) {
 | 
						|
+		struct ocelot_port *ocelot_port = ocelot->ports[port];
 | 
						|
+		struct phy_device *pcs;
 | 
						|
+		bool is_c45 = false;
 | 
						|
+
 | 
						|
+		if (ocelot_port->phy_mode == PHY_INTERFACE_MODE_USXGMII)
 | 
						|
+			is_c45 = true;
 | 
						|
+
 | 
						|
+		pcs = get_phy_device(felix->imdio, port, is_c45);
 | 
						|
+		if (IS_ERR(pcs))
 | 
						|
+			continue;
 | 
						|
+
 | 
						|
+		pcs->interface = ocelot_port->phy_mode;
 | 
						|
+		felix->pcs[port] = pcs;
 | 
						|
+
 | 
						|
+		dev_info(dev, "Found PCS at internal MDIO address %d\n", port);
 | 
						|
+	}
 | 
						|
+
 | 
						|
+	return 0;
 | 
						|
+}
 | 
						|
+
 | 
						|
+static void vsc9959_mdio_bus_free(struct ocelot *ocelot)
 | 
						|
+{
 | 
						|
+	struct felix *felix = ocelot_to_felix(ocelot);
 | 
						|
+	int port;
 | 
						|
+
 | 
						|
+	for (port = 0; port < ocelot->num_phys_ports; port++) {
 | 
						|
+		struct phy_device *pcs = felix->pcs[port];
 | 
						|
+
 | 
						|
+		if (!pcs)
 | 
						|
+			continue;
 | 
						|
+
 | 
						|
+		put_device(&pcs->mdio.dev);
 | 
						|
+	}
 | 
						|
+	mdiobus_unregister(felix->imdio);
 | 
						|
+}
 | 
						|
+
 | 
						|
 struct felix_info felix_info_vsc9959 = {
 | 
						|
 	.target_io_res		= vsc9959_target_io_res,
 | 
						|
 	.port_io_res		= vsc9959_port_io_res,
 | 
						|
+	.imdio_res		= &vsc9959_imdio_res,
 | 
						|
 	.regfields		= vsc9959_regfields,
 | 
						|
 	.map			= vsc9959_regmap,
 | 
						|
 	.ops			= &vsc9959_ops,
 | 
						|
@@ -583,5 +1075,12 @@ struct felix_info felix_info_vsc9959 = {
 | 
						|
 	.num_stats		= ARRAY_SIZE(vsc9959_stats_layout),
 | 
						|
 	.shared_queue_sz	= 128 * 1024,
 | 
						|
 	.num_ports		= 6,
 | 
						|
-	.pci_bar		= 4,
 | 
						|
+	.switch_pci_bar		= 4,
 | 
						|
+	.imdio_pci_bar		= 0,
 | 
						|
+	.mdio_bus_alloc		= vsc9959_mdio_bus_alloc,
 | 
						|
+	.mdio_bus_free		= vsc9959_mdio_bus_free,
 | 
						|
+	.pcs_init		= vsc9959_pcs_init,
 | 
						|
+	.pcs_an_restart		= vsc9959_pcs_an_restart,
 | 
						|
+	.pcs_link_state		= vsc9959_pcs_link_state,
 | 
						|
+	.prevalidate_phy_mode	= vsc9959_prevalidate_phy_mode,
 | 
						|
 };
 |