Commit df2c2ba8 authored by David S. Miller's avatar David S. Miller

Merge branch 'Convert-Felix-DSA-switch-to-PHYLINK'

Vladimir Oltean says:

====================
Convert Felix DSA switch to PHYLINK

Unlike most other conversions, this one is not by far a trivial one, and should
be seen as "Layerscape PCS meets PHYLINK". Actually, the PCS doesn't
need a lot of hand-holding and most of our other devices 'just work'
(this one included) without any sort of operating system awareness, just
an initialization procedure done typically in the bootloader.
Our issues start when the PCS stops from "just working", and that is
where PHYLINK comes in handy.

The PCS is not specific to the Vitesse / Microsemi / Microchip switching core
at all. Variations of this SerDes/PCS design can also be found on DPAA1 and
DPAA2 hardware.

The main idea of the abstraction provided is that the PCS looks so much like a
PHY device, that we model it as an actual PHY device and run the generic PHY
functions on it, where appropriate.

The 4xSGMII, QSGMII and QSXGMII modes are fairly straightforward.

The SerDes protocol which the driver calls 2500Base-X mode (a misnomer) is more
interesting. There is a description of how it works and what can be done with
it in patch 9/9 (in a comment above vsc9959_pcs_init_2500basex).
In short, it is a fixed speed protocol with no auto-negotiation whatsoever.
From my research of the SGMII-2500 patent [1], it has nothing to do with
SGMII-2500. That one:
* does not define any change to the AN base page compared to plain 10/100/1000
  SGMII. This implies that the 2500 speed is not negotiable, but the other
  speeds are. In our case, when the SerDes is configured for this protocol it's
  configured for good, there's no going back to SGMII.
* runs at a higher base frequency than regular SGMII. So SGMII-2500 operating
  at 1000 Mbps wouldn't interoperate with plain SGMII at 1000 Mbps. Strange,
  but ok..
* Emulates lower link speeds than 2500 by duplicating the codewords twice, then
  thrice, then twice again etc (2.5/25/250 times on average). The Layerscape
  PCS doesn't do that (it is fixed at 2500 Mbaud).

But on the other hand it isn't completely compatible with Base-X either,
since it doesn't do 802.3z / clause 37 auto negotiation (flow control,
local/remote fault etc). It is compatible with 2500Base-X without
in-band AN, and that is exactly how we decided to expose it (this is
actually similar to what others do).

For SGMII and USXGMII, the driver is using the PHYLINK 'managed =
"in-band-status"' DTS binding to figure out whether in-band AN is
expected to be enabled in the PCS or not. It is expected that the
attached PHY follows suite, but there is a gap here: the PHY driver does
not react to this setting, so only one of "AN on" and "AN off" works on
any particular PHY, even though that PHY might support bypassing the
SGMII AN process, as is the case on the VSC8514 PHY present on the
LS1028A-RDB board. A separate series will be sent to propose a way to
deal with that.

I dropped the Ocelot PHYLINK conversion because:
* I don't have VSC7514 hardware anyway
* The hardware is so different in this regard that there's almost nothing to
  share anyway.

Changes in v5:

- Added the register write to DEV_CLOCK_CFG back in
  felix_phylink_mac_config in patch 9/9.

Changes in v4:

- This is mostly a resend of v3, with the only notable change that I've
  dropped the PHY core patches for in_band_autoneg and I'll propose them
  independently.

v1 series:
https://www.spinics.net/lists/netdev/msg613869.html

RFC v2 series:
https://www.spinics.net/lists/netdev/msg620128.html

v3 series:
https://www.spinics.net/lists/netdev/msg622060.html

v4 series:
https://www.spinics.net/lists/netdev/msg622606.html

[0]: https://www.spinics.net/lists/netdev/msg613869.html
[1]: https://patents.google.com/patent/US7356047B1/en
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents de1b23b9 bdeced75
...@@ -251,7 +251,8 @@ this documentation. ...@@ -251,7 +251,8 @@ this documentation.
phylink_mac_change(priv->phylink, link_is_up); phylink_mac_change(priv->phylink, link_is_up);
where ``link_is_up`` is true if the link is currently up or false where ``link_is_up`` is true if the link is currently up or false
otherwise. otherwise. If a MAC is unable to provide these interrupts, then
it should set ``priv->phylink_config.pcs_poll = true;`` in step 9.
11. Verify that the driver does not call:: 11. Verify that the driver does not call::
......
...@@ -3,8 +3,10 @@ config NET_DSA_MSCC_FELIX ...@@ -3,8 +3,10 @@ config NET_DSA_MSCC_FELIX
tristate "Ocelot / Felix Ethernet switch support" tristate "Ocelot / Felix Ethernet switch support"
depends on NET_DSA && PCI depends on NET_DSA && PCI
depends on NET_VENDOR_MICROSEMI depends on NET_VENDOR_MICROSEMI
depends on NET_VENDOR_FREESCALE
select MSCC_OCELOT_SWITCH select MSCC_OCELOT_SWITCH
select NET_DSA_TAG_OCELOT select NET_DSA_TAG_OCELOT
select FSL_ENETC_MDIO
help help
This driver supports the VSC9959 network switch, which is a member of This driver supports the VSC9959 network switch, which is a member of
the Vitesse / Microsemi / Microchip Ocelot family of switching cores. the Vitesse / Microsemi / Microchip Ocelot family of switching cores.
......
...@@ -2,9 +2,14 @@ ...@@ -2,9 +2,14 @@
/* Copyright 2019 NXP Semiconductors /* Copyright 2019 NXP Semiconductors
*/ */
#include <uapi/linux/if_bridge.h> #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 <soc/mscc/ocelot.h>
#include <linux/packing.h> #include <linux/packing.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/of_net.h>
#include <linux/pci.h> #include <linux/pci.h>
#include <linux/of.h> #include <linux/of.h>
#include <net/dsa.h> #include <net/dsa.h>
...@@ -26,14 +31,6 @@ static int felix_set_ageing_time(struct dsa_switch *ds, ...@@ -26,14 +31,6 @@ static int felix_set_ageing_time(struct dsa_switch *ds,
return 0; 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, static int felix_fdb_dump(struct dsa_switch *ds, int port,
dsa_fdb_dump_cb_t *cb, void *data) dsa_fdb_dump_cb_t *cb, void *data)
{ {
...@@ -155,6 +152,138 @@ static void felix_port_disable(struct dsa_switch *ds, int port) ...@@ -155,6 +152,138 @@ static void felix_port_disable(struct dsa_switch *ds, int port)
return ocelot_port_disable(ocelot, port); 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, static void felix_get_strings(struct dsa_switch *ds, int port,
u32 stringset, u8 *data) u32 stringset, u8 *data)
{ {
...@@ -185,10 +314,76 @@ static int felix_get_ts_info(struct dsa_switch *ds, int port, ...@@ -185,10 +314,76 @@ static int felix_get_ts_info(struct dsa_switch *ds, int port,
return ocelot_get_ts_info(ocelot, port, info); 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, &phy_mode);
if (err) {
dev_err(dev, "Failed to read phy-mode or "
"phy-interface-type property for port %d\n",
port);
of_node_put(child);
return -ENODEV;
}
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) static int felix_init_structs(struct felix *felix, int num_phys_ports)
{ {
struct ocelot *ocelot = &felix->ocelot; struct ocelot *ocelot = &felix->ocelot;
resource_size_t base; phy_interface_t *port_phy_modes;
resource_size_t switch_base;
int port, i, err; int port, i, err;
ocelot->num_phys_ports = num_phys_ports; ocelot->num_phys_ports = num_phys_ports;
...@@ -203,7 +398,19 @@ static int felix_init_structs(struct felix *felix, int num_phys_ports) ...@@ -203,7 +398,19 @@ static int felix_init_structs(struct felix *felix, int num_phys_ports)
ocelot->shared_queue_sz = felix->info->shared_queue_sz; ocelot->shared_queue_sz = felix->info->shared_queue_sz;
ocelot->ops = felix->info->ops; 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++) { for (i = 0; i < TARGET_MAX; i++) {
struct regmap *target; struct regmap *target;
...@@ -214,13 +421,14 @@ static int felix_init_structs(struct felix *felix, int num_phys_ports) ...@@ -214,13 +421,14 @@ static int felix_init_structs(struct felix *felix, int num_phys_ports)
res = &felix->info->target_io_res[i]; res = &felix->info->target_io_res[i];
res->flags = IORESOURCE_MEM; res->flags = IORESOURCE_MEM;
res->start += base; res->start += switch_base;
res->end += base; res->end += switch_base;
target = ocelot_regmap_init(ocelot, res); target = ocelot_regmap_init(ocelot, res);
if (IS_ERR(target)) { if (IS_ERR(target)) {
dev_err(ocelot->dev, dev_err(ocelot->dev,
"Failed to map device memory space\n"); "Failed to map device memory space\n");
kfree(port_phy_modes);
return PTR_ERR(target); return PTR_ERR(target);
} }
...@@ -230,6 +438,7 @@ static int felix_init_structs(struct felix *felix, int num_phys_ports) ...@@ -230,6 +438,7 @@ static int felix_init_structs(struct felix *felix, int num_phys_ports)
err = ocelot_regfields_init(ocelot, felix->info->regfields); err = ocelot_regfields_init(ocelot, felix->info->regfields);
if (err) { if (err) {
dev_err(ocelot->dev, "failed to init reg fields map\n"); dev_err(ocelot->dev, "failed to init reg fields map\n");
kfree(port_phy_modes);
return err; return err;
} }
...@@ -244,26 +453,37 @@ static int felix_init_structs(struct felix *felix, int num_phys_ports) ...@@ -244,26 +453,37 @@ static int felix_init_structs(struct felix *felix, int num_phys_ports)
if (!ocelot_port) { if (!ocelot_port) {
dev_err(ocelot->dev, dev_err(ocelot->dev,
"failed to allocate port memory\n"); "failed to allocate port memory\n");
kfree(port_phy_modes);
return -ENOMEM; return -ENOMEM;
} }
res = &felix->info->port_io_res[port]; res = &felix->info->port_io_res[port];
res->flags = IORESOURCE_MEM; res->flags = IORESOURCE_MEM;
res->start += base; res->start += switch_base;
res->end += base; res->end += switch_base;
port_regs = devm_ioremap_resource(ocelot->dev, res); port_regs = devm_ioremap_resource(ocelot->dev, res);
if (IS_ERR(port_regs)) { if (IS_ERR(port_regs)) {
dev_err(ocelot->dev, dev_err(ocelot->dev,
"failed to map registers for port %d\n", port); "failed to map registers for port %d\n", port);
kfree(port_phy_modes);
return PTR_ERR(port_regs); return PTR_ERR(port_regs);
} }
ocelot_port->phy_mode = port_phy_modes[port];
ocelot_port->ocelot = ocelot; ocelot_port->ocelot = ocelot;
ocelot_port->regs = port_regs; ocelot_port->regs = port_regs;
ocelot->ports[port] = ocelot_port; 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; return 0;
} }
...@@ -293,12 +513,22 @@ static int felix_setup(struct dsa_switch *ds) ...@@ -293,12 +513,22 @@ static int felix_setup(struct dsa_switch *ds)
OCELOT_TAG_PREFIX_LONG); 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; return 0;
} }
static void felix_teardown(struct dsa_switch *ds) static void felix_teardown(struct dsa_switch *ds)
{ {
struct ocelot *ocelot = ds->priv; 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 */ /* stop workqueue thread */
ocelot_deinit(ocelot); ocelot_deinit(ocelot);
...@@ -369,7 +599,12 @@ static const struct dsa_switch_ops felix_switch_ops = { ...@@ -369,7 +599,12 @@ static const struct dsa_switch_ops felix_switch_ops = {
.get_ethtool_stats = felix_get_ethtool_stats, .get_ethtool_stats = felix_get_ethtool_stats,
.get_sset_count = felix_get_sset_count, .get_sset_count = felix_get_sset_count,
.get_ts_info = felix_get_ts_info, .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_enable = felix_port_enable,
.port_disable = felix_port_disable, .port_disable = felix_port_disable,
.port_fdb_dump = felix_fdb_dump, .port_fdb_dump = felix_fdb_dump,
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
struct felix_info { struct felix_info {
struct resource *target_io_res; struct resource *target_io_res;
struct resource *port_io_res; struct resource *port_io_res;
struct resource *imdio_res;
const struct reg_field *regfields; const struct reg_field *regfields;
const u32 *const *map; const u32 *const *map;
const struct ocelot_ops *ops; const struct ocelot_ops *ops;
...@@ -17,7 +18,18 @@ struct felix_info { ...@@ -17,7 +18,18 @@ struct felix_info {
const struct ocelot_stat_layout *stats_layout; const struct ocelot_stat_layout *stats_layout;
unsigned int num_stats; unsigned int num_stats;
int num_ports; 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; extern struct felix_info felix_info_vsc9959;
...@@ -32,6 +44,8 @@ struct felix { ...@@ -32,6 +44,8 @@ struct felix {
struct pci_dev *pdev; struct pci_dev *pdev;
struct felix_info *info; struct felix_info *info;
struct ocelot ocelot; struct ocelot ocelot;
struct mii_bus *imdio;
struct phy_device **pcs;
}; };
#endif #endif
...@@ -2,12 +2,33 @@ ...@@ -2,12 +2,33 @@
/* Copyright 2017 Microsemi Corporation /* Copyright 2017 Microsemi Corporation
* Copyright 2018-2019 NXP Semiconductors * Copyright 2018-2019 NXP Semiconductors
*/ */
#include <linux/fsl/enetc_mdio.h>
#include <soc/mscc/ocelot_sys.h> #include <soc/mscc/ocelot_sys.h>
#include <soc/mscc/ocelot.h> #include <soc/mscc/ocelot.h>
#include <linux/iopoll.h> #include <linux/iopoll.h>
#include <linux/pci.h> #include <linux/pci.h>
#include "felix.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[] = { static const u32 vsc9959_ana_regmap[] = {
REG(ANA_ADVLEARN, 0x0089a0), REG(ANA_ADVLEARN, 0x0089a0),
REG(ANA_VLANMASK, 0x0089a4), REG(ANA_VLANMASK, 0x0089a4),
...@@ -386,6 +407,15 @@ static struct resource vsc9959_port_io_res[] = { ...@@ -386,6 +407,15 @@ static struct resource vsc9959_port_io_res[] = {
}, },
}; };
/* 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[] = { static const struct reg_field vsc9959_regfields[] = {
[ANA_ADVLEARN_VLAN_CHK] = REG_FIELD(ANA_ADVLEARN, 6, 6), [ANA_ADVLEARN_VLAN_CHK] = REG_FIELD(ANA_ADVLEARN, 6, 6),
[ANA_ADVLEARN_LEARN_MIRROR] = REG_FIELD(ANA_ADVLEARN, 0, 5), [ANA_ADVLEARN_LEARN_MIRROR] = REG_FIELD(ANA_ADVLEARN, 0, 5),
...@@ -565,13 +595,475 @@ static int vsc9959_reset(struct ocelot *ocelot) ...@@ -565,13 +595,475 @@ static int vsc9959_reset(struct ocelot *ocelot)
return 0; 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 = { static const struct ocelot_ops vsc9959_ops = {
.reset = vsc9959_reset, .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 = { struct felix_info felix_info_vsc9959 = {
.target_io_res = vsc9959_target_io_res, .target_io_res = vsc9959_target_io_res,
.port_io_res = vsc9959_port_io_res, .port_io_res = vsc9959_port_io_res,
.imdio_res = &vsc9959_imdio_res,
.regfields = vsc9959_regfields, .regfields = vsc9959_regfields,
.map = vsc9959_regmap, .map = vsc9959_regmap,
.ops = &vsc9959_ops, .ops = &vsc9959_ops,
...@@ -579,5 +1071,12 @@ struct felix_info felix_info_vsc9959 = { ...@@ -579,5 +1071,12 @@ struct felix_info felix_info_vsc9959 = {
.num_stats = ARRAY_SIZE(vsc9959_stats_layout), .num_stats = ARRAY_SIZE(vsc9959_stats_layout),
.shared_queue_sz = 128 * 1024, .shared_queue_sz = 128 * 1024,
.num_ports = 6, .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,
}; };
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
config FSL_ENETC config FSL_ENETC
tristate "ENETC PF driver" tristate "ENETC PF driver"
depends on PCI && PCI_MSI && (ARCH_LAYERSCAPE || COMPILE_TEST) depends on PCI && PCI_MSI && (ARCH_LAYERSCAPE || COMPILE_TEST)
select FSL_ENETC_MDIO
select PHYLIB select PHYLIB
help help
This driver supports NXP ENETC gigabit ethernet controller PCIe This driver supports NXP ENETC gigabit ethernet controller PCIe
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
common-objs := enetc.o enetc_cbdr.o enetc_ethtool.o common-objs := enetc.o enetc_cbdr.o enetc_ethtool.o
obj-$(CONFIG_FSL_ENETC) += fsl-enetc.o obj-$(CONFIG_FSL_ENETC) += fsl-enetc.o
fsl-enetc-y := enetc_pf.o enetc_mdio.o $(common-objs) fsl-enetc-y := enetc_pf.o $(common-objs)
fsl-enetc-$(CONFIG_PCI_IOV) += enetc_msg.o fsl-enetc-$(CONFIG_PCI_IOV) += enetc_msg.o
fsl-enetc-$(CONFIG_FSL_ENETC_QOS) += enetc_qos.o fsl-enetc-$(CONFIG_FSL_ENETC_QOS) += enetc_qos.o
......
...@@ -200,6 +200,7 @@ enum enetc_bdr_type {TX, RX}; ...@@ -200,6 +200,7 @@ enum enetc_bdr_type {TX, RX};
#define ENETC_PFPMR 0x1900 #define ENETC_PFPMR 0x1900
#define ENETC_PFPMR_PMACE BIT(1) #define ENETC_PFPMR_PMACE BIT(1)
#define ENETC_PFPMR_MWLM BIT(0) #define ENETC_PFPMR_MWLM BIT(0)
#define ENETC_EMDIO_BASE 0x1c00
#define ENETC_PSIUMHFR0(n, err) (((err) ? 0x1d08 : 0x1d00) + (n) * 0x10) #define ENETC_PSIUMHFR0(n, err) (((err) ? 0x1d08 : 0x1d00) + (n) * 0x10)
#define ENETC_PSIUMHFR1(n) (0x1d04 + (n) * 0x10) #define ENETC_PSIUMHFR1(n) (0x1d04 + (n) * 0x10)
#define ENETC_PSIMMHFR0(n, err) (((err) ? 0x1d00 : 0x1d08) + (n) * 0x10) #define ENETC_PSIMMHFR0(n, err) (((err) ? 0x1d00 : 0x1d08) + (n) * 0x10)
......
// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) // SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
/* Copyright 2019 NXP */ /* Copyright 2019 NXP */
#include <linux/fsl/enetc_mdio.h>
#include <linux/mdio.h> #include <linux/mdio.h>
#include <linux/of_mdio.h> #include <linux/of_mdio.h>
#include <linux/iopoll.h> #include <linux/iopoll.h>
#include <linux/of.h> #include <linux/of.h>
#include "enetc_mdio.h" #include "enetc_pf.h"
#define ENETC_MDIO_REG_OFFSET 0x1c00
#define ENETC_MDIO_CFG 0x0 /* MDIO configuration and status */ #define ENETC_MDIO_CFG 0x0 /* MDIO configuration and status */
#define ENETC_MDIO_CTL 0x4 /* MDIO control */ #define ENETC_MDIO_CTL 0x4 /* MDIO control */
#define ENETC_MDIO_DATA 0x8 /* MDIO data */ #define ENETC_MDIO_DATA 0x8 /* MDIO data */
#define ENETC_MDIO_ADDR 0xc /* MDIO address */ #define ENETC_MDIO_ADDR 0xc /* MDIO address */
#define enetc_mdio_rd(hw, off) \ static inline u32 _enetc_mdio_rd(struct enetc_mdio_priv *mdio_priv, int off)
enetc_port_rd(hw, ENETC_##off + ENETC_MDIO_REG_OFFSET) {
#define enetc_mdio_wr(hw, off, val) \ return enetc_port_rd(mdio_priv->hw, mdio_priv->mdio_base + off);
enetc_port_wr(hw, ENETC_##off + ENETC_MDIO_REG_OFFSET, val) }
#define enetc_mdio_rd_reg(off) enetc_mdio_rd(hw, off)
static inline void _enetc_mdio_wr(struct enetc_mdio_priv *mdio_priv, int off,
u32 val)
{
enetc_port_wr(mdio_priv->hw, mdio_priv->mdio_base + off, val);
}
#define ENETC_MDC_DIV 258 #define enetc_mdio_rd(mdio_priv, off) \
_enetc_mdio_rd(mdio_priv, ENETC_##off)
#define enetc_mdio_wr(mdio_priv, off, val) \
_enetc_mdio_wr(mdio_priv, ENETC_##off, val)
#define enetc_mdio_rd_reg(off) enetc_mdio_rd(mdio_priv, off)
#define MDIO_CFG_CLKDIV(x) ((((x) >> 1) & 0xff) << 8) #define MDIO_CFG_CLKDIV(x) ((((x) >> 1) & 0xff) << 8)
#define MDIO_CFG_BSY BIT(0) #define MDIO_CFG_BSY BIT(0)
#define MDIO_CFG_RD_ER BIT(1) #define MDIO_CFG_RD_ER BIT(1)
#define MDIO_CFG_HOLD(x) (((x) << 2) & GENMASK(4, 2))
#define MDIO_CFG_ENC45 BIT(6) #define MDIO_CFG_ENC45 BIT(6)
/* external MDIO only - driven on neg MDC edge */ /* external MDIO only - driven on neg MDC edge */
#define MDIO_CFG_NEG BIT(23) #define MDIO_CFG_NEG BIT(23)
#define ENETC_EMDIO_CFG \
(MDIO_CFG_HOLD(2) | \
MDIO_CFG_CLKDIV(258) | \
MDIO_CFG_NEG)
#define MDIO_CTL_DEV_ADDR(x) ((x) & 0x1f) #define MDIO_CTL_DEV_ADDR(x) ((x) & 0x1f)
#define MDIO_CTL_PORT_ADDR(x) (((x) & 0x1f) << 5) #define MDIO_CTL_PORT_ADDR(x) (((x) & 0x1f) << 5)
#define MDIO_CTL_READ BIT(15) #define MDIO_CTL_READ BIT(15)
#define MDIO_DATA(x) ((x) & 0xffff) #define MDIO_DATA(x) ((x) & 0xffff)
#define TIMEOUT 1000 #define TIMEOUT 1000
static int enetc_mdio_wait_complete(struct enetc_hw *hw) static int enetc_mdio_wait_complete(struct enetc_mdio_priv *mdio_priv)
{ {
u32 val; u32 val;
...@@ -46,12 +61,11 @@ static int enetc_mdio_wait_complete(struct enetc_hw *hw) ...@@ -46,12 +61,11 @@ static int enetc_mdio_wait_complete(struct enetc_hw *hw)
int enetc_mdio_write(struct mii_bus *bus, int phy_id, int regnum, u16 value) int enetc_mdio_write(struct mii_bus *bus, int phy_id, int regnum, u16 value)
{ {
struct enetc_mdio_priv *mdio_priv = bus->priv; struct enetc_mdio_priv *mdio_priv = bus->priv;
struct enetc_hw *hw = mdio_priv->hw;
u32 mdio_ctl, mdio_cfg; u32 mdio_ctl, mdio_cfg;
u16 dev_addr; u16 dev_addr;
int ret; int ret;
mdio_cfg = MDIO_CFG_CLKDIV(ENETC_MDC_DIV) | MDIO_CFG_NEG; mdio_cfg = ENETC_EMDIO_CFG;
if (regnum & MII_ADDR_C45) { if (regnum & MII_ADDR_C45) {
dev_addr = (regnum >> 16) & 0x1f; dev_addr = (regnum >> 16) & 0x1f;
mdio_cfg |= MDIO_CFG_ENC45; mdio_cfg |= MDIO_CFG_ENC45;
...@@ -61,44 +75,44 @@ int enetc_mdio_write(struct mii_bus *bus, int phy_id, int regnum, u16 value) ...@@ -61,44 +75,44 @@ int enetc_mdio_write(struct mii_bus *bus, int phy_id, int regnum, u16 value)
mdio_cfg &= ~MDIO_CFG_ENC45; mdio_cfg &= ~MDIO_CFG_ENC45;
} }
enetc_mdio_wr(hw, MDIO_CFG, mdio_cfg); enetc_mdio_wr(mdio_priv, MDIO_CFG, mdio_cfg);
ret = enetc_mdio_wait_complete(hw); ret = enetc_mdio_wait_complete(mdio_priv);
if (ret) if (ret)
return ret; return ret;
/* set port and dev addr */ /* set port and dev addr */
mdio_ctl = MDIO_CTL_PORT_ADDR(phy_id) | MDIO_CTL_DEV_ADDR(dev_addr); mdio_ctl = MDIO_CTL_PORT_ADDR(phy_id) | MDIO_CTL_DEV_ADDR(dev_addr);
enetc_mdio_wr(hw, MDIO_CTL, mdio_ctl); enetc_mdio_wr(mdio_priv, MDIO_CTL, mdio_ctl);
/* set the register address */ /* set the register address */
if (regnum & MII_ADDR_C45) { if (regnum & MII_ADDR_C45) {
enetc_mdio_wr(hw, MDIO_ADDR, regnum & 0xffff); enetc_mdio_wr(mdio_priv, MDIO_ADDR, regnum & 0xffff);
ret = enetc_mdio_wait_complete(hw); ret = enetc_mdio_wait_complete(mdio_priv);
if (ret) if (ret)
return ret; return ret;
} }
/* write the value */ /* write the value */
enetc_mdio_wr(hw, MDIO_DATA, MDIO_DATA(value)); enetc_mdio_wr(mdio_priv, MDIO_DATA, MDIO_DATA(value));
ret = enetc_mdio_wait_complete(hw); ret = enetc_mdio_wait_complete(mdio_priv);
if (ret) if (ret)
return ret; return ret;
return 0; return 0;
} }
EXPORT_SYMBOL_GPL(enetc_mdio_write);
int enetc_mdio_read(struct mii_bus *bus, int phy_id, int regnum) int enetc_mdio_read(struct mii_bus *bus, int phy_id, int regnum)
{ {
struct enetc_mdio_priv *mdio_priv = bus->priv; struct enetc_mdio_priv *mdio_priv = bus->priv;
struct enetc_hw *hw = mdio_priv->hw;
u32 mdio_ctl, mdio_cfg; u32 mdio_ctl, mdio_cfg;
u16 dev_addr, value; u16 dev_addr, value;
int ret; int ret;
mdio_cfg = MDIO_CFG_CLKDIV(ENETC_MDC_DIV) | MDIO_CFG_NEG; mdio_cfg = ENETC_EMDIO_CFG;
if (regnum & MII_ADDR_C45) { if (regnum & MII_ADDR_C45) {
dev_addr = (regnum >> 16) & 0x1f; dev_addr = (regnum >> 16) & 0x1f;
mdio_cfg |= MDIO_CFG_ENC45; mdio_cfg |= MDIO_CFG_ENC45;
...@@ -107,86 +121,56 @@ int enetc_mdio_read(struct mii_bus *bus, int phy_id, int regnum) ...@@ -107,86 +121,56 @@ int enetc_mdio_read(struct mii_bus *bus, int phy_id, int regnum)
mdio_cfg &= ~MDIO_CFG_ENC45; mdio_cfg &= ~MDIO_CFG_ENC45;
} }
enetc_mdio_wr(hw, MDIO_CFG, mdio_cfg); enetc_mdio_wr(mdio_priv, MDIO_CFG, mdio_cfg);
ret = enetc_mdio_wait_complete(hw); ret = enetc_mdio_wait_complete(mdio_priv);
if (ret) if (ret)
return ret; return ret;
/* set port and device addr */ /* set port and device addr */
mdio_ctl = MDIO_CTL_PORT_ADDR(phy_id) | MDIO_CTL_DEV_ADDR(dev_addr); mdio_ctl = MDIO_CTL_PORT_ADDR(phy_id) | MDIO_CTL_DEV_ADDR(dev_addr);
enetc_mdio_wr(hw, MDIO_CTL, mdio_ctl); enetc_mdio_wr(mdio_priv, MDIO_CTL, mdio_ctl);
/* set the register address */ /* set the register address */
if (regnum & MII_ADDR_C45) { if (regnum & MII_ADDR_C45) {
enetc_mdio_wr(hw, MDIO_ADDR, regnum & 0xffff); enetc_mdio_wr(mdio_priv, MDIO_ADDR, regnum & 0xffff);
ret = enetc_mdio_wait_complete(hw); ret = enetc_mdio_wait_complete(mdio_priv);
if (ret) if (ret)
return ret; return ret;
} }
/* initiate the read */ /* initiate the read */
enetc_mdio_wr(hw, MDIO_CTL, mdio_ctl | MDIO_CTL_READ); enetc_mdio_wr(mdio_priv, MDIO_CTL, mdio_ctl | MDIO_CTL_READ);
ret = enetc_mdio_wait_complete(hw); ret = enetc_mdio_wait_complete(mdio_priv);
if (ret) if (ret)
return ret; return ret;
/* return all Fs if nothing was there */ /* return all Fs if nothing was there */
if (enetc_mdio_rd(hw, MDIO_CFG) & MDIO_CFG_RD_ER) { if (enetc_mdio_rd(mdio_priv, MDIO_CFG) & MDIO_CFG_RD_ER) {
dev_dbg(&bus->dev, dev_dbg(&bus->dev,
"Error while reading PHY%d reg at %d.%hhu\n", "Error while reading PHY%d reg at %d.%hhu\n",
phy_id, dev_addr, regnum); phy_id, dev_addr, regnum);
return 0xffff; return 0xffff;
} }
value = enetc_mdio_rd(hw, MDIO_DATA) & 0xffff; value = enetc_mdio_rd(mdio_priv, MDIO_DATA) & 0xffff;
return value; return value;
} }
EXPORT_SYMBOL_GPL(enetc_mdio_read);
int enetc_mdio_probe(struct enetc_pf *pf) struct enetc_hw *enetc_hw_alloc(struct device *dev, void __iomem *port_regs)
{ {
struct device *dev = &pf->si->pdev->dev; struct enetc_hw *hw;
struct enetc_mdio_priv *mdio_priv;
struct device_node *np;
struct mii_bus *bus;
int err;
bus = devm_mdiobus_alloc_size(dev, sizeof(*mdio_priv));
if (!bus)
return -ENOMEM;
bus->name = "Freescale ENETC MDIO Bus";
bus->read = enetc_mdio_read;
bus->write = enetc_mdio_write;
bus->parent = dev;
mdio_priv = bus->priv;
mdio_priv->hw = &pf->si->hw;
snprintf(bus->id, MII_BUS_ID_SIZE, "%s", dev_name(dev));
np = of_get_child_by_name(dev->of_node, "mdio");
if (!np) {
dev_err(dev, "MDIO node missing\n");
return -EINVAL;
}
err = of_mdiobus_register(bus, np);
if (err) {
of_node_put(np);
dev_err(dev, "cannot register MDIO bus\n");
return err;
}
of_node_put(np); hw = devm_kzalloc(dev, sizeof(*hw), GFP_KERNEL);
pf->mdio = bus; if (!hw)
return ERR_PTR(-ENOMEM);
return 0; hw->port = port_regs;
}
void enetc_mdio_remove(struct enetc_pf *pf) return hw;
{
if (pf->mdio)
mdiobus_unregister(pf->mdio);
} }
EXPORT_SYMBOL_GPL(enetc_hw_alloc);
/* SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) */
/* Copyright 2019 NXP */
#include <linux/phy.h>
#include "enetc_pf.h"
struct enetc_mdio_priv {
struct enetc_hw *hw;
};
int enetc_mdio_write(struct mii_bus *bus, int phy_id, int regnum, u16 value);
int enetc_mdio_read(struct mii_bus *bus, int phy_id, int regnum);
// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) // SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
/* Copyright 2019 NXP */ /* Copyright 2019 NXP */
#include <linux/fsl/enetc_mdio.h>
#include <linux/of_mdio.h> #include <linux/of_mdio.h>
#include "enetc_mdio.h" #include "enetc_pf.h"
#define ENETC_MDIO_DEV_ID 0xee01 #define ENETC_MDIO_DEV_ID 0xee01
#define ENETC_MDIO_DEV_NAME "FSL PCIe IE Central MDIO" #define ENETC_MDIO_DEV_NAME "FSL PCIe IE Central MDIO"
...@@ -13,17 +14,29 @@ static int enetc_pci_mdio_probe(struct pci_dev *pdev, ...@@ -13,17 +14,29 @@ static int enetc_pci_mdio_probe(struct pci_dev *pdev,
{ {
struct enetc_mdio_priv *mdio_priv; struct enetc_mdio_priv *mdio_priv;
struct device *dev = &pdev->dev; struct device *dev = &pdev->dev;
void __iomem *port_regs;
struct enetc_hw *hw; struct enetc_hw *hw;
struct mii_bus *bus; struct mii_bus *bus;
int err; int err;
hw = devm_kzalloc(dev, sizeof(*hw), GFP_KERNEL); port_regs = pci_iomap(pdev, 0, 0);
if (!hw) if (!port_regs) {
return -ENOMEM; dev_err(dev, "iomap failed\n");
err = -ENXIO;
goto err_ioremap;
}
hw = enetc_hw_alloc(dev, port_regs);
if (IS_ERR(enetc_hw_alloc)) {
err = PTR_ERR(hw);
goto err_hw_alloc;
}
bus = devm_mdiobus_alloc_size(dev, sizeof(*mdio_priv)); bus = devm_mdiobus_alloc_size(dev, sizeof(*mdio_priv));
if (!bus) if (!bus) {
return -ENOMEM; err = -ENOMEM;
goto err_mdiobus_alloc;
}
bus->name = ENETC_MDIO_BUS_NAME; bus->name = ENETC_MDIO_BUS_NAME;
bus->read = enetc_mdio_read; bus->read = enetc_mdio_read;
...@@ -31,13 +44,14 @@ static int enetc_pci_mdio_probe(struct pci_dev *pdev, ...@@ -31,13 +44,14 @@ static int enetc_pci_mdio_probe(struct pci_dev *pdev,
bus->parent = dev; bus->parent = dev;
mdio_priv = bus->priv; mdio_priv = bus->priv;
mdio_priv->hw = hw; mdio_priv->hw = hw;
mdio_priv->mdio_base = ENETC_EMDIO_BASE;
snprintf(bus->id, MII_BUS_ID_SIZE, "%s", dev_name(dev)); snprintf(bus->id, MII_BUS_ID_SIZE, "%s", dev_name(dev));
pcie_flr(pdev); pcie_flr(pdev);
err = pci_enable_device_mem(pdev); err = pci_enable_device_mem(pdev);
if (err) { if (err) {
dev_err(dev, "device enable failed\n"); dev_err(dev, "device enable failed\n");
return err; goto err_pci_enable;
} }
err = pci_request_region(pdev, 0, KBUILD_MODNAME); err = pci_request_region(pdev, 0, KBUILD_MODNAME);
...@@ -46,13 +60,6 @@ static int enetc_pci_mdio_probe(struct pci_dev *pdev, ...@@ -46,13 +60,6 @@ static int enetc_pci_mdio_probe(struct pci_dev *pdev,
goto err_pci_mem_reg; goto err_pci_mem_reg;
} }
hw->port = pci_iomap(pdev, 0, 0);
if (!hw->port) {
err = -ENXIO;
dev_err(dev, "iomap failed\n");
goto err_ioremap;
}
err = of_mdiobus_register(bus, dev->of_node); err = of_mdiobus_register(bus, dev->of_node);
if (err) if (err)
goto err_mdiobus_reg; goto err_mdiobus_reg;
...@@ -62,12 +69,14 @@ static int enetc_pci_mdio_probe(struct pci_dev *pdev, ...@@ -62,12 +69,14 @@ static int enetc_pci_mdio_probe(struct pci_dev *pdev,
return 0; return 0;
err_mdiobus_reg: err_mdiobus_reg:
iounmap(mdio_priv->hw->port);
err_ioremap:
pci_release_mem_regions(pdev); pci_release_mem_regions(pdev);
err_pci_mem_reg: err_pci_mem_reg:
pci_disable_device(pdev); pci_disable_device(pdev);
err_pci_enable:
err_mdiobus_alloc:
iounmap(port_regs);
err_hw_alloc:
err_ioremap:
return err; return err;
} }
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
/* Copyright 2017-2019 NXP */ /* Copyright 2017-2019 NXP */
#include <linux/module.h> #include <linux/module.h>
#include <linux/fsl/enetc_mdio.h>
#include <linux/of_mdio.h> #include <linux/of_mdio.h>
#include <linux/of_net.h> #include <linux/of_net.h>
#include "enetc_pf.h" #include "enetc_pf.h"
...@@ -749,6 +750,52 @@ static void enetc_pf_netdev_setup(struct enetc_si *si, struct net_device *ndev, ...@@ -749,6 +750,52 @@ static void enetc_pf_netdev_setup(struct enetc_si *si, struct net_device *ndev,
enetc_get_primary_mac_addr(&si->hw, ndev->dev_addr); enetc_get_primary_mac_addr(&si->hw, ndev->dev_addr);
} }
static int enetc_mdio_probe(struct enetc_pf *pf)
{
struct device *dev = &pf->si->pdev->dev;
struct enetc_mdio_priv *mdio_priv;
struct device_node *np;
struct mii_bus *bus;
int err;
bus = devm_mdiobus_alloc_size(dev, sizeof(*mdio_priv));
if (!bus)
return -ENOMEM;
bus->name = "Freescale ENETC MDIO Bus";
bus->read = enetc_mdio_read;
bus->write = enetc_mdio_write;
bus->parent = dev;
mdio_priv = bus->priv;
mdio_priv->hw = &pf->si->hw;
mdio_priv->mdio_base = ENETC_EMDIO_BASE;
snprintf(bus->id, MII_BUS_ID_SIZE, "%s", dev_name(dev));
np = of_get_child_by_name(dev->of_node, "mdio");
if (!np) {
dev_err(dev, "MDIO node missing\n");
return -EINVAL;
}
err = of_mdiobus_register(bus, np);
if (err) {
of_node_put(np);
dev_err(dev, "cannot register MDIO bus\n");
return err;
}
of_node_put(np);
pf->mdio = bus;
return 0;
}
static void enetc_mdio_remove(struct enetc_pf *pf)
{
if (pf->mdio)
mdiobus_unregister(pf->mdio);
}
static int enetc_of_get_phy(struct enetc_ndev_priv *priv) static int enetc_of_get_phy(struct enetc_ndev_priv *priv)
{ {
struct enetc_pf *pf = enetc_si_priv(priv->si); struct enetc_pf *pf = enetc_si_priv(priv->si);
......
...@@ -49,7 +49,3 @@ struct enetc_pf { ...@@ -49,7 +49,3 @@ struct enetc_pf {
int enetc_msg_psi_init(struct enetc_pf *pf); int enetc_msg_psi_init(struct enetc_pf *pf);
void enetc_msg_psi_free(struct enetc_pf *pf); void enetc_msg_psi_free(struct enetc_pf *pf);
void enetc_msg_handle_rxmsg(struct enetc_pf *pf, int mbox_id, u16 *status); void enetc_msg_handle_rxmsg(struct enetc_pf *pf, int mbox_id, u16 *status);
/* MDIO */
int enetc_mdio_probe(struct enetc_pf *pf);
void enetc_mdio_remove(struct enetc_pf *pf);
...@@ -500,13 +500,14 @@ EXPORT_SYMBOL(ocelot_port_enable); ...@@ -500,13 +500,14 @@ EXPORT_SYMBOL(ocelot_port_enable);
static int ocelot_port_open(struct net_device *dev) static int ocelot_port_open(struct net_device *dev)
{ {
struct ocelot_port_private *priv = netdev_priv(dev); struct ocelot_port_private *priv = netdev_priv(dev);
struct ocelot *ocelot = priv->port.ocelot; struct ocelot_port *ocelot_port = &priv->port;
struct ocelot *ocelot = ocelot_port->ocelot;
int port = priv->chip_port; int port = priv->chip_port;
int err; int err;
if (priv->serdes) { if (priv->serdes) {
err = phy_set_mode_ext(priv->serdes, PHY_MODE_ETHERNET, err = phy_set_mode_ext(priv->serdes, PHY_MODE_ETHERNET,
priv->phy_mode); ocelot_port->phy_mode);
if (err) { if (err) {
netdev_err(dev, "Could not set mode of SerDes\n"); netdev_err(dev, "Could not set mode of SerDes\n");
return err; return err;
...@@ -514,7 +515,7 @@ static int ocelot_port_open(struct net_device *dev) ...@@ -514,7 +515,7 @@ static int ocelot_port_open(struct net_device *dev)
} }
err = phy_connect_direct(dev, priv->phy, &ocelot_port_adjust_link, err = phy_connect_direct(dev, priv->phy, &ocelot_port_adjust_link,
priv->phy_mode); ocelot_port->phy_mode);
if (err) { if (err) {
netdev_err(dev, "Could not attach to PHY\n"); netdev_err(dev, "Could not attach to PHY\n");
return err; return err;
......
...@@ -18,11 +18,11 @@ ...@@ -18,11 +18,11 @@
#include <linux/ptp_clock_kernel.h> #include <linux/ptp_clock_kernel.h>
#include <linux/regmap.h> #include <linux/regmap.h>
#include <soc/mscc/ocelot_qsys.h>
#include <soc/mscc/ocelot_sys.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 <soc/mscc/ocelot.h>
#include "ocelot_ana.h"
#include "ocelot_dev.h"
#include "ocelot_qsys.h"
#include "ocelot_rew.h" #include "ocelot_rew.h"
#include "ocelot_qs.h" #include "ocelot_qs.h"
#include "ocelot_tc.h" #include "ocelot_tc.h"
...@@ -68,7 +68,6 @@ struct ocelot_port_private { ...@@ -68,7 +68,6 @@ struct ocelot_port_private {
u8 vlan_aware; u8 vlan_aware;
phy_interface_t phy_mode;
struct phy *serdes; struct phy *serdes;
struct ocelot_port_tc tc; struct ocelot_port_tc tc;
......
...@@ -402,9 +402,9 @@ static int mscc_ocelot_probe(struct platform_device *pdev) ...@@ -402,9 +402,9 @@ static int mscc_ocelot_probe(struct platform_device *pdev)
of_get_phy_mode(portnp, &phy_mode); of_get_phy_mode(portnp, &phy_mode);
priv->phy_mode = phy_mode; ocelot_port->phy_mode = phy_mode;
switch (priv->phy_mode) { switch (ocelot_port->phy_mode) {
case PHY_INTERFACE_MODE_NA: case PHY_INTERFACE_MODE_NA:
continue; continue;
case PHY_INTERFACE_MODE_SGMII: case PHY_INTERFACE_MODE_SGMII:
......
...@@ -281,6 +281,7 @@ static int phylink_parse_mode(struct phylink *pl, struct fwnode_handle *fwnode) ...@@ -281,6 +281,7 @@ static int phylink_parse_mode(struct phylink *pl, struct fwnode_handle *fwnode)
switch (pl->link_config.interface) { switch (pl->link_config.interface) {
case PHY_INTERFACE_MODE_SGMII: case PHY_INTERFACE_MODE_SGMII:
case PHY_INTERFACE_MODE_QSGMII:
phylink_set(pl->supported, 10baseT_Half); phylink_set(pl->supported, 10baseT_Half);
phylink_set(pl->supported, 10baseT_Full); phylink_set(pl->supported, 10baseT_Full);
phylink_set(pl->supported, 100baseT_Half); phylink_set(pl->supported, 100baseT_Half);
...@@ -1021,7 +1022,8 @@ void phylink_start(struct phylink *pl) ...@@ -1021,7 +1022,8 @@ void phylink_start(struct phylink *pl)
if (irq <= 0) if (irq <= 0)
mod_timer(&pl->link_poll, jiffies + HZ); mod_timer(&pl->link_poll, jiffies + HZ);
} }
if (pl->cfg_link_an_mode == MLO_AN_FIXED && pl->get_fixed_state) if ((pl->cfg_link_an_mode == MLO_AN_FIXED && pl->get_fixed_state) ||
pl->config->pcs_poll)
mod_timer(&pl->link_poll, jiffies + HZ); mod_timer(&pl->link_poll, jiffies + HZ);
if (pl->phydev) if (pl->phydev)
phy_start(pl->phydev); phy_start(pl->phydev);
......
/* SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) */
/* Copyright 2019 NXP */
#ifndef _FSL_ENETC_MDIO_H_
#define _FSL_ENETC_MDIO_H_
#include <linux/phy.h>
/* PCS registers */
#define ENETC_PCS_LINK_TIMER1 0x12
#define ENETC_PCS_LINK_TIMER1_VAL 0x06a0
#define ENETC_PCS_LINK_TIMER2 0x13
#define ENETC_PCS_LINK_TIMER2_VAL 0x0003
#define ENETC_PCS_IF_MODE 0x14
#define ENETC_PCS_IF_MODE_SGMII_EN BIT(0)
#define ENETC_PCS_IF_MODE_USE_SGMII_AN BIT(1)
#define ENETC_PCS_IF_MODE_SGMII_SPEED(x) (((x) << 2) & GENMASK(3, 2))
/* Not a mistake, the SerDes PLL needs to be set at 3.125 GHz by Reset
* Configuration Word (RCW, outside Linux control) for 2.5G SGMII mode. The PCS
* still thinks it's at gigabit.
*/
enum enetc_pcs_speed {
ENETC_PCS_SPEED_10 = 0,
ENETC_PCS_SPEED_100 = 1,
ENETC_PCS_SPEED_1000 = 2,
ENETC_PCS_SPEED_2500 = 2,
};
struct enetc_hw;
struct enetc_mdio_priv {
struct enetc_hw *hw;
int mdio_base;
};
#if IS_REACHABLE(CONFIG_FSL_ENETC_MDIO)
int enetc_mdio_read(struct mii_bus *bus, int phy_id, int regnum);
int enetc_mdio_write(struct mii_bus *bus, int phy_id, int regnum, u16 value);
struct enetc_hw *enetc_hw_alloc(struct device *dev, void __iomem *port_regs);
#else
static inline int enetc_mdio_read(struct mii_bus *bus, int phy_id, int regnum)
{ return -EINVAL; }
static inline int enetc_mdio_write(struct mii_bus *bus, int phy_id, int regnum,
u16 value)
{ return -EINVAL; }
struct enetc_hw *enetc_hw_alloc(struct device *dev, void __iomem *port_regs)
{ return ERR_PTR(-EINVAL); }
#endif
#endif
...@@ -372,6 +372,56 @@ static inline u32 mii_lpa_to_ethtool_lpa_x(u32 lpa) ...@@ -372,6 +372,56 @@ static inline u32 mii_lpa_to_ethtool_lpa_x(u32 lpa)
return result | mii_adv_to_ethtool_adv_x(lpa); return result | mii_adv_to_ethtool_adv_x(lpa);
} }
/**
* mii_lpa_mod_linkmode_adv_sgmii
* @lp_advertising: pointer to destination link mode.
* @lpa: value of the MII_LPA register
*
* A small helper function that translates MII_LPA bits to
* linkmode advertisement settings for SGMII.
* Leaves other bits unchanged.
*/
static inline void
mii_lpa_mod_linkmode_lpa_sgmii(unsigned long *lp_advertising, u32 lpa)
{
u32 speed_duplex = lpa & LPA_SGMII_DPX_SPD_MASK;
linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT, lp_advertising,
speed_duplex == LPA_SGMII_1000HALF);
linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, lp_advertising,
speed_duplex == LPA_SGMII_1000FULL);
linkmode_mod_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT, lp_advertising,
speed_duplex == LPA_SGMII_100HALF);
linkmode_mod_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, lp_advertising,
speed_duplex == LPA_SGMII_100FULL);
linkmode_mod_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT, lp_advertising,
speed_duplex == LPA_SGMII_10HALF);
linkmode_mod_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT, lp_advertising,
speed_duplex == LPA_SGMII_10FULL);
}
/**
* mii_lpa_to_linkmode_adv_sgmii
* @advertising: pointer to destination link mode.
* @lpa: value of the MII_LPA register
*
* A small helper function that translates MII_ADVERTISE bits
* to linkmode advertisement settings when in SGMII mode.
* Clears the old value of advertising.
*/
static inline void mii_lpa_to_linkmode_lpa_sgmii(unsigned long *lp_advertising,
u32 lpa)
{
linkmode_zero(lp_advertising);
mii_lpa_mod_linkmode_lpa_sgmii(lp_advertising, lpa);
}
/** /**
* mii_adv_mod_linkmode_adv_t * mii_adv_mod_linkmode_adv_t
* @advertising:pointer to destination link mode. * @advertising:pointer to destination link mode.
......
...@@ -63,10 +63,12 @@ enum phylink_op_type { ...@@ -63,10 +63,12 @@ enum phylink_op_type {
* struct phylink_config - PHYLINK configuration structure * struct phylink_config - PHYLINK configuration structure
* @dev: a pointer to a struct device associated with the MAC * @dev: a pointer to a struct device associated with the MAC
* @type: operation type of PHYLINK instance * @type: operation type of PHYLINK instance
* @pcs_poll: MAC PCS cannot provide link change interrupt
*/ */
struct phylink_config { struct phylink_config {
struct device *dev; struct device *dev;
enum phylink_op_type type; enum phylink_op_type type;
bool pcs_poll;
}; };
/** /**
......
...@@ -279,6 +279,11 @@ struct dsa_switch { ...@@ -279,6 +279,11 @@ struct dsa_switch {
*/ */
bool vlan_filtering; bool vlan_filtering;
/* MAC PCS does not provide link state change interrupt, and requires
* polling. Flag passed on to PHYLINK.
*/
bool pcs_poll;
size_t num_ports; size_t num_ports;
}; };
......
...@@ -420,6 +420,8 @@ struct ocelot_port { ...@@ -420,6 +420,8 @@ struct ocelot_port {
u8 ptp_cmd; u8 ptp_cmd;
struct sk_buff_head tx_skbs; struct sk_buff_head tx_skbs;
u8 ts_id; u8 ts_id;
phy_interface_t phy_mode;
}; };
struct ocelot { struct ocelot {
......
...@@ -131,6 +131,18 @@ ...@@ -131,6 +131,18 @@
#define NWAYTEST_LOOPBACK 0x0100 /* Enable loopback for N-way */ #define NWAYTEST_LOOPBACK 0x0100 /* Enable loopback for N-way */
#define NWAYTEST_RESV2 0xfe00 /* Unused... */ #define NWAYTEST_RESV2 0xfe00 /* Unused... */
/* MAC and PHY tx_config_Reg[15:0] for SGMII in-band auto-negotiation.*/
#define ADVERTISE_SGMII 0x0001 /* MAC can do SGMII */
#define LPA_SGMII 0x0001 /* PHY can do SGMII */
#define LPA_SGMII_DPX_SPD_MASK 0x1C00 /* SGMII duplex and speed bits */
#define LPA_SGMII_10HALF 0x0000 /* Can do 10mbps half-duplex */
#define LPA_SGMII_10FULL 0x1000 /* Can do 10mbps full-duplex */
#define LPA_SGMII_100HALF 0x0400 /* Can do 100mbps half-duplex */
#define LPA_SGMII_100FULL 0x1400 /* Can do 100mbps full-duplex */
#define LPA_SGMII_1000HALF 0x0800 /* Can do 1000mbps half-duplex */
#define LPA_SGMII_1000FULL 0x1800 /* Can do 1000mbps full-duplex */
#define LPA_SGMII_LINK 0x8000 /* PHY link with copper-side partner */
/* 1000BASE-T Control register */ /* 1000BASE-T Control register */
#define ADVERTISE_1000FULL 0x0200 /* Advertise 1000BASE-T full duplex */ #define ADVERTISE_1000FULL 0x0200 /* Advertise 1000BASE-T full duplex */
#define ADVERTISE_1000HALF 0x0100 /* Advertise 1000BASE-T half duplex */ #define ADVERTISE_1000HALF 0x0100 /* Advertise 1000BASE-T half duplex */
......
...@@ -599,6 +599,7 @@ static int dsa_port_phylink_register(struct dsa_port *dp) ...@@ -599,6 +599,7 @@ static int dsa_port_phylink_register(struct dsa_port *dp)
dp->pl_config.dev = ds->dev; dp->pl_config.dev = ds->dev;
dp->pl_config.type = PHYLINK_DEV; dp->pl_config.type = PHYLINK_DEV;
dp->pl_config.pcs_poll = ds->pcs_poll;
dp->pl = phylink_create(&dp->pl_config, of_fwnode_handle(port_dn), dp->pl = phylink_create(&dp->pl_config, of_fwnode_handle(port_dn),
mode, &dsa_port_phylink_mac_ops); mode, &dsa_port_phylink_mac_ops);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment