mirror of
https://github.com/openwrt/openwrt.git
synced 2024-12-23 07:22:33 +00:00
443 lines
13 KiB
Diff
443 lines
13 KiB
Diff
|
From 0e219a6aa07754d81a25c9e4408d81d194cd2000 Mon Sep 17 00:00:00 2001
|
||
|
From: Vladimir Oltean <vladimir.oltean@nxp.com>
|
||
|
Date: Fri, 22 Nov 2019 13:45:52 +0200
|
||
|
Subject: [PATCH] net: dsa: felix: Add PCS operations for PHYLINK
|
||
|
|
||
|
This removes the bootloader dependency for SGMII PCS pre-configuration,
|
||
|
as well as adds support for monitoring the in-band SGMII AN between the
|
||
|
PCS and the system-side link partner (PHY or other MAC).
|
||
|
|
||
|
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
|
||
|
---
|
||
|
drivers/net/dsa/ocelot/felix.c | 23 ++-
|
||
|
drivers/net/dsa/ocelot/felix.h | 7 +-
|
||
|
drivers/net/dsa/ocelot/felix_vsc9959.c | 292 ++++++++++++++++++++++++++++++++-
|
||
|
3 files changed, 314 insertions(+), 8 deletions(-)
|
||
|
|
||
|
--- a/drivers/net/dsa/ocelot/felix.c
|
||
|
+++ b/drivers/net/dsa/ocelot/felix.c
|
||
|
@@ -265,7 +265,7 @@ static int felix_get_ts_info(struct dsa_
|
||
|
static int felix_init_structs(struct felix *felix, int num_phys_ports)
|
||
|
{
|
||
|
struct ocelot *ocelot = &felix->ocelot;
|
||
|
- resource_size_t base;
|
||
|
+ resource_size_t switch_base;
|
||
|
int port, i, err;
|
||
|
|
||
|
ocelot->num_phys_ports = num_phys_ports;
|
||
|
@@ -281,7 +281,8 @@ static int felix_init_structs(struct fel
|
||
|
ocelot->ops = felix->info->ops;
|
||
|
ocelot->quirks = felix->info->quirks;
|
||
|
|
||
|
- base = pci_resource_start(felix->pdev, felix->info->pci_bar);
|
||
|
+ switch_base = pci_resource_start(felix->pdev,
|
||
|
+ felix->info->switch_pci_bar);
|
||
|
|
||
|
for (i = 0; i < TARGET_MAX; i++) {
|
||
|
struct regmap *target;
|
||
|
@@ -292,8 +293,8 @@ 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)) {
|
||
|
@@ -327,8 +328,8 @@ static int felix_init_structs(struct fel
|
||
|
|
||
|
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)) {
|
||
|
@@ -342,6 +343,12 @@ static int felix_init_structs(struct fel
|
||
|
ocelot->ports[port] = ocelot_port;
|
||
|
}
|
||
|
|
||
|
+ if (felix->info->mdio_bus_alloc) {
|
||
|
+ err = felix->info->mdio_bus_alloc(ocelot);
|
||
|
+ if (err < 0)
|
||
|
+ return err;
|
||
|
+ }
|
||
|
+
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
@@ -377,6 +384,10 @@ static int felix_setup(struct dsa_switch
|
||
|
static void felix_teardown(struct dsa_switch *ds)
|
||
|
{
|
||
|
struct ocelot *ocelot = ds->priv;
|
||
|
+ struct felix *felix = ocelot_to_felix(ocelot);
|
||
|
+
|
||
|
+ if (felix->imdio)
|
||
|
+ mdiobus_unregister(felix->imdio);
|
||
|
|
||
|
/* stop workqueue thread */
|
||
|
ocelot_deinit(ocelot);
|
||
|
--- 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,8 +18,10 @@ 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;
|
||
|
unsigned long quirks;
|
||
|
+ int (*mdio_bus_alloc)(struct ocelot *ocelot);
|
||
|
};
|
||
|
|
||
|
extern struct felix_info felix_info_vsc9959;
|
||
|
@@ -33,6 +36,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,6 +2,7 @@
|
||
|
/* 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>
|
||
|
@@ -390,6 +391,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 +579,291 @@ static int vsc9959_reset(struct ocelot *
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
+void vsc9959_pcs_validate(struct ocelot *ocelot, int port,
|
||
|
+ unsigned long *supported,
|
||
|
+ struct phylink_link_state *state)
|
||
|
+{
|
||
|
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = { 0, };
|
||
|
+
|
||
|
+ if (state->interface != PHY_INTERFACE_MODE_NA &&
|
||
|
+ state->interface != PHY_INTERFACE_MODE_GMII &&
|
||
|
+ state->interface != PHY_INTERFACE_MODE_SGMII &&
|
||
|
+ state->interface != PHY_INTERFACE_MODE_QSGMII &&
|
||
|
+ state->interface != PHY_INTERFACE_MODE_USXGMII) {
|
||
|
+ 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);
|
||
|
+ phylink_set(mask, 10baseT_Full);
|
||
|
+ phylink_set(mask, 100baseT_Full);
|
||
|
+ phylink_set(mask, 1000baseT_Full);
|
||
|
+ phylink_set(mask, 1000baseX_Full);
|
||
|
+ phylink_set(mask, 2500baseT_Full);
|
||
|
+ phylink_set(mask, 2500baseX_Full);
|
||
|
+ phylink_set(mask, 1000baseKX_Full);
|
||
|
+
|
||
|
+ bitmap_and(supported, supported, mask,
|
||
|
+ __ETHTOOL_LINK_MODE_MASK_NBITS);
|
||
|
+ bitmap_and(state->advertising, state->advertising, mask,
|
||
|
+ __ETHTOOL_LINK_MODE_MASK_NBITS);
|
||
|
+}
|
||
|
+
|
||
|
+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;
|
||
|
+
|
||
|
+ phy_set_bits(pcs, MII_BMCR, BMCR_ANRESTART);
|
||
|
+}
|
||
|
+
|
||
|
+static void vsc9959_pcs_init_sgmii(struct phy_device *pcs,
|
||
|
+ unsigned int link_an_mode,
|
||
|
+ const struct phylink_link_state *state)
|
||
|
+{
|
||
|
+ /* 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);
|
||
|
+
|
||
|
+ /* Set to SGMII mode, use AN */
|
||
|
+ phy_write(pcs, ENETC_PCS_IF_MODE, ENETC_PCS_IF_MODE_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_SPEED1000 |
|
||
|
+ BMCR_FULLDPLX |
|
||
|
+ BMCR_ANENABLE);
|
||
|
+}
|
||
|
+
|
||
|
+#define ADVERTISE_USXGMII_FDX BIT(12)
|
||
|
+
|
||
|
+static void vsc9959_pcs_init_sxgmii(struct phy_device *pcs,
|
||
|
+ unsigned int link_an_mode,
|
||
|
+ const struct phylink_link_state *state)
|
||
|
+{
|
||
|
+ /* Configure device ability for the USXGMII Replicator */
|
||
|
+ phy_write_mmd(pcs, MDIO_MMD_VEND2, MII_ADVERTISE,
|
||
|
+ ADVERTISE_SGMII |
|
||
|
+ ADVERTISE_LPACK |
|
||
|
+ ADVERTISE_USXGMII_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;
|
||
|
+
|
||
|
+ if (link_an_mode == MLO_AN_FIXED) {
|
||
|
+ phydev_err(pcs, "Fixed modes are not yet supported.\n");
|
||
|
+ return;
|
||
|
+ }
|
||
|
+
|
||
|
+ pcs->interface = state->interface;
|
||
|
+ if (pcs->interface == PHY_INTERFACE_MODE_USXGMII)
|
||
|
+ pcs->is_c45 = true;
|
||
|
+ else
|
||
|
+ pcs->is_c45 = false;
|
||
|
+
|
||
|
+ /* 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_Autoneg_BIT, 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);
|
||
|
+ 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_USXGMII:
|
||
|
+ vsc9959_pcs_init_sxgmii(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(struct ocelot *ocelot, int port,
|
||
|
+ struct phylink_link_state *state)
|
||
|
+{
|
||
|
+ struct felix *felix = ocelot_to_felix(ocelot);
|
||
|
+ struct phy_device *pcs = felix->pcs[port];
|
||
|
+ int err;
|
||
|
+
|
||
|
+ if (!pcs)
|
||
|
+ return;
|
||
|
+
|
||
|
+ /* Reading PCS status not yet supported for USXGMII */
|
||
|
+ if (pcs->is_c45) {
|
||
|
+ state->link = 1;
|
||
|
+ return;
|
||
|
+ }
|
||
|
+
|
||
|
+ pcs->speed = SPEED_UNKNOWN;
|
||
|
+ pcs->duplex = DUPLEX_UNKNOWN;
|
||
|
+ pcs->pause = 0;
|
||
|
+ pcs->asym_pause = 0;
|
||
|
+
|
||
|
+ err = genphy_update_link(pcs);
|
||
|
+ if (err < 0)
|
||
|
+ return;
|
||
|
+
|
||
|
+ if (pcs->autoneg_complete) {
|
||
|
+ u16 lpa = phy_read(pcs, MII_LPA);
|
||
|
+
|
||
|
+ switch (state->interface) {
|
||
|
+ case PHY_INTERFACE_MODE_SGMII:
|
||
|
+ case PHY_INTERFACE_MODE_QSGMII:
|
||
|
+ mii_lpa_to_linkmode_lpa_sgmii(pcs->lp_advertising, lpa);
|
||
|
+ break;
|
||
|
+ default:
|
||
|
+ return;
|
||
|
+ }
|
||
|
+
|
||
|
+ phy_resolve_aneg_linkmode(pcs);
|
||
|
+ }
|
||
|
+
|
||
|
+ 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 = pcs->pause;
|
||
|
+
|
||
|
+ dev_dbg(ocelot->dev,
|
||
|
+ "%s: mode=%s/%s/%s adv=%*pb lpa=%*pb link=%u an_enabled=%u an_complete=%u\n",
|
||
|
+ __func__,
|
||
|
+ phy_modes(state->interface),
|
||
|
+ phy_speed_to_str(state->speed),
|
||
|
+ phy_duplex_to_str(state->duplex),
|
||
|
+ __ETHTOOL_LINK_MODE_MASK_NBITS, state->advertising,
|
||
|
+ __ETHTOOL_LINK_MODE_MASK_NBITS, state->lp_advertising,
|
||
|
+ state->link, state->an_enabled, state->an_complete);
|
||
|
+}
|
||
|
+
|
||
|
static const struct ocelot_ops vsc9959_ops = {
|
||
|
.reset = vsc9959_reset,
|
||
|
+ .pcs_init = vsc9959_pcs_init,
|
||
|
+ .pcs_an_restart = vsc9959_pcs_an_restart,
|
||
|
+ .pcs_link_state = vsc9959_pcs_link_state,
|
||
|
+ .pcs_validate = vsc9959_pcs_validate,
|
||
|
};
|
||
|
|
||
|
+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 phy_device *pcs;
|
||
|
+ bool is_c45 = false;
|
||
|
+
|
||
|
+ pcs = get_phy_device(felix->imdio, port, is_c45);
|
||
|
+ if (IS_ERR(pcs))
|
||
|
+ continue;
|
||
|
+
|
||
|
+ felix->pcs[port] = pcs;
|
||
|
+
|
||
|
+ dev_info(dev, "Found PCS at internal MDIO address %d\n", port);
|
||
|
+ }
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
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,6 +871,8 @@ 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,
|
||
|
.quirks = OCELOT_PCS_PERFORMS_RATE_ADAPTATION,
|
||
|
+ .mdio_bus_alloc = vsc9959_mdio_bus_alloc,
|
||
|
};
|