summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRussell King <rmk+kernel@armlinux.org.uk>2020-01-30 22:42:38 +0000
committerRussell King <rmk+kernel@armlinux.org.uk>2020-10-12 22:41:18 +0100
commit27900f6c502bf248ecb713ed8f52922098df398d (patch)
treef5b27299cf5f41754213f0c8ff9995b4579a341b
parent65e4c1d470c47721372595ff3129923192267f1f (diff)
dpaa2-mac: add 1000BASE-X/SGMII PCS support
Add support for the PCS block, so we can dynamically configure it for 1000base-X or SGMII depending on the SFP module inserted. This gives us more flexibility than using the MC firmware with a "fixed" link type, which can only be setup to support a single interface mode. We add support for the "backplane" mode, which is the mode we should be using. Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
-rw-r--r--drivers/net/ethernet/freescale/dpaa2/dpaa2-mac.c231
-rw-r--r--drivers/net/ethernet/freescale/dpaa2/dpaa2-mac.h3
2 files changed, 231 insertions, 3 deletions
diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-mac.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-mac.c
index f3a2611cb76e..49af3f1af32b 100644
--- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-mac.c
+++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-mac.c
@@ -7,6 +7,143 @@
#define phylink_to_dpaa2_mac(config) \
container_of((config), struct dpaa2_mac, phylink_config)
+struct lynx_pcs {
+ struct mdio_device *mdio;
+ struct phylink_pcs pcs;
+};
+
+static struct lynx_pcs *phylink_pcs_to_lynx(struct phylink_pcs *pcs)
+{
+ return container_of(pcs, struct lynx_pcs, pcs);
+}
+
+// ID: 0x0083 0xe400 => OUI 00:20:f9 => Paralink Networks, Inc.
+#define MII_LINK_TIMER_1 0x12
+#define MII_LINK_TIMER_2 0x13
+#define LINK_TIMER_US_IEEE8023 10000
+#define LINK_TIMER_US_SGMII 1600
+#define DPAA2_MAC_PCS_CLK_MHZ 125
+
+#define MII_IFMODE 0x14
+#define IF_MODE_SGMII_ENA BIT(0)
+#define IF_MODE_USE_SGMII_AN BIT(1)
+#define IF_MODE_SGMII_SPEED_10 (0 << 2)
+#define IF_MODE_SGMII_SPEED_100 (1 << 2)
+#define IF_MODE_SGMII_SPEED_1G (2 << 2)
+#define IF_MODE_SGMII_SPEED_MSK (3 << 2)
+#define IF_MODE_SGMII_DUPLEX BIT(4) // set = half duplex
+
+static void lynx_pcs_get_state(struct phylink_pcs *pcs,
+ struct phylink_link_state *state)
+{
+ struct lynx_pcs *lynx = phylink_pcs_to_lynx(pcs);
+
+ switch (state->interface) {
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_1000BASEX:
+ phylink_mii_c22_pcs_get_state(lynx->mdio, state);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void lynx_pcs_an_restart(struct phylink_pcs *pcs)
+{
+ struct lynx_pcs *lynx = phylink_pcs_to_lynx(pcs);
+
+ phylink_mii_c22_pcs_an_restart(lynx->mdio);
+}
+
+static int lynx_pcs_config(struct phylink_pcs *pcs,
+ unsigned int mode, phy_interface_t interface,
+ const unsigned long *advertising,
+ bool permit_pause_to_mac)
+{
+ struct lynx_pcs *lynx = phylink_pcs_to_lynx(pcs);
+ u32 link_timer;
+ u16 if_mode;
+ int ret;
+
+ switch (interface) {
+ case PHY_INTERFACE_MODE_SGMII:
+ if_mode = IF_MODE_SGMII_ENA;
+ if (mode == MLO_AN_INBAND)
+ if_mode |= IF_MODE_USE_SGMII_AN;
+ link_timer = DPAA2_MAC_PCS_CLK_MHZ * LINK_TIMER_US_SGMII;
+ mdiobus_write(lynx->mdio->bus, lynx->mdio->addr,
+ MII_LINK_TIMER_1, link_timer & 0xffff);
+ mdiobus_write(lynx->mdio->bus, lynx->mdio->addr,
+ MII_LINK_TIMER_2, link_timer >> 16);
+ mdiobus_modify(lynx->mdio->bus, lynx->mdio->addr,
+ MII_IFMODE,
+ IF_MODE_SGMII_ENA | IF_MODE_USE_SGMII_AN,
+ if_mode);
+ ret = phylink_mii_c22_pcs_config(lynx->mdio, mode,
+ interface, advertising);
+ break;
+
+ case PHY_INTERFACE_MODE_1000BASEX:
+ link_timer = DPAA2_MAC_PCS_CLK_MHZ * LINK_TIMER_US_IEEE8023;
+ mdiobus_write(lynx->mdio->bus, lynx->mdio->addr,
+ MII_LINK_TIMER_1, link_timer & 0xffff);
+ mdiobus_write(lynx->mdio->bus, lynx->mdio->addr,
+ MII_LINK_TIMER_2, link_timer >> 16);
+ mdiobus_write(lynx->mdio->bus, lynx->mdio->addr,
+ MII_IFMODE, 0);
+ ret = phylink_mii_c22_pcs_config(lynx->mdio, mode,
+ interface, advertising);
+ break;
+
+ default:
+ ret = 0;
+ break;
+ }
+
+ return ret;
+}
+
+static void lynx_pcs_link_up(struct phylink_pcs *pcs,
+ unsigned int mode, phy_interface_t interface,
+ int speed, int duplex)
+{
+ struct lynx_pcs *lynx = phylink_pcs_to_lynx(pcs);
+ u16 if_mode;
+
+ /* The PCS PHY needs to be configured manually for the speed and
+ * duplex when operating in SGMII mode without in-band negotiation.
+ */
+ if (mode == MLO_AN_INBAND || interface != PHY_INTERFACE_MODE_SGMII)
+ return;
+
+ switch (speed) {
+ case SPEED_10:
+ if_mode = IF_MODE_SGMII_SPEED_10;
+ break;
+
+ case SPEED_100:
+ if_mode = IF_MODE_SGMII_SPEED_100;
+ break;
+
+ default:
+ if_mode = IF_MODE_SGMII_SPEED_1G;
+ break;
+ }
+ if (duplex == DUPLEX_HALF)
+ if_mode |= IF_MODE_SGMII_DUPLEX;
+
+ mdiobus_modify(lynx->mdio->bus, lynx->mdio->addr, MII_IFMODE,
+ IF_MODE_SGMII_DUPLEX | IF_MODE_SGMII_SPEED_MSK, if_mode);
+}
+
+static const struct phylink_pcs_ops dpaa2_pcs_phylink_ops = {
+ .pcs_get_state = lynx_pcs_get_state,
+ .pcs_config = lynx_pcs_config,
+ .pcs_an_restart = lynx_pcs_an_restart,
+ .pcs_link_up = lynx_pcs_link_up,
+};
+
static int phy_mode(enum dpmac_eth_if eth_if, phy_interface_t *if_mode)
{
*if_mode = PHY_INTERFACE_MODE_NA;
@@ -15,6 +152,11 @@ static int phy_mode(enum dpmac_eth_if eth_if, phy_interface_t *if_mode)
case DPMAC_ETH_IF_RGMII:
*if_mode = PHY_INTERFACE_MODE_RGMII;
break;
+
+ case DPMAC_ETH_IF_SGMII:
+ *if_mode = PHY_INTERFACE_MODE_SGMII;
+ break;
+
default:
return -EINVAL;
}
@@ -67,6 +209,10 @@ static bool dpaa2_mac_phy_mode_mismatch(struct dpaa2_mac *mac,
phy_interface_t interface)
{
switch (interface) {
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_1000BASEX:
+ return interface != mac->if_mode && !mac->pcs;
+
case PHY_INTERFACE_MODE_RGMII:
case PHY_INTERFACE_MODE_RGMII_ID:
case PHY_INTERFACE_MODE_RGMII_RXID:
@@ -95,13 +241,19 @@ static void dpaa2_mac_validate(struct phylink_config *config,
phylink_set(mask, Asym_Pause);
switch (state->interface) {
+ case PHY_INTERFACE_MODE_NA:
+ case PHY_INTERFACE_MODE_1000BASEX:
+ case PHY_INTERFACE_MODE_SGMII:
case PHY_INTERFACE_MODE_RGMII:
case PHY_INTERFACE_MODE_RGMII_ID:
case PHY_INTERFACE_MODE_RGMII_RXID:
case PHY_INTERFACE_MODE_RGMII_TXID:
- phylink_set(mask, 10baseT_Full);
- phylink_set(mask, 100baseT_Full);
+ phylink_set(mask, 1000baseX_Full);
phylink_set(mask, 1000baseT_Full);
+ if (state->interface == PHY_INTERFACE_MODE_1000BASEX)
+ break;
+ phylink_set(mask, 100baseT_Full);
+ phylink_set(mask, 10baseT_Full);
break;
default:
goto empty_set;
@@ -227,6 +379,57 @@ out:
return fixed;
}
+static int dpaa2_pcs_create(struct dpaa2_mac *mac,
+ struct device_node *dpmac_node, int id)
+{
+ struct mdio_device *mdiodev;
+ struct device_node *node;
+ struct lynx_pcs *lynx;
+
+ node = of_parse_phandle(dpmac_node, "pcs-handle", 0);
+ if (!node) {
+ /* allow old DT files to work */
+ netdev_warn(mac->net_dev, "pcs-handle node not found\n");
+ return 0;
+ }
+
+ if (!of_device_is_available(node) ||
+ !of_device_is_available(node->parent)) {
+ netdev_err(mac->net_dev, "pcs-handle node not available\n");
+ return -ENODEV;
+ }
+
+ mdiodev = of_mdio_find_device(node);
+ of_node_put(node);
+ if (!mdiodev)
+ return -EPROBE_DEFER;
+
+ lynx = kmalloc(sizeof(*lynx), GFP_KERNEL);
+ if (!lynx) {
+ put_device(&mdiodev->dev);
+ return -ENOMEM;
+ }
+
+ lynx->mdio = mdiodev;
+ lynx->pcs.ops = &dpaa2_pcs_phylink_ops;
+ lynx->pcs.poll = true;
+
+ mac->pcs = lynx;
+
+ return 0;
+}
+
+static void dpaa2_pcs_destroy(struct dpaa2_mac *mac)
+{
+ struct lynx_pcs *pcs = mac->pcs;
+
+ if (pcs) {
+ mac->pcs = NULL;
+ put_device(&pcs->mdio->dev);
+ kfree(pcs);
+ }
+}
+
int dpaa2_mac_connect(struct dpaa2_mac *mac)
{
struct fsl_mc_device *dpmac_dev = mac->mc_dev;
@@ -236,6 +439,8 @@ int dpaa2_mac_connect(struct dpaa2_mac *mac)
struct dpmac_attr attr;
int err;
+ memset(&mac->phylink_config, 0, sizeof(mac->phylink_config));
+
err = dpmac_open(mac->mc_io, 0, dpmac_dev->obj_desc.id,
&dpmac_dev->mc_handle);
if (err || !dpmac_dev->mc_handle) {
@@ -278,18 +483,34 @@ int dpaa2_mac_connect(struct dpaa2_mac *mac)
goto err_put_node;
}
+ if ((attr.link_type == DPMAC_LINK_TYPE_PHY &&
+ attr.eth_if != DPMAC_ETH_IF_RGMII) ||
+ attr.link_type == DPMAC_LINK_TYPE_BACKPLANE) {
+ err = dpaa2_pcs_create(mac, dpmac_node, attr.id);
+ if (err)
+ goto err_put_node;
+ }
+
mac->phylink_config.dev = &net_dev->dev;
mac->phylink_config.type = PHYLINK_NETDEV;
+ __set_bit(mac->if_mode, mac->phylink_config.supported_interfaces);
+ if (mac->if_mode == PHY_INTERFACE_MODE_SGMII && mac->pcs)
+ __set_bit(PHY_INTERFACE_MODE_1000BASEX,
+ mac->phylink_config.supported_interfaces);
+
phylink = phylink_create(&mac->phylink_config,
of_fwnode_handle(dpmac_node), mac->if_mode,
&dpaa2_mac_phylink_ops);
if (IS_ERR(phylink)) {
err = PTR_ERR(phylink);
- goto err_put_node;
+ goto err_pcs_destroy;
}
mac->phylink = phylink;
+ if (mac->pcs)
+ phylink_set_pcs(mac->phylink, &mac->pcs->pcs);
+
rtnl_lock();
err = phylink_of_phy_connect(mac->phylink, dpmac_node, 0);
rtnl_unlock();
@@ -304,6 +525,8 @@ int dpaa2_mac_connect(struct dpaa2_mac *mac)
err_phylink_destroy:
phylink_destroy(mac->phylink);
+err_pcs_destroy:
+ dpaa2_pcs_destroy(mac);
err_put_node:
of_node_put(dpmac_node);
err_close_dpmac:
@@ -320,6 +543,8 @@ void dpaa2_mac_disconnect(struct dpaa2_mac *mac)
phylink_disconnect_phy(mac->phylink);
rtnl_unlock();
phylink_destroy(mac->phylink);
+ dpaa2_pcs_destroy(mac);
+
dpmac_close(mac->mc_io, 0, mac->mc_dev->mc_handle);
}
diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-mac.h b/drivers/net/ethernet/freescale/dpaa2/dpaa2-mac.h
index 2130d9c7d40e..e6acb357b214 100644
--- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-mac.h
+++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-mac.h
@@ -11,6 +11,8 @@
#include "dpmac.h"
#include "dpmac-cmd.h"
+struct lynx_pcs;
+
struct dpaa2_mac {
struct fsl_mc_device *mc_dev;
struct dpmac_link_state state;
@@ -19,6 +21,7 @@ struct dpaa2_mac {
struct phylink_config phylink_config;
struct phylink *phylink;
+ struct lynx_pcs *pcs;
phy_interface_t if_mode;
enum dpmac_link_type if_link_type;
};