aboutsummaryrefslogtreecommitdiffstats
path: root/target/linux/layerscape/patches-5.4/701-net-0386-net-dsa-felix-Add-PCS-operations-for-PHYLINK.patch
diff options
context:
space:
mode:
Diffstat (limited to 'target/linux/layerscape/patches-5.4/701-net-0386-net-dsa-felix-Add-PCS-operations-for-PHYLINK.patch')
-rw-r--r--target/linux/layerscape/patches-5.4/701-net-0386-net-dsa-felix-Add-PCS-operations-for-PHYLINK.patch1087
1 files changed, 0 insertions, 1087 deletions
diff --git a/target/linux/layerscape/patches-5.4/701-net-0386-net-dsa-felix-Add-PCS-operations-for-PHYLINK.patch b/target/linux/layerscape/patches-5.4/701-net-0386-net-dsa-felix-Add-PCS-operations-for-PHYLINK.patch
deleted file mode 100644
index 4286c3b49a..0000000000
--- a/target/linux/layerscape/patches-5.4/701-net-0386-net-dsa-felix-Add-PCS-operations-for-PHYLINK.patch
+++ /dev/null
@@ -1,1087 +0,0 @@
-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,
- };