summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRussell King <rmk+kernel@armlinux.org.uk>2017-04-14 14:21:25 +0100
committerRussell King <rmk+kernel@armlinux.org.uk>2019-07-09 11:44:26 +0100
commitfd233fb1c6a1d845e7f981c621e1cd1a431e4832 (patch)
treef9fabe4d568cc0990cc21d76c5de6bf246ed7ab2
parent39b2a9af64e44208d59442e07b79468b7727e602 (diff)
net: phy: marvell10g: add SFP+ support
Add support for SFP+ cages to the Marvell 10G PHY driver. This is slightly complicated by the way phylib works in that we need to use a multi-step process to attach the SFP bus, and we also need to track the phylink state machine to know when the module's transmit disable signal should change state. With appropriate DT changes, this allows the SFP+ canges on the Macchiatobin platform to be functional. Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
-rw-r--r--drivers/net/phy/marvell10g.c96
1 files changed, 95 insertions, 1 deletions
diff --git a/drivers/net/phy/marvell10g.c b/drivers/net/phy/marvell10g.c
index 4e2f59c414d6..a84873d5f788 100644
--- a/drivers/net/phy/marvell10g.c
+++ b/drivers/net/phy/marvell10g.c
@@ -25,7 +25,10 @@
#include <linux/ctype.h>
#include <linux/hwmon.h>
#include <linux/marvell_phy.h>
+#include <linux/netdevice.h>
#include <linux/phy.h>
+#include <linux/property.h>
+#include <linux/sfp.h>
#define MV_PHY_ALASKA_NBT_QUIRK_MASK 0xfffffffe
#define MV_PHY_ALASKA_NBT_QUIRK_REV (MARVELL_PHY_ID_88X3310 | 0xa)
@@ -62,6 +65,10 @@ enum {
};
struct mv3310_priv {
+ struct sfp_bus *sfp_bus;
+ bool sfp_bus_attached;
+ enum phy_state state;
+ bool running;
bool firmware_failed;
struct device *hwmon_dev;
@@ -208,6 +215,49 @@ static int mv3310_hwmon_probe(struct phy_device *phydev)
}
#endif
+static void mv3310_sfp_attach(void *upstream, struct sfp_bus *bus)
+{
+ struct phy_device *phydev = upstream;
+ struct mv3310_priv *priv = dev_get_drvdata(&phydev->mdio.dev);
+
+ if (phydev->attached_dev)
+ phydev->attached_dev->sfp_bus = bus;
+ priv->sfp_bus_attached = true;
+}
+
+static void mv3310_sfp_detach(void *upstream, struct sfp_bus *bus)
+{
+ struct phy_device *phydev = upstream;
+ struct mv3310_priv *priv = dev_get_drvdata(&phydev->mdio.dev);
+
+ if (phydev->attached_dev)
+ phydev->attached_dev->sfp_bus = NULL;
+ priv->sfp_bus_attached = false;
+}
+
+static int mv3310_sfp_insert(void *upstream, const struct sfp_eeprom_id *id)
+{
+ struct phy_device *phydev = upstream;
+ struct mv3310_priv *priv = dev_get_drvdata(&phydev->mdio.dev);
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(support) = { 0, };
+ phy_interface_t iface;
+
+ sfp_parse_support(priv->sfp_bus, id, support);
+ iface = sfp_select_interface(priv->sfp_bus, id, support);
+
+ if (iface != PHY_INTERFACE_MODE_10GKR) {
+ dev_err(&phydev->mdio.dev, "incompatible SFP module inserted\n");
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static const struct sfp_upstream_ops mv3310_sfp_ops = {
+ .attach = mv3310_sfp_attach,
+ .detach = mv3310_sfp_detach,
+ .module_insert = mv3310_sfp_insert,
+};
+
static int mv3310_probe(struct phy_device *phydev)
{
struct mv3310_priv *priv;
@@ -238,9 +288,30 @@ static int mv3310_probe(struct phy_device *phydev)
if (ret)
return ret;
+ if (phydev->mdio.dev.fwnode) {
+ struct fwnode_reference_args ref;
+
+ ret = fwnode_property_get_reference_args(phydev->mdio.dev.fwnode,
+ "sfp", NULL, 0, 0,
+ &ref);
+ if (ret == 0) {
+ priv->sfp_bus = sfp_register_upstream(ref.fwnode,
+ phydev, &mv3310_sfp_ops);
+ fwnode_handle_put(ref.fwnode);
+ }
+ }
+
return 0;
}
+static void mv3310_remove(struct phy_device *phydev)
+{
+ struct mv3310_priv *priv = dev_get_drvdata(&phydev->mdio.dev);
+
+ if (priv->sfp_bus)
+ sfp_unregister_upstream(priv->sfp_bus);
+}
+
static int mv3310_suspend(struct phy_device *phydev)
{
return phy_set_bits_mmd(phydev, MDIO_MMD_VEND2, MV_V2_PORT_CTRL,
@@ -263,12 +334,26 @@ static void mv3310_link_change_notify(struct phy_device *phydev)
{
struct mv3310_priv *priv = dev_get_drvdata(&phydev->mdio.dev);
enum phy_state state = phydev->state;
+ bool running;
if (priv->firmware_failed &&
(state == PHY_UP || state == PHY_RESUMING)) {
dev_warn(&phydev->mdio.dev,
"PHY firmware failure: link forced down");
- phydev->state = PHY_HALTED;
+ phydev->state = state = PHY_HALTED;
+ }
+
+ if (priv->sfp_bus && priv->state != state) {
+ priv->state = state;
+
+ running = state >= PHY_UP && state < PHY_HALTED;
+ if (priv->running != running) {
+ priv->running = running;
+ if (running)
+ sfp_upstream_start(priv->sfp_bus);
+ else
+ sfp_upstream_stop(priv->sfp_bus);
+ }
}
}
@@ -291,6 +376,8 @@ static bool mv3310_has_pma_ngbaset_quirk(struct phy_device *phydev)
static int mv3310_config_init(struct phy_device *phydev)
{
+ struct mv3310_priv *priv = dev_get_drvdata(&phydev->mdio.dev);
+
/* Check that the PHY interface type is compatible */
if (phydev->interface != PHY_INTERFACE_MODE_SGMII &&
phydev->interface != PHY_INTERFACE_MODE_2500BASEX &&
@@ -299,6 +386,9 @@ static int mv3310_config_init(struct phy_device *phydev)
phydev->interface != PHY_INTERFACE_MODE_10GKR)
return -ENODEV;
+ if (priv->sfp_bus_attached)
+ phydev->attached_dev->sfp_bus = priv->sfp_bus;
+
return 0;
}
@@ -505,6 +595,8 @@ static struct phy_driver mv3310_drivers[] = {
.config_aneg = mv3310_config_aneg,
.aneg_done = mv3310_aneg_done,
.read_status = mv3310_read_status,
+ .remove = mv3310_remove,
+ .link_change_notify = mv3310_link_change_notify,
},
{
.phy_id = MARVELL_PHY_ID_88E2110,
@@ -518,6 +610,8 @@ static struct phy_driver mv3310_drivers[] = {
.config_aneg = mv3310_config_aneg,
.aneg_done = mv3310_aneg_done,
.read_status = mv3310_read_status,
+ .remove = mv3310_remove,
+ .link_change_notify = mv3310_link_change_notify,
},
};