Subject: Re: [PATCH] cpuidle: mvebu: indicate failure to enter deeper sleep states

From: Russell King <[email-masked]>
Date: Mon, 15 Aug 2016 18:57:40 +0100

From: Russell King <[email-masked]>

The cpuidle ->enter method expects the return value to be the sleep
state we entered.  Returning negative numbers or other codes is not
permissible since coupled CPU idle was merged.

At least some of the mvebu_v7_cpu_suspend() implementations return the
value from cpu_suspend(), which returns zero if the CPU vectors back
into the kernel via cpu_resume() (the success case), or the non-zero
return value of the suspend actor, or one (failure cases).

We do not want to be returning the failure case value back to CPU idle
as that indicates that we successfully entered one of the deeper idle
states.  Always return zero instead, indicating that we slept for the
shortest amount of time.

Signed-off-by: Russell King <[email-masked]>
---
 drivers/cpuidle/cpuidle-mvebu-v7.c | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/drivers/cpuidle/cpuidle-mvebu-v7.c b/drivers/cpuidle/cpuidle-mvebu-v7.c
index 01a856971f05..18ded9e7cb34 100644
--- a/drivers/cpuidle/cpuidle-mvebu-v7.c
+++ b/drivers/cpuidle/cpuidle-mvebu-v7.c
@@ -39,8 +39,12 @@ static int mvebu_v7_enter_idle(struct cpuidle_device *dev,
 	ret = mvebu_v7_cpu_suspend(deepidle);
 	cpu_pm_exit();
 
+	/*
+	 * If we failed to enter the desired state, indicate that we
+	 * slept lightly.
+	 */
 	if (ret)
-		return ret;
+		return 0;
 
 	return index;
 }

---

From: Russell King <[email-masked]>
Date: Mon, 15 Aug 2016 18:57:40 +0100

Sometimes, we need to do additional work between the PHY coming up and
marking the carrier present - for example, we may need to wait for the
PHY to MAC link to finish negotiation.  This changes phylib to provide
a notification function pointer which avoids the built-in
netif_carrier_on() and netif_carrier_off() functions.

Standard ->adjust_link functionality is provided by hooking a helper
into the new ->phy_link_change method.

Reviewed-by: Florian Fainelli <[email-masked]>
Signed-off-by: Russell King <[email-masked]>
---
 drivers/net/phy/phy.c        | 42 ++++++++++++++++++++++--------------------
 drivers/net/phy/phy_device.c | 14 ++++++++++++++
 include/linux/phy.h          |  1 +
 3 files changed, 37 insertions(+), 20 deletions(-)

diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c
index c5dc2c363f96..7a2249e151a5 100644
--- a/drivers/net/phy/phy.c
+++ b/drivers/net/phy/phy.c
@@ -891,6 +891,16 @@ void phy_start(struct phy_device *phydev)
 }
 EXPORT_SYMBOL(phy_start);
 
+static void phy_link_up(struct phy_device *phydev)
+{
+	phydev->phy_link_change(phydev, true, true);
+}
+
+static void phy_link_down(struct phy_device *phydev, bool do_carrier)
+{
+	phydev->phy_link_change(phydev, false, do_carrier);
+}
+
 /**
  * phy_state_machine - Handle the state machine
  * @work: work_struct that describes the work to be done
@@ -932,8 +942,7 @@ void phy_state_machine(struct work_struct *work)
 		/* If the link is down, give up on negotiation for now */
 		if (!phydev->link) {
 			phydev->state = PHY_NOLINK;
-			netif_carrier_off(phydev->attached_dev);
-			phydev->adjust_link(phydev->attached_dev);
+			phy_link_down(phydev, true);
 			break;
 		}
 
@@ -945,9 +954,7 @@ void phy_state_machine(struct work_struct *work)
 		/* If AN is done, we're running */
 		if (err > 0) {
 			phydev->state = PHY_RUNNING;
-			netif_carrier_on(phydev->attached_dev);
-			phydev->adjust_link(phydev->attached_dev);
-
+			phy_link_up(phydev);
 		} else if (0 == phydev->link_timeout--)
 			needs_aneg = true;
 		break;
@@ -972,8 +979,7 @@ void phy_state_machine(struct work_struct *work)
 				}
 			}
 			phydev->state = PHY_RUNNING;
-			netif_carrier_on(phydev->attached_dev);
-			phydev->adjust_link(phydev->attached_dev);
+			phy_link_up(phydev);
 		}
 		break;
 	case PHY_FORCING:
@@ -983,13 +989,12 @@ void phy_state_machine(struct work_struct *work)
 
 		if (phydev->link) {
 			phydev->state = PHY_RUNNING;
-			netif_carrier_on(phydev->attached_dev);
+			phy_link_up(phydev);
 		} else {
 			if (0 == phydev->link_timeout--)
 				needs_aneg = true;
+			phy_link_down(phydev, false);
 		}
-
-		phydev->adjust_link(phydev->attached_dev);
 		break;
 	case PHY_RUNNING:
 		/* Only register a CHANGE if we are polling and link changed
@@ -1012,14 +1017,12 @@ void phy_state_machine(struct work_struct *work)
 
 		if (phydev->link) {
 			phydev->state = PHY_RUNNING;
-			netif_carrier_on(phydev->attached_dev);
+			phy_link_up(phydev);
 		} else {
 			phydev->state = PHY_NOLINK;
-			netif_carrier_off(phydev->attached_dev);
+			phy_link_down(phydev, true);
 		}
 
-		phydev->adjust_link(phydev->attached_dev);
-
 		if (phy_interrupt_is_valid(phydev))
 			err = phy_config_interrupt(phydev,
 						   PHY_INTERRUPT_ENABLED);
@@ -1027,8 +1030,7 @@ void phy_state_machine(struct work_struct *work)
 	case PHY_HALTED:
 		if (phydev->link) {
 			phydev->link = 0;
-			netif_carrier_off(phydev->attached_dev);
-			phydev->adjust_link(phydev->attached_dev);
+			phy_link_down(phydev, true);
 			do_suspend = true;
 		}
 		break;
@@ -1048,11 +1050,11 @@ void phy_state_machine(struct work_struct *work)
 
 				if (phydev->link) {
 					phydev->state = PHY_RUNNING;
-					netif_carrier_on(phydev->attached_dev);
+					phy_link_up(phydev);
 				} else	{
 					phydev->state = PHY_NOLINK;
+					phy_link_down(phydev, false);
 				}
-				phydev->adjust_link(phydev->attached_dev);
 			} else {
 				phydev->state = PHY_AN;
 				phydev->link_timeout = PHY_AN_TIMEOUT;
@@ -1064,11 +1066,11 @@ void phy_state_machine(struct work_struct *work)
 
 			if (phydev->link) {
 				phydev->state = PHY_RUNNING;
-				netif_carrier_on(phydev->attached_dev);
+				phy_link_up(phydev);
 			} else	{
 				phydev->state = PHY_NOLINK;
+				phy_link_down(phydev, false);
 			}
-			phydev->adjust_link(phydev->attached_dev);
 		}
 		break;
 	}
diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
index e977ba931878..da181066020d 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -645,6 +645,19 @@ struct phy_device *phy_find_first(struct mii_bus *bus)
 }
 EXPORT_SYMBOL(phy_find_first);
 
+static void phy_link_change(struct phy_device *phydev, bool up, bool do_carrier)
+{
+	struct net_device *netdev = phydev->attached_dev;
+
+	if (do_carrier) {
+		if (up)
+			netif_carrier_on(netdev);
+		else
+			netif_carrier_off(netdev);
+	}
+	phydev->adjust_link(netdev);
+}
+
 /**
  * phy_prepare_link - prepares the PHY layer to monitor link status
  * @phydev: target phy_device struct
@@ -892,6 +905,7 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev,
 		goto error;
 	}
 
+	phydev->phy_link_change = phy_link_change;
 	phydev->attached_dev = dev;
 	dev->phydev = phydev;
 
diff --git a/include/linux/phy.h b/include/linux/phy.h
index 2d24b283aa2d..4674417bd352 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -423,6 +423,7 @@ struct phy_device {
 
 	u8 mdix;
 
+	void (*phy_link_change)(struct phy_device *, bool up, bool do_carrier);
 	void (*adjust_link)(struct net_device *dev);
 };
 #define to_phy_device(d) container_of(to_mdio_device(d), \

---

From: Russell King <[email-masked]>
Date: Mon, 15 Aug 2016 18:57:40 +0100

phylink will need phy_start_machine exported, so lets export it as a
GPL symbol.  Documentation/networking/phy.txt indicates that this
should be a PHY API function.

Reviewed-by: Florian Fainelli <[email-masked]>
Signed-off-by: Russell King <[email-masked]>
---
 drivers/net/phy/phy.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c
index 7a2249e151a5..c25b414b702d 100644
--- a/drivers/net/phy/phy.c
+++ b/drivers/net/phy/phy.c
@@ -606,6 +606,7 @@ void phy_start_machine(struct phy_device *phydev)
 {
 	queue_delayed_work(system_power_efficient_wq, &phydev->state_queue, HZ);
 }
+EXPORT_SYMBOL_GPL(phy_start_machine);
 
 /**
  * phy_stop_machine - stop the PHY state machine tracking

---

From: Russell King <[email-masked]>
Date: Mon, 15 Aug 2016 18:57:40 +0100

phylink would like to reuse phy_speed_to_str() to convert the speed
to a string.  Add a prototype and export this helper function.

Reviewed-by: Florian Fainelli <[email-masked]>
Signed-off-by: Russell King <[email-masked]>
---
 drivers/net/phy/phy.c | 3 ++-
 include/linux/phy.h   | 1 +
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c
index c25b414b702d..45d812ba7270 100644
--- a/drivers/net/phy/phy.c
+++ b/drivers/net/phy/phy.c
@@ -38,7 +38,7 @@
 
 #include <asm/irq.h>
 
-static const char *phy_speed_to_str(int speed)
+const char *phy_speed_to_str(int speed)
 {
 	switch (speed) {
 	case SPEED_10:
@@ -57,6 +57,7 @@ static const char *phy_speed_to_str(int speed)
 		return "Unsupported (update phy.c)";
 	}
 }
+EXPORT_SYMBOL_GPL(phy_speed_to_str);
 
 #define PHY_STATE_STR(_state)			\
 	case PHY_##_state:			\
diff --git a/include/linux/phy.h b/include/linux/phy.h
index 4674417bd352..0f73a68067d1 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -812,6 +812,7 @@ int phy_ethtool_ksettings_set(struct phy_device *phydev,
 			      const struct ethtool_link_ksettings *cmd);
 int phy_mii_ioctl(struct phy_device *phydev, struct ifreq *ifr, int cmd);
 int phy_start_interrupts(struct phy_device *phydev);
+const char *phy_speed_to_str(int speed);
 void phy_print_status(struct phy_device *phydev);
 void phy_device_free(struct phy_device *phydev);
 int phy_set_max_speed(struct phy_device *phydev, u32 max_speed);

---

From: Russell King <[email-masked]>
Date: Mon, 15 Aug 2016 18:57:40 +0100

Add an I2C MDIO bus bridge library, to allow phylib to access PHYs which
are connected to an I2C bus instead of the more conventional MDIO bus.
Such PHYs can be found in SFP adapters and SFF modules.

Since PHYs appear at I2C bus address 0x40..0x5f, and 0x50/0x51 are
reserved for SFP EEPROMs/diagnostics, we must not allow the MDIO bus
to access these I2C addresses.

Signed-off-by: Russell King <[email-masked]>
---
 drivers/net/phy/Kconfig    |  10 +++++
 drivers/net/phy/Makefile   |   1 +
 drivers/net/phy/mdio-i2c.c | 109 +++++++++++++++++++++++++++++++++++++++++++++
 drivers/net/phy/mdio-i2c.h |  19 ++++++++
 4 files changed, 139 insertions(+)
 create mode 100644 drivers/net/phy/mdio-i2c.c
 create mode 100644 drivers/net/phy/mdio-i2c.h

diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index 47a64342cc16..caf16b9a2559 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -190,6 +190,16 @@ config MDIO_GPIO
 config MDIO_CAVIUM
 	tristate
 
+config MDIO_I2C
+	tristate
+	depends on I2C
+	help
+	  Support I2C based PHYs.  This provides a MDIO bus bridged
+	  to I2C to allow PHYs connected in I2C mode to be accessed
+	  using the existing infrastructure.
+
+	  This is library mode.
+
 config MDIO_OCTEON
 	tristate "Support for MDIO buses on Octeon and some ThunderX SOCs"
 	depends on 64BIT
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index 534dfa74d5a2..d03ac62b76ee 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -25,6 +25,7 @@ obj-$(CONFIG_LSI_ET1011C_PHY)	+= et1011c.o
 obj-$(CONFIG_FIXED_PHY)		+= fixed_phy.o
 obj-$(CONFIG_MDIO_BITBANG)	+= mdio-bitbang.o
 obj-$(CONFIG_MDIO_GPIO)		+= mdio-gpio.o
+obj-$(CONFIG_MDIO_I2C)		+= mdio-i2c.o
 obj-$(CONFIG_NATIONAL_PHY)	+= national.o
 obj-$(CONFIG_DP83640_PHY)	+= dp83640.o
 obj-$(CONFIG_DP83848_PHY)	+= dp83848.o
diff --git a/drivers/net/phy/mdio-i2c.c b/drivers/net/phy/mdio-i2c.c
new file mode 100644
index 000000000000..6d24fd13ca86
--- /dev/null
+++ b/drivers/net/phy/mdio-i2c.c
@@ -0,0 +1,109 @@
+/*
+ * MDIO I2C bridge
+ *
+ * Copyright (C) 2015-2016 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Network PHYs can appear on I2C buses when they are part of SFP module.
+ * This driver exposes these PHYs to the networking PHY code, allowing
+ * our PHY drivers access to these PHYs, and so allowing configuration
+ * of their settings.
+ */
+#include <linux/i2c.h>
+#include <linux/phy.h>
+
+#include "mdio-i2c.h"
+
+/*
+ * I2C bus addresses 0x50 and 0x51 are normally an EEPROM, which is
+ * specified to be present in SFP modules.  These correspond with PHY
+ * addresses 16 and 17.  Disallow access to these "phy" addresses.
+ */
+static bool i2c_mii_valid_phy_id(int phy_id)
+{
+	return phy_id != 0x10 && phy_id != 0x11;
+}
+
+static unsigned int i2c_mii_phy_addr(int phy_id)
+{
+	return phy_id + 0x40;
+}
+
+static int i2c_mii_read(struct mii_bus *bus, int phy_id, int reg)
+{
+	struct i2c_adapter *i2c = bus->priv;
+	struct i2c_msg msgs[2];
+	u8 data[2], dev_addr = reg;
+	int bus_addr, ret;
+
+	if (!i2c_mii_valid_phy_id(phy_id))
+		return 0xffff;
+
+	bus_addr = i2c_mii_phy_addr(phy_id);
+	msgs[0].addr = bus_addr;
+	msgs[0].flags = 0;
+	msgs[0].len = 1;
+	msgs[0].buf = &dev_addr;
+	msgs[1].addr = bus_addr;
+	msgs[1].flags = I2C_M_RD;
+	msgs[1].len = sizeof(data);
+	msgs[1].buf = data;
+
+	ret = i2c_transfer(i2c, msgs, ARRAY_SIZE(msgs));
+	if (ret != ARRAY_SIZE(msgs))
+		return 0xffff;
+
+	return data[0] << 8 | data[1];
+}
+
+static int i2c_mii_write(struct mii_bus *bus, int phy_id, int reg, u16 val)
+{
+	struct i2c_adapter *i2c = bus->priv;
+	struct i2c_msg msg;
+	int ret;
+	u8 data[3];
+
+	if (!i2c_mii_valid_phy_id(phy_id))
+		return 0;
+
+	data[0] = reg;
+	data[1] = val >> 8;
+	data[2] = val;
+
+	msg.addr = i2c_mii_phy_addr(phy_id);
+	msg.flags = 0;
+	msg.len = 3;
+	msg.buf = data;
+
+	ret = i2c_transfer(i2c, &msg, 1);
+
+	return ret < 0 ? ret : 0;
+}
+
+struct mii_bus *mdio_i2c_alloc(struct device *parent, struct i2c_adapter *i2c)
+{
+	struct mii_bus *mii;
+
+	if (!i2c_check_functionality(i2c, I2C_FUNC_I2C))
+		return ERR_PTR(-EINVAL);
+
+	mii = mdiobus_alloc();
+	if (!mii)
+		return ERR_PTR(-ENOMEM);
+
+	snprintf(mii->id, MII_BUS_ID_SIZE, "i2c:%s", dev_name(parent));
+	mii->parent = parent;
+	mii->read = i2c_mii_read;
+	mii->write = i2c_mii_write;
+	mii->priv = i2c;
+
+	return mii;
+}
+EXPORT_SYMBOL_GPL(mdio_i2c_alloc);
+
+MODULE_AUTHOR("Russell King");
+MODULE_DESCRIPTION("MDIO I2C bridge library");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/phy/mdio-i2c.h b/drivers/net/phy/mdio-i2c.h
new file mode 100644
index 000000000000..889ab57d7f3e
--- /dev/null
+++ b/drivers/net/phy/mdio-i2c.h
@@ -0,0 +1,19 @@
+/*
+ * MDIO I2C bridge
+ *
+ * Copyright (C) 2015 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef MDIO_I2C_H
+#define MDIO_I2C_H
+
+struct device;
+struct i2c_adapter;
+struct mii_bus;
+
+struct mii_bus *mdio_i2c_alloc(struct device *parent, struct i2c_adapter *i2c);
+
+#endif

---

From: Russell King <[email-masked]>
Date: Mon, 15 Aug 2016 18:57:41 +0100

The link between the ethernet MAC and its PHY has become more complex
as the interface evolves.  This is especially true with serdes links,
where the part of the PHY is effectively integrated into the MAC.

Serdes links can be connected to a variety of devices, including SFF
modules soldered down onto the board with the MAC, a SFP cage with
a hotpluggable SFP module which may contain a PHY or directly modulate
the serdes signals onto optical media with or without a PHY, or even
a classical PHY connection.

Moreover, the negotiation information on serdes links comes in two
varieties - SGMII mode, where the PHY provides its speed/duplex/flow
control information to the MAC, and 1000base-X mode where both ends
exchange their abilities and each resolve the link capabilities.

This means we need a more flexible means to support these arrangements,
particularly with the hotpluggable nature of SFP, where the PHY can
be attached or detached after the network device has been brought up.

Ethtool information can come from multiple sources:
- we may have a PHY operating in either SGMII or 1000base-X mode, in
  which case we take ethtool/mii data directly from the PHY.
- we may have a optical SFP module without a PHY, with the MAC
  operating in 1000base-X mode - the ethtool/mii data needs to come
  from the MAC.
- we may have a copper SFP module with a PHY whic can't be accessed,
  which means we need to take ethtool/mii data from the MAC.

Phylink aims to solve this by providing an intermediary between the
MAC and PHY, providing a safe way for PHYs to be hotplugged, and
allowing a SFP driver to reconfigure the serdes connection.

Phylink also takes over support of fixed link connections, where
the speed/duplex/flow control are fixed, but link status may be
controlled by a GPIO signal.  By avoiding the fixed-phy implementation,
phylink can provide a faster response to link events: fixed-phy has
to wait for phylib to operate its state machine, which can take
several seconds.  In comparison, phylink takes milliseconds.

Signed-off-by: Russell King <[email-masked]>
---
 drivers/net/phy/Kconfig      |  10 +
 drivers/net/phy/Makefile     |   1 +
 drivers/net/phy/phy_device.c |   1 +
 drivers/net/phy/phylink.c    | 804 +++++++++++++++++++++++++++++++++++++++++++
 include/linux/phy.h          |   2 +
 include/linux/phylink.h      |  70 ++++
 6 files changed, 888 insertions(+)
 create mode 100644 drivers/net/phy/phylink.c
 create mode 100644 include/linux/phylink.h

diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index caf16b9a2559..fc0c9eb543e5 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -10,6 +10,16 @@ menuconfig PHYLIB
 	  devices.  This option provides infrastructure for
 	  managing PHY devices.
 
+config PHYLINK
+	tristate
+	depends on NETDEVICES
+	select PHYLIB
+	select SWPHY
+	help
+	  PHYlink models the link between the PHY and MAC, allowing fixed
+	  configuration links, PHYs, and Serdes links with MAC level
+	  autonegotiation modes.
+
 if PHYLIB
 
 config SWPHY
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index d03ac62b76ee..5ba8a48a4bcc 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -3,6 +3,7 @@
 libphy-y			:= phy.o phy_device.o mdio_bus.o mdio_device.o
 libphy-$(CONFIG_SWPHY)		+= swphy.o
 
+obj-$(CONFIG_PHYLINK)		+= phylink.o
 obj-$(CONFIG_PHYLIB)		+= libphy.o
 obj-$(CONFIG_AQUANTIA_PHY)	+= aquantia.o
 obj-$(CONFIG_MARVELL_PHY)	+= marvell.o
diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
index da181066020d..1877889e0439 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -989,6 +989,7 @@ void phy_detach(struct phy_device *phydev)
 	phydev->attached_dev->phydev = NULL;
 	phydev->attached_dev = NULL;
 	phy_suspend(phydev);
+	phydev->phylink = NULL;
 
 	/* If the device had no specific driver before (i.e. - it
 	 * was using the generic driver), we unbind the device
diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
new file mode 100644
index 000000000000..1d4ddba0d239
--- /dev/null
+++ b/drivers/net/phy/phylink.c
@@ -0,0 +1,804 @@
+/*
+ * phylink models the MAC to optional PHY connection, supporting
+ * technologies such as SFP cages where the PHY is hot-pluggable.
+ *
+ * Copyright (C) 2015 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/ethtool.h>
+#include <linux/export.h>
+#include <linux/gpio/consumer.h>
+#include <linux/netdevice.h>
+#include <linux/of.h>
+#include <linux/of_mdio.h>
+#include <linux/phy.h>
+#include <linux/phy_fixed.h>
+#include <linux/phylink.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+
+#include "swphy.h"
+
+#define SUPPORTED_INTERFACES \
+	(SUPPORTED_TP | SUPPORTED_MII | SUPPORTED_FIBRE | \
+	 SUPPORTED_BNC | SUPPORTED_AUI | SUPPORTED_Backplane)
+#define ADVERTISED_INTERFACES \
+	(ADVERTISED_TP | ADVERTISED_MII | ADVERTISED_FIBRE | \
+	 ADVERTISED_BNC | ADVERTISED_AUI | ADVERTISED_Backplane)
+
+enum {
+	PHYLINK_DISABLE_STOPPED,
+};
+
+struct phylink {
+	struct net_device *netdev;
+	const struct phylink_mac_ops *ops;
+	struct mutex config_mutex;
+
+	unsigned long phylink_disable_state; /* bitmask of disables */
+	struct phy_device *phydev;
+	phy_interface_t link_interface;	/* PHY_INTERFACE_xxx */
+	u8 link_an_mode;		/* MLO_AN_xxx */
+	u8 link_port;			/* The current non-phy ethtool port */
+	u32 link_port_support;		/* SUPPORTED_xxx ethtool for ports */
+
+	/* The link configuration settings */
+	struct phylink_link_state link_config;
+	struct gpio_desc *link_gpio;
+
+	struct mutex state_mutex;	/* may be taken within config_mutex */
+	struct phylink_link_state phy_state;
+	struct work_struct resolve;
+
+	bool mac_link_up;
+};
+
+static const char *phylink_an_mode_str(unsigned int mode)
+{
+	static const char *modestr[] = {
+		[MLO_AN_PHY] = "phy",
+		[MLO_AN_FIXED] = "fixed",
+		[MLO_AN_SGMII] = "SGMII",
+		[MLO_AN_8023Z] = "802.3z",
+	};
+
+	return mode < ARRAY_SIZE(modestr) ? modestr[mode] : "unknown";
+}
+
+static int phylink_parse_fixedlink(struct phylink *pl, struct device_node *np)
+{
+	struct device_node *fixed_node;
+	int ret, len;
+
+	fixed_node = of_get_child_by_name(np, "fixed-link");
+	if (fixed_node) {
+		struct gpio_desc *desc;
+		u32 speed;
+
+		ret = of_property_read_u32(fixed_node, "speed", &speed);
+
+		pl->link_an_mode = MLO_AN_FIXED;
+		pl->link_config.link = 1;
+		pl->link_config.an_complete = 1;
+		pl->link_config.speed = speed;
+		pl->link_config.duplex = DUPLEX_HALF;
+
+		if (of_property_read_bool(fixed_node, "full-duplex"))
+			pl->link_config.duplex = DUPLEX_FULL;
+		if (of_property_read_bool(fixed_node, "pause"))
+			pl->link_config.pause |= MLO_PAUSE_SYM;
+		if (of_property_read_bool(fixed_node, "asym-pause"))
+			pl->link_config.pause |= MLO_PAUSE_ASYM;
+
+		if (ret == 0) {
+			desc = fwnode_get_named_gpiod(&fixed_node->fwnode,
+						      "link-gpios");
+
+			if (!IS_ERR(desc))
+				pl->link_gpio = desc;
+			else if (desc == ERR_PTR(-EPROBE_DEFER))
+				ret = -EPROBE_DEFER;
+		}
+		of_node_put(fixed_node);
+	} else {
+		const __be32 *fixed_prop;
+
+		fixed_prop = of_get_property(np, "fixed-link", &len);
+		if (fixed_prop && len == 5 * sizeof(*fixed_prop)) {
+			pl->link_config.duplex = be32_to_cpu(fixed_prop[1]) ?
+						DUPLEX_FULL : DUPLEX_HALF;
+			pl->link_config.speed = be32_to_cpu(fixed_prop[2]);
+			if (be32_to_cpu(fixed_prop[3]))
+				pl->link_config.pause |= MLO_PAUSE_SYM;
+			if (be32_to_cpu(fixed_prop[4]))
+				pl->link_config.pause |= MLO_PAUSE_ASYM;
+
+			pl->link_an_mode = MLO_AN_FIXED;
+		}
+		ret = 0;
+	}
+
+	if (pl->link_an_mode == MLO_AN_FIXED) {
+		if (pl->link_config.speed > SPEED_1000 &&
+		    pl->link_config.duplex != DUPLEX_FULL)
+			netdev_warn(pl->netdev, "fixed link specifies half duplex for %dMbps link?\n",
+				    pl->link_config.speed);
+
+#define S(spd) \
+	pl->link_config.supported |= pl->link_config.duplex ? \
+		SUPPORTED_##spd##_Full : SUPPORTED_##spd##_Half
+#define A(spd) \
+	pl->link_config.advertising |= pl->link_config.duplex ? \
+		ADVERTISED_##spd##_Full : ADVERTISED_##spd##_Half
+#define C(spd, tech) \
+		case spd: \
+			S(spd##tech); \
+			A(spd##tech); \
+			break
+		switch (pl->link_config.speed) {
+		C(10, baseT);
+		C(100, baseT);
+		C(1000, baseT);
+#undef S
+#undef A
+#define S(spd) pl->link_config.supported |= SUPPORTED_##spd##_Full
+#define A(spd) pl->link_config.advertising |= ADVERTISED_##spd##_Full
+		C(2500, baseX);
+		C(10000, baseT);
+		}
+#undef S
+#undef A
+#undef C
+	}
+	return ret;
+}
+
+static int phylink_parse_managed(struct phylink *pl, struct device_node *np)
+{
+	const char *managed;
+
+	if (of_property_read_string(np, "managed", &managed) == 0 &&
+	    strcmp(managed, "in-band-status") == 0) {
+		if (pl->link_an_mode == MLO_AN_FIXED) {
+			netdev_err(pl->netdev,
+				   "can't use both fixed-link and in-band-status\n");
+			return -EINVAL;
+		}
+		pl->link_an_mode = MLO_AN_SGMII;
+		pl->link_config.an_enabled = true;
+	}
+
+	return 0;
+}
+
+
+static int phylink_get_support(struct phylink *pl, unsigned int mode)
+{
+	struct phylink_link_state state = pl->link_config;
+	int ret;
+
+	ret = pl->ops->mac_get_support(pl->netdev, mode, &state);
+	if (ret == 0) {
+		pl->link_an_mode = mode;
+		pl->link_config = state;
+	}
+
+	return ret;
+}
+
+static void phylink_mac_config(struct phylink *pl,
+			       const struct phylink_link_state *state)
+{
+	pl->ops->mac_config(pl->netdev, pl->link_an_mode, state);
+}
+
+static void phylink_mac_an_restart(struct phylink *pl)
+{
+	if (pl->link_config.an_enabled)
+		pl->ops->mac_an_restart(pl->netdev, pl->link_an_mode);
+}
+
+static int phylink_get_mac_state(struct phylink *pl, struct phylink_link_state *state)
+{
+	struct net_device *ndev = pl->netdev;
+
+	state->supported = pl->link_config.supported;
+	state->advertising = pl->link_config.advertising;
+	state->an_enabled = pl->link_config.an_enabled;
+	state->link = 1;
+	state->sync = 1;
+
+	return pl->ops->mac_link_state(ndev, state);
+}
+
+/* The fixed state is... fixed except for the link state,
+ * which may be determined by a GPIO.
+ */
+static void phylink_get_fixed_state(struct phylink *pl, struct phylink_link_state *state)
+{
+	*state = pl->link_config;
+	if (pl->link_gpio)
+		state->link = !!gpiod_get_value(pl->link_gpio);
+}
+
+extern const char *phy_speed_to_str(int speed);
+
+static void phylink_resolve(struct work_struct *w)
+{
+	struct phylink *pl = container_of(w, struct phylink, resolve);
+	struct phylink_link_state link_state;
+	struct net_device *ndev = pl->netdev;
+
+	mutex_lock(&pl->state_mutex);
+	if (pl->phylink_disable_state) {
+		link_state.link = false;
+	} else {
+		switch (pl->link_an_mode) {
+		case MLO_AN_PHY:
+			link_state = pl->phy_state;
+			break;
+
+		case MLO_AN_FIXED:
+			phylink_get_fixed_state(pl, &link_state);
+			break;
+
+		case MLO_AN_SGMII:
+			phylink_get_mac_state(pl, &link_state);
+			if (pl->phydev)
+				link_state.link = link_state.link &&
+						  pl->phy_state.link;
+			break;
+
+		case MLO_AN_8023Z:
+			phylink_get_mac_state(pl, &link_state);
+			break;
+		}
+	}
+
+	if (link_state.link != netif_carrier_ok(ndev)) {
+		if (!link_state.link) {
+			netif_carrier_off(ndev);
+			pl->ops->mac_link_down(ndev, pl->link_an_mode);
+			netdev_info(ndev, "Link is Down\n");
+		} else {
+			/* If we have a PHY, we need the MAC updated with
+			 * the current link parameters (eg, in SGMII mode,
+			 * with flow control status.)
+			 */
+			if (pl->phydev)
+				phylink_mac_config(pl, &link_state);
+
+			pl->ops->mac_link_up(ndev, pl->link_an_mode);
+
+			netif_carrier_on(ndev);
+
+			netdev_info(ndev,
+				    "Link is Up - %s/%s - flow control %s\n",
+				    phy_speed_to_str(link_state.speed),
+				    link_state.duplex ? "Full" : "Half",
+				    link_state.pause ? "rx/tx" : "off");
+		}
+	}
+	mutex_unlock(&pl->state_mutex);
+}
+
+static void phylink_run_resolve(struct phylink *pl)
+{
+	if (!pl->phylink_disable_state)
+		queue_work(system_power_efficient_wq, &pl->resolve);
+}
+
+struct phylink *phylink_create(struct net_device *ndev, struct device_node *np,
+	phy_interface_t iface, const struct phylink_mac_ops *ops)
+{
+	struct phylink *pl;
+	int ret;
+
+	pl = kzalloc(sizeof(*pl), GFP_KERNEL);
+	if (!pl)
+		return ERR_PTR(-ENOMEM);
+
+	mutex_init(&pl->state_mutex);
+	mutex_init(&pl->config_mutex);
+	INIT_WORK(&pl->resolve, phylink_resolve);
+	pl->netdev = ndev;
+	pl->link_interface = iface;
+	pl->link_port_support = SUPPORTED_MII;
+	pl->link_port = PORT_MII;
+	pl->ops = ops;
+	__set_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state);
+
+	ret = phylink_parse_fixedlink(pl, np);
+	if (ret < 0) {
+		kfree(pl);
+		return ERR_PTR(ret);
+	}
+
+	ret = phylink_parse_managed(pl, np);
+	if (ret < 0) {
+		kfree(pl);
+		return ERR_PTR(ret);
+	}
+
+	ret = phylink_get_support(pl, pl->link_an_mode);
+	if (ret) {
+		kfree(pl);
+		return ERR_PTR(ret);
+	}
+
+	return pl;
+}
+EXPORT_SYMBOL_GPL(phylink_create);
+
+void phylink_destroy(struct phylink *pl)
+{
+	cancel_work_sync(&pl->resolve);
+	kfree(pl);
+}
+EXPORT_SYMBOL_GPL(phylink_destroy);
+
+void phylink_phy_change(struct phy_device *phy, bool up, bool do_carrier)
+{
+	struct phylink *pl = phy->phylink;
+
+	mutex_lock(&pl->state_mutex);
+	pl->phy_state.speed = phy->speed;
+	pl->phy_state.duplex = phy->duplex;
+	pl->phy_state.pause = MLO_PAUSE_NONE;
+	if (phy->pause)
+		pl->phy_state.pause |= MLO_PAUSE_SYM;
+	if (phy->asym_pause)
+		pl->phy_state.pause |= MLO_PAUSE_ASYM;
+	pl->phy_state.link = up;
+	mutex_unlock(&pl->state_mutex);
+
+	phylink_run_resolve(pl);
+
+	netdev_dbg(pl->netdev, "phy link %s\n", up ? "up" : "down");
+}
+
+static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy)
+{
+	mutex_lock(&pl->config_mutex);
+	phy->phylink = pl;
+	phy->phy_link_change = phylink_phy_change;
+
+	netdev_info(pl->netdev,
+		    "PHY [%s] driver [%s]\n", dev_name(&phy->mdio.dev),
+		    phy->drv->name);
+
+	mutex_lock(&pl->state_mutex);
+	pl->phydev = phy;
+
+	/* Restrict the phy advertisment to the union of the PHY and
+	 * MAC-level advert.
+	 */
+	phy->advertising &= ADVERTISED_INTERFACES |
+			    pl->link_config.advertising;
+	mutex_unlock(&pl->state_mutex);
+
+	phy_start_machine(phy);
+	if (phy->irq > 0)
+		phy_start_interrupts(phy);
+
+	mutex_unlock(&pl->config_mutex);
+
+	return 0;
+}
+
+int phylink_connect_phy(struct phylink *pl, struct phy_device *phy)
+{
+	int ret;
+
+	ret = phy_attach_direct(pl->netdev, phy, 0, pl->link_interface);
+	if (ret)
+		return ret;
+
+	ret = phylink_bringup_phy(pl, phy);
+	if (ret)
+		phy_detach(phy);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(phylink_connect_phy);
+
+int phylink_of_phy_connect(struct phylink *pl, struct device_node *dn)
+{
+	struct device_node *phy_node;
+	struct phy_device *phy_dev;
+	int ret;
+
+	/* Fixed links are handled without needing a PHY */
+	if (pl->link_an_mode == MLO_AN_FIXED)
+		return 0;
+
+	phy_node = of_parse_phandle(dn, "phy-handle", 0);
+	if (!phy_node)
+		phy_node = of_parse_phandle(dn, "phy", 0);
+	if (!phy_node)
+		phy_node = of_parse_phandle(dn, "phy-device", 0);
+
+	if (!phy_node) {
+		if (pl->link_an_mode == MLO_AN_PHY) {
+			netdev_err(pl->netdev, "unable to find PHY node\n");
+			return -ENODEV;
+		}
+		return 0;
+	}
+
+	phy_dev = of_phy_attach(pl->netdev, phy_node, 0, pl->link_interface);
+	/* We're done with the phy_node handle */
+	of_node_put(phy_node);
+
+	if (!phy_dev)
+		return -ENODEV;
+
+	ret = phylink_bringup_phy(pl, phy_dev);
+	if (ret)
+		phy_detach(phy_dev);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(phylink_of_phy_connect);
+
+void phylink_disconnect_phy(struct phylink *pl)
+{
+	struct phy_device *phy;
+
+	mutex_lock(&pl->config_mutex);
+	phy = pl->phydev;
+
+	mutex_lock(&pl->state_mutex);
+	pl->phydev = NULL;
+	mutex_unlock(&pl->state_mutex);
+	flush_work(&pl->resolve);
+
+	if (phy)
+		phy_disconnect(phy);
+
+	mutex_unlock(&pl->config_mutex);
+}
+EXPORT_SYMBOL_GPL(phylink_disconnect_phy);
+
+void phylink_mac_change(struct phylink *pl, bool up)
+{
+	phylink_run_resolve(pl);
+	netdev_dbg(pl->netdev, "mac link %s\n", up ? "up" : "down");
+}
+EXPORT_SYMBOL_GPL(phylink_mac_change);
+
+void phylink_start(struct phylink *pl)
+{
+	mutex_lock(&pl->config_mutex);
+
+	netdev_info(pl->netdev, "configuring for %s link mode\n",
+		    phylink_an_mode_str(pl->link_an_mode));
+
+	/* Apply the link configuration to the MAC when starting. This allows
+	 * a fixed-link to start with the correct parameters, and also
+	 * ensures that we set the appropriate advertisment for Serdes links.
+	 */
+	phylink_mac_config(pl, &pl->link_config);
+
+	clear_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state);
+	phylink_run_resolve(pl);
+
+	if (pl->phydev)
+		phy_start(pl->phydev);
+
+	mutex_unlock(&pl->config_mutex);
+}
+EXPORT_SYMBOL_GPL(phylink_start);
+
+void phylink_stop(struct phylink *pl)
+{
+	mutex_lock(&pl->config_mutex);
+
+	if (pl->phydev)
+		phy_stop(pl->phydev);
+
+	set_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state);
+	flush_work(&pl->resolve);
+
+	pl->mac_link_up = false;
+
+	mutex_unlock(&pl->config_mutex);
+}
+EXPORT_SYMBOL_GPL(phylink_stop);
+
+static void phylink_get_ethtool(const struct phylink_link_state *state,
+				struct ethtool_cmd *cmd)
+{
+	cmd->supported &= SUPPORTED_INTERFACES;
+	cmd->supported |= state->supported;
+	cmd->advertising &= ADVERTISED_INTERFACES;
+	cmd->advertising |= state->advertising;
+	ethtool_cmd_speed_set(cmd, state->speed);
+	cmd->duplex = state->duplex;
+
+	cmd->autoneg = state->an_enabled ? AUTONEG_ENABLE : AUTONEG_DISABLE;
+}
+
+static int phylink_ethtool_gset(struct phylink *pl, struct ethtool_cmd *cmd)
+{
+	struct phylink_link_state link_state;
+	int ret;
+
+	if (pl->phydev) {
+		ret = phy_ethtool_gset(pl->phydev, cmd);
+		if (ret)
+			return ret;
+
+		cmd->supported &= SUPPORTED_INTERFACES |
+				  pl->link_config.supported;
+	} else {
+		cmd->supported = pl->link_port_support;
+		cmd->transceiver = XCVR_EXTERNAL;
+		cmd->port = pl->link_port;
+	}
+
+	switch (pl->link_an_mode) {
+	case MLO_AN_FIXED:
+		/* We are using fixed settings. Report these as the
+		 * current link settings - and note that these also
+		 * represent the supported speeds/duplex/pause modes.
+		 */
+		phylink_get_fixed_state(pl, &link_state);
+		phylink_get_ethtool(&link_state, cmd);
+		break;
+
+	case MLO_AN_SGMII:
+		/* If there is a phy attached, then use the reported
+		 * settings from the phy with no modification.
+		 */
+		if (pl->phydev)
+			break;
+
+	case MLO_AN_8023Z:
+		phylink_get_mac_state(pl, &link_state);
+
+		/* The MAC is reporting the link results from its own PCS
+		 * layer via in-band status. Report these as the current
+		 * link settings.
+		 */
+		phylink_get_ethtool(&link_state, cmd);
+		break;
+	}
+
+	return 0;
+}
+
+int phylink_ethtool_get_settings(struct phylink *pl, struct ethtool_cmd *cmd)
+{
+	int ret;
+
+	mutex_lock(&pl->config_mutex);
+	ret = phylink_ethtool_gset(pl, cmd);
+	mutex_unlock(&pl->config_mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(phylink_ethtool_get_settings);
+
+static int phylink_ethtool_sset(struct phylink *pl, struct ethtool_cmd *cmd)
+{
+	u32 supported;
+	int ret;
+
+	/* Calculate the union of the MAC support and attached phy support */
+	supported = pl->link_config.supported;
+	if (pl->phydev)
+		supported &= pl->phydev->supported;
+
+	/* Mask out unsupported advertisments */
+	cmd->advertising &= supported;
+
+	/* FIXME: should we reject autoneg if phy/mac does not support it? */
+
+	if (cmd->autoneg == AUTONEG_DISABLE) {
+		/* Autonegotiation disabled, validate speed and duplex */
+		if (cmd->duplex != DUPLEX_HALF && cmd->duplex != DUPLEX_FULL)
+			return -EINVAL;
+
+		/* FIXME: validate speed/duplex against supported */
+
+		cmd->advertising &= ~ADVERTISED_Autoneg;
+	} else {
+		/* Autonegotiation enabled, validate advertisment */
+		/* FIXME: shouldn't we ensure there's some duplex/speeds set */
+		if (cmd->advertising == 0)
+			return -EINVAL;
+
+		cmd->advertising |= ADVERTISED_Autoneg;
+	}
+
+	/* If we have a fixed link (as specified by firmware), refuse
+	 * to enable autonegotiation, or change link parameters.
+	 */
+	if (pl->link_an_mode == MLO_AN_FIXED) {
+		if (cmd->autoneg != AUTONEG_DISABLE ||
+		    ethtool_cmd_speed(cmd) != pl->link_config.speed ||
+		    cmd->duplex != pl->link_config.duplex)
+			return -EINVAL;
+	}
+
+	/* If we have a PHY, configure the phy */
+	if (pl->phydev) {
+		ret = phy_ethtool_sset(pl->phydev, cmd);
+		if (ret)
+			return ret;
+	}
+
+	mutex_lock(&pl->state_mutex);
+	/* Configure the MAC to match the new settings */
+	pl->link_config.advertising = cmd->advertising;
+	pl->link_config.speed = cmd->speed;
+	pl->link_config.duplex = cmd->duplex;
+	pl->link_config.an_enabled = cmd->autoneg != AUTONEG_DISABLE;
+
+	phylink_mac_config(pl, &pl->link_config);
+	phylink_mac_an_restart(pl);
+	mutex_unlock(&pl->state_mutex);
+
+	return ret;
+}
+
+int phylink_ethtool_set_settings(struct phylink *pl, struct ethtool_cmd *cmd)
+{
+	int ret;
+
+	if (cmd->autoneg != AUTONEG_DISABLE && cmd->autoneg != AUTONEG_ENABLE)
+		return -EINVAL;
+
+	mutex_lock(&pl->config_mutex);
+	ret = phylink_ethtool_sset(pl, cmd);
+	mutex_unlock(&pl->config_mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(phylink_ethtool_set_settings);
+
+/* This emulates MII registers for a fixed-mode phy operating as per the
+ * passed in state. "aneg" defines if we report negotiation is possible.
+ *
+ * FIXME: should deal with negotiation state too.
+ */
+static int phylink_mii_emul_read(struct net_device *ndev, unsigned int reg,
+				 struct phylink_link_state *state, bool aneg)
+{
+	struct fixed_phy_status fs;
+	int val;
+
+	fs.link = state->link;
+	fs.speed = state->speed;
+	fs.duplex = state->duplex;
+	fs.pause = state->pause & MLO_PAUSE_SYM;
+	fs.asym_pause = state->pause & MLO_PAUSE_ASYM;
+
+	val = swphy_read_reg(reg, &fs);
+	if (reg == MII_BMSR) {
+		if (!state->an_complete)
+			val &= ~BMSR_ANEGCOMPLETE;
+		if (!aneg)
+			val &= ~BMSR_ANEGCAPABLE;
+	}
+	return val;
+}
+
+static int phylink_mii_read(struct phylink *pl, unsigned int phy_id,
+			    unsigned int reg)
+{
+	struct phylink_link_state state;
+	int val = 0xffff;
+
+	if (pl->phydev && pl->phydev->mdio.addr != phy_id)
+		return mdiobus_read(pl->phydev->mdio.bus, phy_id, reg);
+
+	if (!pl->phydev && phy_id != 0)
+		return val;
+
+	switch (pl->link_an_mode) {
+	case MLO_AN_FIXED:
+		phylink_get_fixed_state(pl, &state);
+		val = phylink_mii_emul_read(pl->netdev, reg, &state, true);
+		break;
+
+	case MLO_AN_PHY:
+		val = mdiobus_read(pl->phydev->mdio.bus, phy_id, reg);
+		break;
+
+	case MLO_AN_SGMII:
+		if (pl->phydev) {
+			val = mdiobus_read(pl->phydev->mdio.bus,
+					   pl->phydev->mdio.addr, reg);
+			break;
+		}
+		/* No phy, fall through to reading the MAC end */
+	case MLO_AN_8023Z:
+		val = phylink_get_mac_state(pl, &state);
+		if (val < 0)
+			return val;
+
+		val = phylink_mii_emul_read(pl->netdev, reg, &state, true);
+		break;
+	}
+
+	return val & 0xffff;
+}
+
+static void phylink_mii_write(struct phylink *pl, unsigned int phy_id,
+			      unsigned int reg, unsigned int val)
+{
+	if (pl->phydev && pl->phydev->mdio.addr != phy_id) {
+		mdiobus_write(pl->phydev->mdio.bus, phy_id, reg, val);
+		return;
+	}
+
+	if (!pl->phydev && phy_id != 0)
+		return;
+
+	switch (pl->link_an_mode) {
+	case MLO_AN_FIXED:
+		break;
+
+	case MLO_AN_PHY:
+		mdiobus_write(pl->phydev->mdio.bus, pl->phydev->mdio.addr,
+			      reg, val);
+		break;
+
+	case MLO_AN_SGMII:
+		if (pl->phydev) {
+			mdiobus_write(pl->phydev->mdio.bus, phy_id, reg, val);
+			break;
+		}
+		/* No phy, fall through to reading the MAC end */
+	case MLO_AN_8023Z:
+		break;
+	}
+}
+
+int phylink_mii_ioctl(struct phylink *pl, struct ifreq *ifr, int cmd)
+{
+	struct mii_ioctl_data *mii_data = if_mii(ifr);
+	int val, ret;
+
+	mutex_lock(&pl->config_mutex);
+
+	switch (cmd) {
+	case SIOCGMIIPHY:
+		mii_data->phy_id = pl->phydev ? pl->phydev->mdio.addr : 0;
+		/* fallthrough */
+
+	case SIOCGMIIREG:
+		val = phylink_mii_read(pl, mii_data->phy_id, mii_data->reg_num);
+		if (val < 0) {
+			ret = val;
+		} else {
+			mii_data->val_out = val;
+			ret = 0;
+		}
+		break;
+
+	case SIOCSMIIREG:
+		phylink_mii_write(pl, mii_data->phy_id, mii_data->reg_num,
+				  mii_data->val_in);
+		ret = 0;
+		break;
+
+	default:
+		ret = -EOPNOTSUPP;
+		if (pl->phydev)
+			ret = phy_mii_ioctl(pl->phydev, ifr, cmd);
+		break;
+	}
+
+	mutex_unlock(&pl->config_mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(phylink_mii_ioctl);
+
+MODULE_LICENSE("GPL");
diff --git a/include/linux/phy.h b/include/linux/phy.h
index 0f73a68067d1..fd8dfd97e2f2 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -150,6 +150,7 @@ static inline const char *phy_modes(phy_interface_t interface)
 #define MII_ADDR_C45 (1<<30)
 
 struct device;
+struct phylink;
 struct sk_buff;
 
 /*
@@ -419,6 +420,7 @@ struct phy_device {
 
 	struct mutex lock;
 
+	struct phylink *phylink;
 	struct net_device *attached_dev;
 
 	u8 mdix;
diff --git a/include/linux/phylink.h b/include/linux/phylink.h
new file mode 100644
index 000000000000..05953c8abc70
--- /dev/null
+++ b/include/linux/phylink.h
@@ -0,0 +1,70 @@
+#ifndef NETDEV_PCS_H
+#define NETDEV_PCS_H
+
+#include <linux/phy.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+
+struct device_node;
+struct ethtool_cmd;
+struct net_device;
+
+enum {
+	MLO_PAUSE_NONE,
+	MLO_PAUSE_ASYM = BIT(0),
+	MLO_PAUSE_SYM = BIT(1),
+
+	MLO_AN_PHY = 0,
+	MLO_AN_FIXED,
+	MLO_AN_SGMII,
+	MLO_AN_8023Z,
+};
+
+struct phylink_link_state {
+	u32 supported;
+	u32 advertising;
+	u32 lp_advertising;
+	int speed;
+	int duplex;
+	int pause;
+	unsigned int link:1;
+	unsigned int sync:1;
+	unsigned int an_enabled:1;
+	unsigned int an_complete:1;
+};
+
+struct phylink_mac_ops {
+	/* Get the ethtool supported mask for the indicated mode */
+	int (*mac_get_support)(struct net_device *, unsigned int mode,
+			       struct phylink_link_state *);
+
+	/* Read the current link state from the hardware */
+	int (*mac_link_state)(struct net_device *, struct phylink_link_state *);
+
+	/* Configure the MAC */
+	void (*mac_config)(struct net_device *, unsigned int mode,
+			   const struct phylink_link_state *);
+	void (*mac_an_restart)(struct net_device *, unsigned int mode);
+
+	void (*mac_link_down)(struct net_device *, unsigned int mode);
+	void (*mac_link_up)(struct net_device *, unsigned int mode);
+};
+
+struct phylink *phylink_create(struct net_device *, struct device_node *,
+	phy_interface_t iface, const struct phylink_mac_ops *ops);
+void phylink_destroy(struct phylink *);
+
+int phylink_connect_phy(struct phylink *, struct phy_device *);
+int phylink_of_phy_connect(struct phylink *, struct device_node *);
+void phylink_disconnect_phy(struct phylink *);
+
+void phylink_mac_change(struct phylink *, bool up);
+
+void phylink_start(struct phylink *);
+void phylink_stop(struct phylink *);
+
+int phylink_ethtool_get_settings(struct phylink *, struct ethtool_cmd *);
+int phylink_ethtool_set_settings(struct phylink *, struct ethtool_cmd *);
+int phylink_mii_ioctl(struct phylink *, struct ifreq *, int);
+
+#endif

---

From: Russell King <[email-masked]>
Date: Mon, 15 Aug 2016 18:57:41 +0100

Add support to phylink for SFP, which needs to control and configure
the ethernet MAC link state.  Specifically, SFP needs to:

1. set the negotiation mode between SGMII and 1000base-X
2. attach and detach the module PHY
3. prevent the link coming up when errors are reported

In the absence of a PHY, we also need to set the ethtool port type
according to the module plugged in.

Reviewed-by: Florian Fainelli <[email-masked]>
Signed-off-by: Russell King <[email-masked]>
---
 drivers/net/phy/phylink.c | 82 +++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/phylink.h   |  6 ++++
 2 files changed, 88 insertions(+)

diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index 1d4ddba0d239..2053a3ecaf45 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -11,6 +11,7 @@
 #include <linux/ethtool.h>
 #include <linux/export.h>
 #include <linux/gpio/consumer.h>
+#include <linux/list.h>
 #include <linux/netdevice.h>
 #include <linux/of.h>
 #include <linux/of_mdio.h>
@@ -29,11 +30,16 @@
 	(ADVERTISED_TP | ADVERTISED_MII | ADVERTISED_FIBRE | \
 	 ADVERTISED_BNC | ADVERTISED_AUI | ADVERTISED_Backplane)
 
+static LIST_HEAD(phylinks);
+static DEFINE_MUTEX(phylink_mutex);
+
 enum {
 	PHYLINK_DISABLE_STOPPED,
+	PHYLINK_DISABLE_LINK,
 };
 
 struct phylink {
+	struct list_head node;
 	struct net_device *netdev;
 	const struct phylink_mac_ops *ops;
 	struct mutex config_mutex;
@@ -329,12 +335,20 @@ struct phylink *phylink_create(struct net_device *ndev, struct device_node *np,
 		return ERR_PTR(ret);
 	}
 
+	mutex_lock(&phylink_mutex);
+	list_add_tail(&pl->node, &phylinks);
+	mutex_unlock(&phylink_mutex);
+
 	return pl;
 }
 EXPORT_SYMBOL_GPL(phylink_create);
 
 void phylink_destroy(struct phylink *pl)
 {
+	mutex_lock(&phylink_mutex);
+	list_del(&pl->node);
+	mutex_unlock(&phylink_mutex);
+
 	cancel_work_sync(&pl->resolve);
 	kfree(pl);
 }
@@ -801,4 +815,72 @@ int phylink_mii_ioctl(struct phylink *pl, struct ifreq *ifr, int cmd)
 }
 EXPORT_SYMBOL_GPL(phylink_mii_ioctl);
 
+
+
+void phylink_disable(struct phylink *pl)
+{
+	set_bit(PHYLINK_DISABLE_LINK, &pl->phylink_disable_state);
+	flush_work(&pl->resolve);
+
+	netif_carrier_off(pl->netdev);
+}
+EXPORT_SYMBOL_GPL(phylink_disable);
+
+void phylink_enable(struct phylink *pl)
+{
+	clear_bit(PHYLINK_DISABLE_LINK, &pl->phylink_disable_state);
+	phylink_run_resolve(pl);
+}
+EXPORT_SYMBOL_GPL(phylink_enable);
+
+void phylink_set_link_port(struct phylink *pl, u32 support, u8 port)
+{
+	WARN_ON(support & ~SUPPORTED_INTERFACES);
+
+	mutex_lock(&pl->config_mutex);
+	pl->link_port_support = support;
+	pl->link_port = port;
+	mutex_unlock(&pl->config_mutex);
+}
+EXPORT_SYMBOL_GPL(phylink_set_link_port);
+
+int phylink_set_link_an_mode(struct phylink *pl, unsigned int mode)
+{
+	int ret = 0;
+
+	mutex_lock(&pl->config_mutex);
+	if (pl->link_an_mode != mode) {
+		ret = phylink_get_support(pl, mode);
+		if (ret == 0) {
+			if (!test_bit(PHYLINK_DISABLE_STOPPED,
+				      &pl->phylink_disable_state))
+				phylink_mac_config(pl, &pl->link_config);
+
+			netdev_info(pl->netdev, "switched to %s link mode\n",
+				    phylink_an_mode_str(mode));
+		}
+	}
+	mutex_unlock(&pl->config_mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(phylink_set_link_an_mode);
+
+struct phylink *phylink_lookup_by_netdev(struct net_device *ndev)
+{
+	struct phylink *pl, *found = NULL;
+
+	mutex_lock(&phylink_mutex);
+	list_for_each_entry(pl, &phylinks, node)
+		if (pl->netdev == ndev) {
+			found = pl;
+			break;
+		}
+
+	mutex_unlock(&phylink_mutex);
+
+	return found;
+}
+EXPORT_SYMBOL_GPL(phylink_lookup_by_netdev);
+
 MODULE_LICENSE("GPL");
diff --git a/include/linux/phylink.h b/include/linux/phylink.h
index 05953c8abc70..c7a665a538c1 100644
--- a/include/linux/phylink.h
+++ b/include/linux/phylink.h
@@ -67,4 +67,10 @@ int phylink_ethtool_get_settings(struct phylink *, struct ethtool_cmd *);
 int phylink_ethtool_set_settings(struct phylink *, struct ethtool_cmd *);
 int phylink_mii_ioctl(struct phylink *, struct ifreq *, int);
 
+void phylink_set_link_port(struct phylink *pl, u32 support, u8 port);
+int phylink_set_link_an_mode(struct phylink *pl, unsigned int mode);
+void phylink_disable(struct phylink *pl);
+void phylink_enable(struct phylink *pl);
+struct phylink *phylink_lookup_by_netdev(struct net_device *ndev);
+
 #endif

---

From: Russell King <[email-masked]>
Date: Mon, 15 Aug 2016 18:57:41 +0100

Add support for SFP hotpluggable modules via phylink.  This supports
both copper and optical SFP modules, which require different Serdes
modes in order to properly negotiate the link.

Optical SFP modules typically require the Serdes link to be talking
1000base-X mode - this is the gigabit ethernet mode defined by the
802.3 standard.

Copper SFP modules typically integrate a PHY in the module to convert
from Serdes to copper, and the PHY will be configured by the vendor
to either present a 1000base-X Serdes link (for fixed 1000base-T) or
a SGMII Serdes link.  However, this is vendor defined, so we instead
detect the PHY, switch the link to SGMII mode, and use traditional
PHY based negotiation.

Signed-off-by: Russell King <[email-masked]>
---
 drivers/net/phy/Kconfig  |   5 +
 drivers/net/phy/Makefile |   1 +
 drivers/net/phy/sfp.c    | 986 +++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/sfp.h      | 339 ++++++++++++++++
 4 files changed, 1331 insertions(+)
 create mode 100644 drivers/net/phy/sfp.c
 create mode 100644 include/linux/sfp.h

diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index fc0c9eb543e5..dabf1d4ca4ba 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -179,6 +179,11 @@ config FIXED_PHY
 
 	  Currently tested with mpc866ads and mpc8349e-mitx.
 
+config SFP
+	tristate "SFP cage support"
+	depends on I2C && PHYLINK
+	select MDIO_I2C
+
 config MDIO_BITBANG
 	tristate "Support for bitbanged MDIO buses"
 	help
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index 5ba8a48a4bcc..7dd7b96df819 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -51,3 +51,4 @@ obj-$(CONFIG_MDIO_BCM_IPROC)	+= mdio-bcm-iproc.o
 obj-$(CONFIG_INTEL_XWAY_PHY)	+= intel-xway.o
 obj-$(CONFIG_MDIO_HISI_FEMAC)	+= mdio-hisi-femac.o
 obj-$(CONFIG_MDIO_XGENE)	+= mdio-xgene.o
+obj-$(CONFIG_SFP)		+= sfp.o
diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c
new file mode 100644
index 000000000000..86029f21c4d9
--- /dev/null
+++ b/drivers/net/phy/sfp.c
@@ -0,0 +1,986 @@
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/netdevice.h>
+#include <linux/of.h>
+#include <linux/of_net.h>
+#include <linux/phylink.h>
+#include <linux/platform_device.h>
+#include <linux/sfp.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#include "mdio-i2c.h"
+#include "swphy.h"
+
+enum {
+	GPIO_MODDEF0,
+	GPIO_LOS,
+	GPIO_TX_FAULT,
+	GPIO_TX_DISABLE,
+	GPIO_RATE_SELECT,
+	GPIO_MAX,
+
+	SFP_F_PRESENT = BIT(GPIO_MODDEF0),
+	SFP_F_LOS = BIT(GPIO_LOS),
+	SFP_F_TX_FAULT = BIT(GPIO_TX_FAULT),
+	SFP_F_TX_DISABLE = BIT(GPIO_TX_DISABLE),
+	SFP_F_RATE_SELECT = BIT(GPIO_RATE_SELECT),
+
+	SFP_E_INSERT = 0,
+	SFP_E_REMOVE,
+	SFP_E_DEV_DOWN,
+	SFP_E_DEV_UP,
+	SFP_E_TX_FAULT,
+	SFP_E_TX_CLEAR,
+	SFP_E_LOS_HIGH,
+	SFP_E_LOS_LOW,
+	SFP_E_TIMEOUT,
+
+	SFP_MOD_EMPTY = 0,
+	SFP_MOD_PROBE,
+	SFP_MOD_PRESENT,
+	SFP_MOD_ERROR,
+
+	SFP_DEV_DOWN = 0,
+	SFP_DEV_UP,
+
+	SFP_S_DOWN = 0,
+	SFP_S_INIT,
+	SFP_S_WAIT_LOS,
+	SFP_S_LINK_UP,
+	SFP_S_TX_FAULT,
+	SFP_S_REINIT,
+	SFP_S_TX_DISABLE,
+};
+
+static const char *gpio_of_names[] = {
+	"moddef0",
+	"los",
+	"tx-fault",
+	"tx-disable",
+	"rate-select",
+};
+
+static const enum gpiod_flags gpio_flags[] = {
+	GPIOD_IN,
+	GPIOD_IN,
+	GPIOD_IN,
+	GPIOD_ASIS,
+	GPIOD_ASIS,
+};
+
+#define T_INIT_JIFFIES	msecs_to_jiffies(300)
+#define T_RESET_US	10
+#define T_FAULT_RECOVER	msecs_to_jiffies(1000)
+
+/* SFP module presence detection is poor: the three MOD DEF signals are
+ * the same length on the PCB, which means it's possible for MOD DEF 0 to
+ * connect before the I2C bus on MOD DEF 1/2.  Try to work around this
+ * design bug by waiting 50ms before probing, and then retry every 250ms.
+ */
+#define T_PROBE_INIT	msecs_to_jiffies(50)
+#define T_PROBE_RETRY	msecs_to_jiffies(250)
+
+/*
+ * SFP modules appear to always have their PHY configured for bus address
+ * 0x56 (which with mdio-i2c, translates to a PHY address of 22).
+ */
+#define SFP_PHY_ADDR	22
+
+/*
+ * Give this long for the PHY to reset.
+ */
+#define T_PHY_RESET_MS	50
+
+static DEFINE_MUTEX(sfp_mutex);
+
+struct sfp {
+	struct device *dev;
+	struct i2c_adapter *i2c;
+	struct mii_bus *i2c_mii;
+	struct net_device *ndev;
+	struct phylink *phylink;
+	struct phy_device *mod_phy;
+
+	unsigned int (*get_state)(struct sfp *);
+	void (*set_state)(struct sfp *, unsigned int);
+	int (*read)(struct sfp *, bool, u8, void *, size_t);
+
+	struct gpio_desc *gpio[GPIO_MAX];
+
+	unsigned int state;
+	struct delayed_work poll;
+	struct delayed_work timeout;
+	struct mutex sm_mutex;
+	unsigned char sm_mod_state;
+	unsigned char sm_dev_state;
+	unsigned short sm_state;
+	unsigned int sm_retries;
+
+	struct sfp_eeprom_id id;
+
+	struct notifier_block netdev_nb;
+};
+
+static unsigned long poll_jiffies;
+
+static unsigned int sfp_gpio_get_state(struct sfp *sfp)
+{
+	unsigned int i, state, v;
+
+	for (i = state = 0; i < GPIO_MAX; i++) {
+		if (gpio_flags[i] != GPIOD_IN || !sfp->gpio[i])
+			continue;
+
+		v = gpiod_get_value_cansleep(sfp->gpio[i]);
+		if (v)
+			state |= BIT(i);
+	}
+
+	return state;
+}
+
+static void sfp_gpio_set_state(struct sfp *sfp, unsigned int state)
+{
+	if (state & SFP_F_PRESENT) {
+		/* If the module is present, drive the signals */
+		if (sfp->gpio[GPIO_TX_DISABLE])
+			gpiod_direction_output(sfp->gpio[GPIO_TX_DISABLE],
+						state & SFP_F_TX_DISABLE);
+		if (state & SFP_F_RATE_SELECT)
+			gpiod_direction_output(sfp->gpio[GPIO_RATE_SELECT],
+						state & SFP_F_RATE_SELECT);
+	} else {
+		/* Otherwise, let them float to the pull-ups */
+		if (sfp->gpio[GPIO_TX_DISABLE])
+			gpiod_direction_input(sfp->gpio[GPIO_TX_DISABLE]);
+		if (state & SFP_F_RATE_SELECT)
+			gpiod_direction_input(sfp->gpio[GPIO_RATE_SELECT]);
+	}
+}
+
+static int sfp__i2c_read(struct i2c_adapter *i2c, u8 bus_addr, u8 dev_addr,
+	void *buf, size_t len)
+{
+	struct i2c_msg msgs[2];
+	int ret;
+
+	msgs[0].addr = bus_addr;
+	msgs[0].flags = 0;
+	msgs[0].len = 1;
+	msgs[0].buf = &dev_addr;
+	msgs[1].addr = bus_addr;
+	msgs[1].flags = I2C_M_RD;
+	msgs[1].len = len;
+	msgs[1].buf = buf;
+
+	ret = i2c_transfer(i2c, msgs, ARRAY_SIZE(msgs));
+	if (ret < 0)
+		return ret;
+
+	return ret == ARRAY_SIZE(msgs) ? len : 0;
+}
+
+static int sfp_i2c_read(struct sfp *sfp, bool a2, u8 addr, void *buf,
+	size_t len)
+{
+	return sfp__i2c_read(sfp->i2c, a2 ? 0x51 : 0x50, addr, buf, len);
+}
+
+static int sfp_i2c_configure(struct sfp *sfp, struct i2c_adapter *i2c)
+{
+	struct mii_bus *i2c_mii;
+	int ret;
+
+	if (!i2c_check_functionality(i2c, I2C_FUNC_I2C))
+		return -EINVAL;
+
+	sfp->i2c = i2c;
+	sfp->read = sfp_i2c_read;
+
+	i2c_mii = mdio_i2c_alloc(sfp->dev, i2c);
+	if (IS_ERR(i2c_mii))
+		return PTR_ERR(i2c_mii);
+
+	i2c_mii->name = "SFP I2C Bus";
+	i2c_mii->phy_mask = ~0;
+
+	ret = mdiobus_register(i2c_mii);
+	if (ret < 0) {
+		mdiobus_free(i2c_mii);
+		return ret;
+	}
+
+	sfp->i2c_mii = i2c_mii;
+
+	return 0;
+}
+
+
+/* Interface */
+static unsigned int sfp_get_state(struct sfp *sfp)
+{
+	return sfp->get_state(sfp);
+}
+
+static void sfp_set_state(struct sfp *sfp, unsigned int state)
+{
+	sfp->set_state(sfp, state);
+}
+
+static int sfp_read(struct sfp *sfp, bool a2, u8 addr, void *buf, size_t len)
+{
+	return sfp->read(sfp, a2, addr, buf, len);
+}
+
+static unsigned int sfp_check(void *buf, size_t len)
+{
+	u8 *p, check;
+
+	for (p = buf, check = 0; len; p++, len--)
+		check += *p;
+
+	return check;
+}
+
+/* Helpers */
+static void sfp_module_tx_disable(struct sfp *sfp)
+{
+	dev_dbg(sfp->dev, "tx disable %u -> %u\n",
+		sfp->state & SFP_F_TX_DISABLE ? 1 : 0, 1);
+	sfp->state |= SFP_F_TX_DISABLE;
+	sfp_set_state(sfp, sfp->state);
+}
+
+static void sfp_module_tx_enable(struct sfp *sfp)
+{
+	dev_dbg(sfp->dev, "tx disable %u -> %u\n",
+		sfp->state & SFP_F_TX_DISABLE ? 1 : 0, 0);
+	sfp->state &= ~SFP_F_TX_DISABLE;
+	sfp_set_state(sfp, sfp->state);
+}
+
+static void sfp_module_tx_fault_reset(struct sfp *sfp)
+{
+	unsigned int state = sfp->state;
+
+	if (state & SFP_F_TX_DISABLE)
+		return;
+
+	sfp_set_state(sfp, state | SFP_F_TX_DISABLE);
+
+	udelay(T_RESET_US);
+
+	sfp_set_state(sfp, state);
+}
+
+/* SFP state machine */
+static void sfp_sm_set_timer(struct sfp *sfp, unsigned int timeout)
+{
+	if (timeout)
+		mod_delayed_work(system_power_efficient_wq, &sfp->timeout,
+				 timeout);
+	else
+		cancel_delayed_work(&sfp->timeout);
+}
+
+static void sfp_sm_next(struct sfp *sfp, unsigned int state,
+			unsigned int timeout)
+{
+	sfp->sm_state = state;
+	sfp_sm_set_timer(sfp, timeout);
+}
+
+static void sfp_sm_ins_next(struct sfp *sfp, unsigned int state, unsigned int timeout)
+{
+	sfp->sm_mod_state = state;
+	sfp_sm_set_timer(sfp, timeout);
+}
+
+static void sfp_sm_phy_detach(struct sfp *sfp)
+{
+	phy_stop(sfp->mod_phy);
+	if (sfp->phylink)
+		phylink_disconnect_phy(sfp->phylink);
+	phy_device_remove(sfp->mod_phy);
+	phy_device_free(sfp->mod_phy);
+	sfp->mod_phy = NULL;
+}
+
+static void sfp_sm_probe_phy(struct sfp *sfp)
+{
+	struct phy_device *phy;
+	int err;
+
+	msleep(T_PHY_RESET_MS);
+
+	phy = mdiobus_scan(sfp->i2c_mii, SFP_PHY_ADDR);
+	if (IS_ERR(phy)) {
+		dev_err(sfp->dev, "mdiobus scan returned %ld\n", PTR_ERR(phy));
+		return;
+	}
+	if (!phy) {
+		dev_info(sfp->dev, "no PHY detected\n");
+		return;
+	}
+
+	err = phylink_connect_phy(sfp->phylink, phy);
+	if (err) {
+		phy_device_remove(phy);
+		phy_device_free(phy);
+		dev_err(sfp->dev, "phylink_connect_phy failed: %d\n", err);
+		return;
+	}
+
+	sfp->mod_phy = phy;
+	phy_start(phy);
+}
+
+static void sfp_sm_link_up(struct sfp *sfp)
+{
+	if (sfp->phylink)
+		phylink_enable(sfp->phylink);
+
+	sfp_sm_next(sfp, SFP_S_LINK_UP, 0);
+}
+
+static void sfp_sm_link_down(struct sfp *sfp)
+{
+	if (sfp->phylink)
+		phylink_disable(sfp->phylink);
+}
+
+static void sfp_sm_link_check_los(struct sfp *sfp)
+{
+	unsigned int los = sfp->state & SFP_F_LOS;
+
+	/* FIXME: what if neither SFP_OPTIONS_LOS_INVERTED nor
+	 * SFP_OPTIONS_LOS_NORMAL are set?  For now, we assume
+	 * the same as SFP_OPTIONS_LOS_NORMAL set.
+	 */
+	if (sfp->id.ext.options & SFP_OPTIONS_LOS_INVERTED)
+		los ^= SFP_F_LOS;
+
+	if (los)
+		sfp_sm_next(sfp, SFP_S_WAIT_LOS, 0);
+	else
+		sfp_sm_link_up(sfp);
+}
+
+static void sfp_sm_fault(struct sfp *sfp, bool warn)
+{
+	if (sfp->sm_retries && !--sfp->sm_retries) {
+		dev_err(sfp->dev, "module persistently indicates fault, disabling\n");
+		sfp_sm_next(sfp, SFP_S_TX_DISABLE, 0);
+	} else {
+		if (warn)
+			dev_err(sfp->dev, "module transmit fault indicated\n");
+
+		sfp_sm_next(sfp, SFP_S_TX_FAULT, T_FAULT_RECOVER);
+	}
+}
+
+static void sfp_sm_mod_init(struct sfp *sfp)
+{
+	sfp_module_tx_enable(sfp);
+
+	/* Wait t_init before indicating that the link is up, provided the
+	 * current state indicates no TX_FAULT.  If TX_FAULT clears before
+	 * this time, that's fine too.
+	 */
+	sfp_sm_next(sfp, SFP_S_INIT, T_INIT_JIFFIES);
+	sfp->sm_retries = 5;
+
+	if (sfp->phylink) {
+		/* Setting the serdes link mode is guesswork: there's no
+		 * field in the EEPROM which indicates what mode should
+		 * be used.
+		 *
+		 * If it's a gigabit-only fiber module, it probably does
+		 * not have a PHY, so switch to 802.3z negotiation mode.
+		 * Otherwise, switch to SGMII mode (which is required to
+		 * support non-gigabit speeds) and probe for a PHY.
+		 */
+		if (!sfp->id.base.e1000_base_t &&
+		    !sfp->id.base.e100_base_lx &&
+		    !sfp->id.base.e100_base_fx) {
+			phylink_set_link_an_mode(sfp->phylink, MLO_AN_8023Z);
+		} else {
+			phylink_set_link_an_mode(sfp->phylink, MLO_AN_SGMII);
+			sfp_sm_probe_phy(sfp);
+		}
+	}
+}
+
+static int sfp_sm_mod_probe(struct sfp *sfp)
+{
+	/* SFP module inserted - read I2C data */
+	struct sfp_eeprom_id id;
+	char vendor[17];
+	char part[17];
+	char sn[17];
+	char date[9];
+	char rev[5];
+	u8 check;
+	int err;
+
+	err = sfp_read(sfp, false, 0, &id, sizeof(id));
+	if (err < 0) {
+		dev_err(sfp->dev, "failed to read EEPROM: %d\n", err);
+		return -EAGAIN;
+	}
+
+	/* Validate the checksum over the base structure */
+	check = sfp_check(&id.base, sizeof(id.base) - 1);
+	if (check != id.base.cc_base) {
+		dev_err(sfp->dev,
+			"EEPROM base structure checksum failure: 0x%02x\n",
+			check);
+		return -EINVAL;
+	}
+
+	check = sfp_check(&id.ext, sizeof(id.ext) - 1);
+	if (check != id.ext.cc_ext) {
+		dev_err(sfp->dev,
+			"EEPROM extended structure checksum failure: 0x%02x\n",
+			check);
+		memset(&id.ext, 0, sizeof(id.ext));
+	}
+
+	sfp->id = id;
+
+	memcpy(vendor, sfp->id.base.vendor_name, 16);
+	vendor[16] = '\0';
+	memcpy(part, sfp->id.base.vendor_pn, 16);
+	part[16] = '\0';
+	memcpy(rev, sfp->id.base.vendor_rev, 4);
+	rev[4] = '\0';
+	memcpy(sn, sfp->id.ext.vendor_sn, 16);
+	sn[16] = '\0';
+	memcpy(date, sfp->id.ext.datecode, 8);
+	date[8] = '\0';
+
+	dev_info(sfp->dev, "module %s %s rev %s sn %s dc %s\n", vendor, part, rev, sn, date);
+
+	/* We only support SFP modules, not the legacy GBIC modules. */
+	if (sfp->id.base.phys_id != SFP_PHYS_ID_SFP ||
+	    sfp->id.base.phys_ext_id != SFP_PHYS_EXT_ID_SFP) {
+		dev_err(sfp->dev, "module is not SFP - phys id 0x%02x 0x%02x\n",
+			sfp->id.base.phys_id, sfp->id.base.phys_ext_id);
+		return -EINVAL;
+	}
+
+	/*
+	 * What isn't clear from the SFP documentation is whether this
+	 * specifies the encoding expected on the TD/RD lines, or whether
+	 * the TD/RD lines are always 8b10b encoded, but the transceiver
+	 * converts.  Eg, think of a copper SFP supporting 1G/100M/10M
+	 * ethernet: this requires 8b10b encoding for 1G, 4b5b for 100M,
+	 * and manchester for 10M.
+	 */
+	/* 1Gbit ethernet requires 8b10b encoding */
+	if (sfp->id.base.encoding != SFP_ENCODING_8B10B) {
+		dev_err(sfp->dev, "module does not support 8B10B encoding\n");
+		return -EINVAL;
+	}
+
+	if (sfp->phylink) {
+		u32 support;
+		u8 port;
+
+		if (sfp->id.base.e1000_base_t) {
+			support = SUPPORTED_TP;
+			port = PORT_TP;
+		} else {
+			support = SUPPORTED_FIBRE;
+			port = PORT_FIBRE;
+		}
+		phylink_set_link_port(sfp->phylink, support, port);
+	}
+
+	return 0;
+}
+
+static void sfp_sm_mod_remove(struct sfp *sfp)
+{
+	if (sfp->mod_phy)
+		sfp_sm_phy_detach(sfp);
+
+	sfp_module_tx_disable(sfp);
+
+	memset(&sfp->id, 0, sizeof(sfp->id));
+
+	dev_info(sfp->dev, "module removed\n");
+}
+
+static void sfp_sm_event(struct sfp *sfp, unsigned int event)
+{
+	mutex_lock(&sfp->sm_mutex);
+
+	dev_dbg(sfp->dev, "SM: enter %u:%u:%u event %u\n",
+		sfp->sm_mod_state, sfp->sm_dev_state, sfp->sm_state, event);
+
+	/* This state machine tracks the insert/remove state of
+	 * the module, and handles probing the on-board EEPROM.
+	 */
+	switch (sfp->sm_mod_state) {
+	default:
+		if (event == SFP_E_INSERT) {
+			sfp_module_tx_disable(sfp);
+			sfp_sm_ins_next(sfp, SFP_MOD_PROBE, T_PROBE_INIT);
+		}
+		break;
+
+	case SFP_MOD_PROBE:
+		if (event == SFP_E_REMOVE) {
+			sfp_sm_ins_next(sfp, SFP_MOD_EMPTY, 0);
+		} else if (event == SFP_E_TIMEOUT) {
+			int err = sfp_sm_mod_probe(sfp);
+
+			if (err == 0)
+				sfp_sm_ins_next(sfp, SFP_MOD_PRESENT, 0);
+			else if (err == -EAGAIN)
+				sfp_sm_set_timer(sfp, T_PROBE_RETRY);
+			else
+				sfp_sm_ins_next(sfp, SFP_MOD_ERROR, 0);
+		}
+		break;
+
+	case SFP_MOD_PRESENT:
+	case SFP_MOD_ERROR:
+		if (event == SFP_E_REMOVE) {
+			sfp_sm_mod_remove(sfp);
+			sfp_sm_ins_next(sfp, SFP_MOD_EMPTY, 0);
+		}
+		break;
+	}
+
+	/* This state machine tracks the netdev up/down state */
+	switch (sfp->sm_dev_state) {
+	default:
+		if (event == SFP_E_DEV_UP)
+			sfp->sm_dev_state = SFP_DEV_UP;
+		break;
+
+	case SFP_DEV_UP:
+		if (event == SFP_E_DEV_DOWN) {
+			/* If the module has a PHY, avoid raising TX disable
+			 * as this resets the PHY. Otherwise, raise it to
+			 * turn the laser off.
+			 */
+			if (!sfp->mod_phy)
+				sfp_module_tx_disable(sfp);
+			sfp->sm_dev_state = SFP_DEV_DOWN;
+		}
+		break;
+	}
+
+	/* Some events are global */
+	if (sfp->sm_state != SFP_S_DOWN &&
+	    (sfp->sm_mod_state != SFP_MOD_PRESENT ||
+	     sfp->sm_dev_state != SFP_DEV_UP)) {
+		if (sfp->sm_state == SFP_S_LINK_UP &&
+		    sfp->sm_dev_state == SFP_DEV_UP)
+			sfp_sm_link_down(sfp);
+		if (sfp->mod_phy)
+			sfp_sm_phy_detach(sfp);
+		sfp_sm_next(sfp, SFP_S_DOWN, 0);
+		mutex_unlock(&sfp->sm_mutex);
+		return;
+	}
+
+	/* The main state machine */
+	switch (sfp->sm_state) {
+	case SFP_S_DOWN:
+		if (sfp->sm_mod_state == SFP_MOD_PRESENT &&
+		    sfp->sm_dev_state == SFP_DEV_UP)
+			sfp_sm_mod_init(sfp);
+		break;
+
+	case SFP_S_INIT:
+		if (event == SFP_E_TIMEOUT && sfp->state & SFP_F_TX_FAULT)
+			sfp_sm_fault(sfp, true);
+		else if (event == SFP_E_TIMEOUT || event == SFP_E_TX_CLEAR)
+			sfp_sm_link_check_los(sfp);
+		break;
+
+	case SFP_S_WAIT_LOS:
+		if (event == SFP_E_TX_FAULT)
+			sfp_sm_fault(sfp, true);
+		else if (event ==
+			 (sfp->id.ext.options & SFP_OPTIONS_LOS_INVERTED ?
+			  SFP_E_LOS_HIGH : SFP_E_LOS_LOW))
+			sfp_sm_link_up(sfp);
+		break;
+
+	case SFP_S_LINK_UP:
+		if (event == SFP_E_TX_FAULT) {
+			sfp_sm_link_down(sfp);
+			sfp_sm_fault(sfp, true);
+		} else if (event ==
+			   (sfp->id.ext.options & SFP_OPTIONS_LOS_INVERTED ?
+			    SFP_E_LOS_LOW : SFP_E_LOS_HIGH)) {
+			sfp_sm_link_down(sfp);
+			sfp_sm_next(sfp, SFP_S_WAIT_LOS, 0);
+		}
+		break;
+
+	case SFP_S_TX_FAULT:
+		if (event == SFP_E_TIMEOUT) {
+			sfp_module_tx_fault_reset(sfp);
+			sfp_sm_next(sfp, SFP_S_REINIT, T_INIT_JIFFIES);
+		}
+		break;
+
+	case SFP_S_REINIT:
+		if (event == SFP_E_TIMEOUT && sfp->state & SFP_F_TX_FAULT) {
+			sfp_sm_fault(sfp, false);
+		} else if (event == SFP_E_TIMEOUT || event == SFP_E_TX_CLEAR) {
+			dev_info(sfp->dev, "module transmit fault recovered\n");
+			sfp_sm_link_check_los(sfp);
+		}
+		break;
+
+	case SFP_S_TX_DISABLE:
+		break;
+	}
+
+	dev_dbg(sfp->dev, "SM: exit %u:%u:%u\n",
+		sfp->sm_mod_state, sfp->sm_dev_state, sfp->sm_state);
+
+	mutex_unlock(&sfp->sm_mutex);
+}
+
+#if 0
+static int sfp_phy_module_info(struct phy_device *phy,
+			       struct ethtool_modinfo *modinfo)
+{
+	struct sfp *sfp = phy->priv;
+
+	/* locking... and check module is present */
+
+	if (sfp->id.ext.sff8472_compliance) {
+		modinfo->type = ETH_MODULE_SFF_8472;
+		modinfo->eeprom_len = ETH_MODULE_SFF_8472_LEN;
+	} else {
+		modinfo->type = ETH_MODULE_SFF_8079;
+		modinfo->eeprom_len = ETH_MODULE_SFF_8079_LEN;
+	}
+	return 0;
+}
+
+static int sfp_phy_module_eeprom(struct phy_device *phy,
+	struct ethtool_eeprom *ee, u8 *data)
+{
+	struct sfp *sfp = phy->priv;
+	unsigned int first, last, len;
+	int ret;
+
+	if (ee->len == 0)
+		return -EINVAL;
+
+	first = ee->offset;
+	last = ee->offset + ee->len;
+	if (first < ETH_MODULE_SFF_8079_LEN) {
+		len = last;
+		if (len > ETH_MODULE_SFF_8079_LEN)
+			len = ETH_MODULE_SFF_8079_LEN;
+		len -= first;
+
+		ret = sfp->read(sfp, false, first, data, len);
+		if (ret < 0)
+			return ret;
+
+		first += len;
+		data += len;
+	}
+	if (first >= ETH_MODULE_SFF_8079_LEN && last > first) {
+		len = last - first;
+
+		ret = sfp->read(sfp, true, first, data, len);
+		if (ret < 0)
+			return ret;
+	}
+	return 0;
+}
+#endif
+
+static void sfp_timeout(struct work_struct *work)
+{
+	struct sfp *sfp = container_of(work, struct sfp, timeout.work);
+
+	sfp_sm_event(sfp, SFP_E_TIMEOUT);
+}
+
+static void sfp_check_state(struct sfp *sfp)
+{
+	unsigned int state, i, changed;
+
+	state = sfp_get_state(sfp);
+	changed = state ^ sfp->state;
+	changed &= SFP_F_PRESENT | SFP_F_LOS | SFP_F_TX_FAULT;
+
+	for (i = 0; i < GPIO_MAX; i++)
+		if (changed & BIT(i))
+			dev_dbg(sfp->dev, "%s %u -> %u\n", gpio_of_names[i],
+				!!(sfp->state & BIT(i)), !!(state & BIT(i)));
+
+	state |= sfp->state & (SFP_F_TX_DISABLE | SFP_F_RATE_SELECT);
+	sfp->state = state;
+
+	if (changed & SFP_F_PRESENT)
+		sfp_sm_event(sfp, state & SFP_F_PRESENT ?
+				SFP_E_INSERT : SFP_E_REMOVE);
+
+	if (changed & SFP_F_TX_FAULT)
+		sfp_sm_event(sfp, state & SFP_F_TX_FAULT ?
+				SFP_E_TX_FAULT : SFP_E_TX_CLEAR);
+
+	if (changed & SFP_F_LOS)
+		sfp_sm_event(sfp, state & SFP_F_LOS ?
+				SFP_E_LOS_HIGH : SFP_E_LOS_LOW);
+}
+
+static irqreturn_t sfp_irq(int irq, void *data)
+{
+	struct sfp *sfp = data;
+
+	sfp_check_state(sfp);
+
+	return IRQ_HANDLED;
+}
+
+static void sfp_poll(struct work_struct *work)
+{
+	struct sfp *sfp = container_of(work, struct sfp, poll.work);
+
+	sfp_check_state(sfp);
+	mod_delayed_work(system_wq, &sfp->poll, poll_jiffies);
+}
+
+static int sfp_netdev_notify(struct notifier_block *nb, unsigned long act, void *data)
+{
+	struct sfp *sfp = container_of(nb, struct sfp, netdev_nb);
+	struct netdev_notifier_info *info = data;
+	struct net_device *ndev = info->dev;
+
+	if (!sfp->ndev || ndev != sfp->ndev)
+		return NOTIFY_DONE;
+
+	switch (act) {
+	case NETDEV_UP:
+		sfp_sm_event(sfp, SFP_E_DEV_UP);
+		break;
+
+	case NETDEV_GOING_DOWN:
+		sfp_sm_event(sfp, SFP_E_DEV_DOWN);
+		break;
+
+	case NETDEV_UNREGISTER:
+		if (sfp->mod_phy && sfp->phylink)
+			phylink_disconnect_phy(sfp->phylink);
+		sfp->phylink = NULL;
+		dev_put(sfp->ndev);
+		sfp->ndev = NULL;
+		break;
+	}
+	return NOTIFY_OK;
+}
+
+static struct sfp *sfp_alloc(struct device *dev)
+{
+	struct sfp *sfp;
+
+	sfp = kzalloc(sizeof(*sfp), GFP_KERNEL);
+	if (!sfp)
+		return ERR_PTR(-ENOMEM);
+
+	sfp->dev = dev;
+
+	mutex_init(&sfp->sm_mutex);
+	INIT_DELAYED_WORK(&sfp->poll, sfp_poll);
+	INIT_DELAYED_WORK(&sfp->timeout, sfp_timeout);
+
+	sfp->netdev_nb.notifier_call = sfp_netdev_notify;
+
+	return sfp;
+}
+
+static void sfp_destroy(struct sfp *sfp)
+{
+	cancel_delayed_work_sync(&sfp->poll);
+	cancel_delayed_work_sync(&sfp->timeout);
+	if (sfp->i2c_mii) {
+		mdiobus_unregister(sfp->i2c_mii);
+		mdiobus_free(sfp->i2c_mii);
+	}
+	if (sfp->i2c)
+		i2c_put_adapter(sfp->i2c);
+	of_node_put(sfp->dev->of_node);
+	kfree(sfp);
+}
+
+static void sfp_cleanup(void *data)
+{
+	struct sfp *sfp = data;
+
+	sfp_destroy(sfp);
+}
+
+static int sfp_probe(struct platform_device *pdev)
+{
+	struct sfp *sfp;
+	bool poll = false;
+	int irq, err, i;
+
+	sfp = sfp_alloc(&pdev->dev);
+	if (IS_ERR(sfp))
+		return PTR_ERR(sfp);
+
+	platform_set_drvdata(pdev, sfp);
+
+	err = devm_add_action(sfp->dev, sfp_cleanup, sfp);
+	if (err < 0)
+		return err;
+
+	if (pdev->dev.of_node) {
+		struct device_node *node = pdev->dev.of_node;
+		struct device_node *np;
+
+		np = of_parse_phandle(node, "i2c-bus", 0);
+		if (np) {
+			struct i2c_adapter *i2c;
+
+			i2c = of_find_i2c_adapter_by_node(np);
+			of_node_put(np);
+			if (!i2c)
+				return -EPROBE_DEFER;
+
+			err = sfp_i2c_configure(sfp, i2c);
+			if (err < 0) {
+				i2c_put_adapter(i2c);
+				return err;
+			}
+		}
+
+		for (i = 0; i < GPIO_MAX; i++) {
+			sfp->gpio[i] = devm_gpiod_get_optional(sfp->dev,
+					   gpio_of_names[i], gpio_flags[i]);
+			if (IS_ERR(sfp->gpio[i]))
+				return PTR_ERR(sfp->gpio[i]);
+		}
+
+		sfp->get_state = sfp_gpio_get_state;
+		sfp->set_state = sfp_gpio_set_state;
+
+		np = of_parse_phandle(node, "sfp,ethernet", 0);
+		if (!np) {
+			dev_err(sfp->dev, "missing sfp,ethernet property\n");
+			return -EINVAL;
+		}
+
+		sfp->ndev = of_find_net_device_by_node(np);
+		if (!sfp->ndev) {
+			dev_err(sfp->dev, "ethernet device not found\n");
+			return -EPROBE_DEFER;
+		}
+
+		dev_hold(sfp->ndev);
+		put_device(&sfp->ndev->dev);
+
+		sfp->phylink = phylink_lookup_by_netdev(sfp->ndev);
+		if (!sfp->phylink) {
+			dev_err(sfp->dev, "ethernet device not found\n");
+			return -EPROBE_DEFER;
+		}
+
+		phylink_disable(sfp->phylink);
+	}
+
+	sfp->state = sfp_get_state(sfp);
+	if (sfp->gpio[GPIO_TX_DISABLE] &&
+	    gpiod_get_value_cansleep(sfp->gpio[GPIO_TX_DISABLE]))
+		sfp->state |= SFP_F_TX_DISABLE;
+	if (sfp->gpio[GPIO_RATE_SELECT] &&
+	    gpiod_get_value_cansleep(sfp->gpio[GPIO_RATE_SELECT]))
+		sfp->state |= SFP_F_RATE_SELECT;
+	sfp_set_state(sfp, sfp->state);
+	sfp_module_tx_disable(sfp);
+	if (sfp->state & SFP_F_PRESENT)
+		sfp_sm_event(sfp, SFP_E_INSERT);
+
+	for (i = 0; i < GPIO_MAX; i++) {
+		if (gpio_flags[i] != GPIOD_IN || !sfp->gpio[i])
+			continue;
+
+		irq = gpiod_to_irq(sfp->gpio[i]);
+		if (!irq) {
+			poll = true;
+			continue;
+		}
+
+		err = devm_request_threaded_irq(sfp->dev, irq, NULL, sfp_irq,
+						IRQF_ONESHOT |
+						IRQF_TRIGGER_RISING |
+						IRQF_TRIGGER_FALLING,
+						dev_name(sfp->dev), sfp);
+		if (err)
+			poll = true;
+	}
+
+	if (poll)
+		mod_delayed_work(system_wq, &sfp->poll, poll_jiffies);
+
+	register_netdevice_notifier(&sfp->netdev_nb);
+
+	return 0;
+}
+
+static int sfp_remove(struct platform_device *pdev)
+{
+	struct sfp *sfp = platform_get_drvdata(pdev);
+
+	unregister_netdevice_notifier(&sfp->netdev_nb);
+	if (sfp->ndev)
+		dev_put(sfp->ndev);
+
+	return 0;
+}
+
+static const struct of_device_id sfp_of_match[] = {
+	{ .compatible = "sff,sfp", },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, sfp_of_match);
+
+static struct platform_driver sfp_driver = {
+	.probe = sfp_probe,
+	.remove = sfp_remove,
+	.driver = {
+		.name = "sfp",
+		.of_match_table = sfp_of_match,
+	},
+};
+
+static int sfp_init(void)
+{
+	poll_jiffies = msecs_to_jiffies(100);
+
+	return platform_driver_register(&sfp_driver);
+}
+module_init(sfp_init);
+
+static void sfp_exit(void)
+{
+	platform_driver_unregister(&sfp_driver);
+}
+module_exit(sfp_exit);
+
+MODULE_ALIAS("platform:sfp");
+MODULE_AUTHOR("Russell King");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/sfp.h b/include/linux/sfp.h
new file mode 100644
index 000000000000..87b88e8d9712
--- /dev/null
+++ b/include/linux/sfp.h
@@ -0,0 +1,339 @@
+#ifndef LINUX_SFP_H
+#define LINUX_SFP_H
+
+struct __packed sfp_eeprom_base {
+	u8 phys_id;
+	u8 phys_ext_id;
+	u8 connector;
+#if defined __BIG_ENDIAN_BITFIELD
+	u8 e10g_base_er:1;
+	u8 e10g_base_lrm:1;
+	u8 e10g_base_lr:1;
+	u8 e10g_base_sr:1;
+	u8 if_1x_sx:1;
+	u8 if_1x_lx:1;
+	u8 if_1x_copper_active:1;
+	u8 if_1x_copper_passive:1;
+
+	u8 escon_mmf_1310_led:1;
+	u8 escon_smf_1310_laser:1;
+	u8 sonet_oc192_short_reach:1;
+	u8 sonet_reach_bit1:1;
+	u8 sonet_reach_bit2:1;
+	u8 sonet_oc48_long_reach:1;
+	u8 sonet_oc48_intermediate_reach:1;
+	u8 sonet_oc48_short_reach:1;
+
+	u8 unallocated_5_7:1;
+	u8 sonet_oc12_smf_long_reach:1;
+	u8 sonet_oc12_smf_intermediate_reach:1;
+	u8 sonet_oc12_short_reach:1;
+	u8 unallocated_5_3:1;
+	u8 sonet_oc3_smf_long_reach:1;
+	u8 sonet_oc3_smf_intermediate_reach:1;
+	u8 sonet_oc3_short_reach:1;
+
+	u8 e_base_px:1;
+	u8 e_base_bx10:1;
+	u8 e100_base_fx:1;
+	u8 e100_base_lx:1;
+	u8 e1000_base_t:1;
+	u8 e1000_base_cx:1;
+	u8 e1000_base_lx:1;
+	u8 e1000_base_sx:1;
+
+	u8 fc_ll_v:1;
+	u8 fc_ll_s:1;
+	u8 fc_ll_i:1;
+	u8 fc_ll_l:1;
+	u8 fc_ll_m:1;
+	u8 fc_tech_sa:1;
+	u8 fc_tech_lc:1;
+	u8 fc_tech_electrical_inter_enclosure:1;
+
+	u8 fc_tech_electrical_intra_enclosure:1;
+	u8 fc_tech_sn:1;
+	u8 fc_tech_sl:1;
+	u8 fc_tech_ll:1;
+	u8 sfp_ct_active:1;
+	u8 sfp_ct_passive:1;
+	u8 unallocated_8_1:1;
+	u8 unallocated_8_0:1;
+
+	u8 fc_media_tw:1;
+	u8 fc_media_tp:1;
+	u8 fc_media_mi:1;
+	u8 fc_media_tv:1;
+	u8 fc_media_m6:1;
+	u8 fc_media_m5:1;
+	u8 unallocated_9_1:1;
+	u8 fc_media_sm:1;
+
+	u8 fc_speed_1200:1;
+	u8 fc_speed_800:1;
+	u8 fc_speed_1600:1;
+	u8 fc_speed_400:1;
+	u8 fc_speed_3200:1;
+	u8 fc_speed_200:1;
+	u8 unallocated_10_1:1;
+	u8 fc_speed_100:1;
+#elif defined __LITTLE_ENDIAN_BITFIELD
+	u8 if_1x_copper_passive:1;
+	u8 if_1x_copper_active:1;
+	u8 if_1x_lx:1;
+	u8 if_1x_sx:1;
+	u8 e10g_base_sr:1;
+	u8 e10g_base_lr:1;
+	u8 e10g_base_lrm:1;
+	u8 e10g_base_er:1;
+
+	u8 sonet_oc3_short_reach:1;
+	u8 sonet_oc3_smf_intermediate_reach:1;
+	u8 sonet_oc3_smf_long_reach:1;
+	u8 unallocated_5_3:1;
+	u8 sonet_oc12_short_reach:1;
+	u8 sonet_oc12_smf_intermediate_reach:1;
+	u8 sonet_oc12_smf_long_reach:1;
+	u8 unallocated_5_7:1;
+
+	u8 sonet_oc48_short_reach:1;
+	u8 sonet_oc48_intermediate_reach:1;
+	u8 sonet_oc48_long_reach:1;
+	u8 sonet_reach_bit2:1;
+	u8 sonet_reach_bit1:1;
+	u8 sonet_oc192_short_reach:1;
+	u8 escon_smf_1310_laser:1;
+	u8 escon_mmf_1310_led:1;
+
+	u8 e1000_base_sx:1;
+	u8 e1000_base_lx:1;
+	u8 e1000_base_cx:1;
+	u8 e1000_base_t:1;
+	u8 e100_base_lx:1;
+	u8 e100_base_fx:1;
+	u8 e_base_bx10:1;
+	u8 e_base_px:1;
+
+	u8 fc_tech_electrical_inter_enclosure:1;
+	u8 fc_tech_lc:1;
+	u8 fc_tech_sa:1;
+	u8 fc_ll_m:1;
+	u8 fc_ll_l:1;
+	u8 fc_ll_i:1;
+	u8 fc_ll_s:1;
+	u8 fc_ll_v:1;
+
+	u8 unallocated_8_0:1;
+	u8 unallocated_8_1:1;
+	u8 sfp_ct_passive:1;
+	u8 sfp_ct_active:1;
+	u8 fc_tech_ll:1;
+	u8 fc_tech_sl:1;
+	u8 fc_tech_sn:1;
+	u8 fc_tech_electrical_intra_enclosure:1;
+
+	u8 fc_media_sm:1;
+	u8 unallocated_9_1:1;
+	u8 fc_media_m5:1;
+	u8 fc_media_m6:1;
+	u8 fc_media_tv:1;
+	u8 fc_media_mi:1;
+	u8 fc_media_tp:1;
+	u8 fc_media_tw:1;
+
+	u8 fc_speed_100:1;
+	u8 unallocated_10_1:1;
+	u8 fc_speed_200:1;
+	u8 fc_speed_3200:1;
+	u8 fc_speed_400:1;
+	u8 fc_speed_1600:1;
+	u8 fc_speed_800:1;
+	u8 fc_speed_1200:1;
+#else
+#error Unknown Endian
+#endif
+	u8 encoding;
+	u8 br_nominal;
+	u8 rate_id;
+	u8 link_len[6];
+	char vendor_name[16];
+	u8 reserved36;
+	char vendor_oui[3];
+	char vendor_pn[16];
+	char vendor_rev[4];
+	union {
+		__be16 optical_wavelength;
+		u8 cable_spec;
+	};
+	u8 reserved62;
+	u8 cc_base;
+};
+
+struct __packed sfp_eeprom_ext {
+	__be16 options;
+	u8 br_max;
+	u8 br_min;
+	char vendor_sn[16];
+	char datecode[8];
+	u8 diagmon;
+	u8 enhopts;
+	u8 sff8472_compliance;
+	u8 cc_ext;
+};
+
+struct __packed sfp_eeprom_id {
+	struct sfp_eeprom_base base;
+	struct sfp_eeprom_ext ext;
+};
+
+/* SFP EEPROM registers */
+enum {
+	SFP_PHYS_ID			= 0x00,
+	SFP_PHYS_EXT_ID			= 0x01,
+	SFP_CONNECTOR			= 0x02,
+	SFP_COMPLIANCE			= 0x03,
+	SFP_ENCODING			= 0x0b,
+	SFP_BR_NOMINAL			= 0x0c,
+	SFP_RATE_ID			= 0x0d,
+	SFP_LINK_LEN_SM_KM		= 0x0e,
+	SFP_LINK_LEN_SM_100M		= 0x0f,
+	SFP_LINK_LEN_50UM_OM2_10M	= 0x10,
+	SFP_LINK_LEN_62_5UM_OM1_10M	= 0x11,
+	SFP_LINK_LEN_COPPER_1M		= 0x12,
+	SFP_LINK_LEN_50UM_OM4_10M	= 0x12,
+	SFP_LINK_LEN_50UM_OM3_10M	= 0x13,
+	SFP_VENDOR_NAME			= 0x14,
+	SFP_VENDOR_OUI			= 0x25,
+	SFP_VENDOR_PN			= 0x28,
+	SFP_VENDOR_REV			= 0x38,
+	SFP_OPTICAL_WAVELENGTH_MSB	= 0x3c,
+	SFP_OPTICAL_WAVELENGTH_LSB	= 0x3d,
+	SFP_CABLE_SPEC			= 0x3c,
+	SFP_CC_BASE			= 0x3f,
+	SFP_OPTIONS			= 0x40,	/* 2 bytes, MSB, LSB */
+	SFP_BR_MAX			= 0x42,
+	SFP_BR_MIN			= 0x43,
+	SFP_VENDOR_SN			= 0x44,
+	SFP_DATECODE			= 0x54,
+	SFP_DIAGMON			= 0x5c,
+	SFP_ENHOPTS			= 0x5d,
+	SFP_SFF8472_COMPLIANCE		= 0x5e,
+	SFP_CC_EXT			= 0x5f,
+
+	SFP_PHYS_ID_SFP			= 0x03,
+	SFP_PHYS_EXT_ID_SFP		= 0x04,
+	SFP_CONNECTOR_UNSPEC		= 0x00,
+	/* codes 01-05 not supportable on SFP, but some modules have single SC */
+	SFP_CONNECTOR_SC		= 0x01,
+	SFP_CONNECTOR_FIBERJACK		= 0x06,
+	SFP_CONNECTOR_LC		= 0x07,
+	SFP_CONNECTOR_MT_RJ		= 0x08,
+	SFP_CONNECTOR_MU		= 0x09,
+	SFP_CONNECTOR_SG		= 0x0a,
+	SFP_CONNECTOR_OPTICAL_PIGTAIL	= 0x0b,
+	SFP_CONNECTOR_HSSDC_II		= 0x20,
+	SFP_CONNECTOR_COPPER_PIGTAIL	= 0x21,
+	SFP_ENCODING_UNSPEC		= 0x00,
+	SFP_ENCODING_8B10B		= 0x01,
+	SFP_ENCODING_4B5B		= 0x02,
+	SFP_ENCODING_NRZ		= 0x03,
+	SFP_ENCODING_MANCHESTER		= 0x04,
+	SFP_OPTIONS_HIGH_POWER_LEVEL	= BIT(13),
+	SFP_OPTIONS_PAGING_A2		= BIT(12),
+	SFP_OPTIONS_RETIMER		= BIT(11),
+	SFP_OPTIONS_COOLED_XCVR		= BIT(10),
+	SFP_OPTIONS_POWER_DECL		= BIT(9),
+	SFP_OPTIONS_RX_LINEAR_OUT	= BIT(8),
+	SFP_OPTIONS_RX_DECISION_THRESH	= BIT(7),
+	SFP_OPTIONS_TUNABLE_TX		= BIT(6),
+	SFP_OPTIONS_RATE_SELECT		= BIT(5),
+	SFP_OPTIONS_TX_DISABLE		= BIT(4),
+	SFP_OPTIONS_TX_FAULT		= BIT(3),
+	SFP_OPTIONS_LOS_INVERTED	= BIT(2),
+	SFP_OPTIONS_LOS_NORMAL		= BIT(1),
+	SFP_DIAGMON_DDM			= BIT(6),
+	SFP_DIAGMON_INT_CAL		= BIT(5),
+	SFP_DIAGMON_EXT_CAL		= BIT(4),
+	SFP_DIAGMON_RXPWR_AVG		= BIT(3),
+	SFP_DIAGMON_ADDRMODE		= BIT(2),
+	SFP_ENHOPTS_ALARMWARN		= BIT(7),
+	SFP_ENHOPTS_SOFT_TX_DISABLE	= BIT(6),
+	SFP_ENHOPTS_SOFT_TX_FAULT	= BIT(5),
+	SFP_ENHOPTS_SOFT_RX_LOS		= BIT(4),
+	SFP_ENHOPTS_SOFT_RATE_SELECT	= BIT(3),
+	SFP_ENHOPTS_APP_SELECT_SFF8079	= BIT(2),
+	SFP_ENHOPTS_SOFT_RATE_SFF8431	= BIT(1),
+	SFP_SFF8472_COMPLIANCE_NONE	= 0x00,
+	SFP_SFF8472_COMPLIANCE_REV9_3	= 0x01,
+	SFP_SFF8472_COMPLIANCE_REV9_5	= 0x02,
+	SFP_SFF8472_COMPLIANCE_REV10_2	= 0x03,
+	SFP_SFF8472_COMPLIANCE_REV10_4	= 0x04,
+	SFP_SFF8472_COMPLIANCE_REV11_0	= 0x05,
+	SFP_SFF8472_COMPLIANCE_REV11_3	= 0x06,
+	SFP_SFF8472_COMPLIANCE_REV11_4	= 0x07,
+	SFP_SFF8472_COMPLIANCE_REV12_0	= 0x08,
+};
+
+/* SFP Diagnostics */
+enum {
+	/* Alarm and warnings stored MSB at lower address then LSB */
+	SFP_TEMP_HIGH_ALARM		= 0x00,
+	SFP_TEMP_LOW_ALARM		= 0x02,
+	SFP_TEMP_HIGH_WARN		= 0x04,
+	SFP_TEMP_LOW_WARN		= 0x06,
+	SFP_VOLT_HIGH_ALARM		= 0x08,
+	SFP_VOLT_LOW_ALARM		= 0x0a,
+	SFP_VOLT_HIGH_WARN		= 0x0c,
+	SFP_VOLT_LOW_WARN		= 0x0e,
+	SFP_BIAS_HIGH_ALARM		= 0x10,
+	SFP_BIAS_LOW_ALARM		= 0x12,
+	SFP_BIAS_HIGH_WARN		= 0x14,
+	SFP_BIAS_LOW_WARN		= 0x16,
+	SFP_TXPWR_HIGH_ALARM		= 0x18,
+	SFP_TXPWR_LOW_ALARM		= 0x1a,
+	SFP_TXPWR_HIGH_WARN		= 0x1c,
+	SFP_TXPWR_LOW_WARN		= 0x1e,
+	SFP_RXPWR_HIGH_ALARM		= 0x20,
+	SFP_RXPWR_LOW_ALARM		= 0x22,
+	SFP_RXPWR_HIGH_WARN		= 0x24,
+	SFP_RXPWR_LOW_WARN		= 0x26,
+	SFP_LASER_TEMP_HIGH_ALARM	= 0x28,
+	SFP_LASER_TEMP_LOW_ALARM	= 0x2a,
+	SFP_LASER_TEMP_HIGH_WARN	= 0x2c,
+	SFP_LASER_TEMP_LOW_WARN		= 0x2e,
+	SFP_TEC_CUR_HIGH_ALARM		= 0x30,
+	SFP_TEC_CUR_LOW_ALARM		= 0x32,
+	SFP_TEC_CUR_HIGH_WARN		= 0x34,
+	SFP_TEC_CUR_LOW_WARN		= 0x36,
+	SFP_CAL_RXPWR4			= 0x38,
+	SFP_CAL_RXPWR3			= 0x3c,
+	SFP_CAL_RXPWR2			= 0x40,
+	SFP_CAL_RXPWR1			= 0x44,
+	SFP_CAL_RXPWR0			= 0x48,
+	SFP_CAL_TXI_SLOPE		= 0x4c,
+	SFP_CAL_TXI_OFFSET		= 0x4e,
+	SFP_CAL_TXPWR_SLOPE		= 0x50,
+	SFP_CAL_TXPWR_OFFSET		= 0x52,
+	SFP_CAL_T_SLOPE			= 0x54,
+	SFP_CAL_T_OFFSET		= 0x56,
+	SFP_CAL_V_SLOPE			= 0x58,
+	SFP_CAL_V_OFFSET		= 0x5a,
+	SFP_CHKSUM			= 0x5f,
+
+	SFP_TEMP			= 0x60,
+	SFP_VCC				= 0x62,
+	SFP_TX_BIAS			= 0x64,
+	SFP_TX_POWER			= 0x66,
+	SFP_RX_POWER			= 0x68,
+	SFP_LASER_TEMP			= 0x6a,
+	SFP_TEC_CUR			= 0x6c,
+
+	SFP_STATUS			= 0x6e,
+	SFP_ALARM			= 0x70,
+
+	SFP_EXT_STATUS			= 0x76,
+	SFP_VSL				= 0x78,
+	SFP_PAGE			= 0x7f,
+};
+
+#endif

---

From: Russell King <[email-masked]>
Date: Mon, 15 Aug 2016 18:57:41 +0100

Signed-off-by: Russell King <[email-masked]>
---
 drivers/net/phy/sfp.c | 247 +++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 246 insertions(+), 1 deletion(-)

diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c
index 86029f21c4d9..6f32e945df50 100644
--- a/drivers/net/phy/sfp.c
+++ b/drivers/net/phy/sfp.c
@@ -248,6 +248,182 @@ static unsigned int sfp_check(void *buf, size_t len)
 	return check;
 }
 
+static const char *sfp_link_len(char *buf, size_t size, unsigned int length,
+	unsigned int multiplier)
+{
+	if (length == 0)
+		return "unsupported/unspecified";
+
+	if (length == 255) {
+		*buf++ = '>';
+		size -= 1;
+		length -= 1;
+	}
+
+	length *= multiplier;
+
+	if (length >= 1000)
+		snprintf(buf, size, "%u.%0*ukm",
+			length / 1000,
+			multiplier > 100 ? 1 :
+			multiplier > 10 ? 2 : 3,
+			length % 1000);
+	else
+		snprintf(buf, size, "%um", length);
+
+	return buf;
+}
+
+struct bitfield {
+	unsigned int mask;
+	unsigned int val;
+	const char *str;
+};
+
+static const struct bitfield sfp_options[] = {
+	{
+		.mask = SFP_OPTIONS_HIGH_POWER_LEVEL,
+		.val = SFP_OPTIONS_HIGH_POWER_LEVEL,
+		.str = "hpl",
+	}, {
+		.mask = SFP_OPTIONS_PAGING_A2,
+		.val = SFP_OPTIONS_PAGING_A2,
+		.str = "paginga2",
+	}, {
+		.mask = SFP_OPTIONS_RETIMER,
+		.val = SFP_OPTIONS_RETIMER,
+		.str = "retimer",
+	}, {
+		.mask = SFP_OPTIONS_COOLED_XCVR,
+		.val = SFP_OPTIONS_COOLED_XCVR,
+		.str = "cooled",
+	}, {
+		.mask = SFP_OPTIONS_POWER_DECL,
+		.val = SFP_OPTIONS_POWER_DECL,
+		.str = "powerdecl",
+	}, {
+		.mask = SFP_OPTIONS_RX_LINEAR_OUT,
+		.val = SFP_OPTIONS_RX_LINEAR_OUT,
+		.str = "rxlinear",
+	}, {
+		.mask = SFP_OPTIONS_RX_DECISION_THRESH,
+		.val = SFP_OPTIONS_RX_DECISION_THRESH,
+		.str = "rxthresh",
+	}, {
+		.mask = SFP_OPTIONS_TUNABLE_TX,
+		.val = SFP_OPTIONS_TUNABLE_TX,
+		.str = "tunabletx",
+	}, {
+		.mask = SFP_OPTIONS_RATE_SELECT,
+		.val = SFP_OPTIONS_RATE_SELECT,
+		.str = "ratesel",
+	}, {
+		.mask = SFP_OPTIONS_TX_DISABLE,
+		.val = SFP_OPTIONS_TX_DISABLE,
+		.str = "txdisable",
+	}, {
+		.mask = SFP_OPTIONS_TX_FAULT,
+		.val = SFP_OPTIONS_TX_FAULT,
+		.str = "txfault",
+	}, {
+		.mask = SFP_OPTIONS_LOS_INVERTED,
+		.val = SFP_OPTIONS_LOS_INVERTED,
+		.str = "los-",
+	}, {
+		.mask = SFP_OPTIONS_LOS_NORMAL,
+		.val = SFP_OPTIONS_LOS_NORMAL,
+		.str = "los+",
+	}, { }
+};
+
+static const struct bitfield diagmon[] = {
+	{
+		.mask = SFP_DIAGMON_DDM,
+		.val = SFP_DIAGMON_DDM,
+		.str = "ddm",
+	}, {
+		.mask = SFP_DIAGMON_INT_CAL,
+		.val = SFP_DIAGMON_INT_CAL,
+		.str = "intcal",
+	}, {
+		.mask = SFP_DIAGMON_EXT_CAL,
+		.val = SFP_DIAGMON_EXT_CAL,
+		.str = "extcal",
+	}, {
+		.mask = SFP_DIAGMON_RXPWR_AVG,
+		.val = SFP_DIAGMON_RXPWR_AVG,
+		.str = "rxpwravg",
+	}, { }
+};
+
+static const char *sfp_bitfield(char *out, size_t outsz, const struct bitfield *bits, unsigned int val)
+{
+	char *p = out;
+	int n;
+
+	*p = '\0';
+	while (bits->mask) {
+		if ((val & bits->mask) == bits->val) {
+			n = snprintf(p, outsz, "%s%s",
+				     out != p ? ", " : "",
+				     bits->str);
+			if (n == outsz)
+				break;
+			p += n;
+			outsz -= n;
+		}
+		bits++;
+	}
+
+	return out;
+}
+
+static const char *sfp_connector(unsigned int connector)
+{
+	switch (connector) {
+	case SFP_CONNECTOR_UNSPEC:
+		return "unknown/unspecified";
+	case SFP_CONNECTOR_SC:
+		return "SC";
+	case SFP_CONNECTOR_FIBERJACK:
+		return "Fiberjack";
+	case SFP_CONNECTOR_LC:
+		return "LC";
+	case SFP_CONNECTOR_MT_RJ:
+		return "MT-RJ";
+	case SFP_CONNECTOR_MU:
+		return "MU";
+	case SFP_CONNECTOR_SG:
+		return "SG";
+	case SFP_CONNECTOR_OPTICAL_PIGTAIL:
+		return "Optical pigtail";
+	case SFP_CONNECTOR_HSSDC_II:
+		return "HSSDC II";
+	case SFP_CONNECTOR_COPPER_PIGTAIL:
+		return "Copper pigtail";
+	default:
+		return "unknown";
+	}
+}
+
+static const char *sfp_encoding(unsigned int encoding)
+{
+	switch (encoding) {
+	case SFP_ENCODING_UNSPEC:
+		return "unspecified";
+	case SFP_ENCODING_8B10B:
+		return "8b10b";
+	case SFP_ENCODING_4B5B:
+		return "4b5b";
+	case SFP_ENCODING_NRZ:
+		return "NRZ";
+	case SFP_ENCODING_MANCHESTER:
+		return "MANCHESTER";
+	default:
+		return "unknown";
+	}
+}
+
 /* Helpers */
 static void sfp_module_tx_disable(struct sfp *sfp)
 {
@@ -426,6 +602,7 @@ static int sfp_sm_mod_probe(struct sfp *sfp)
 	char sn[17];
 	char date[9];
 	char rev[5];
+	char options[80];
 	u8 check;
 	int err;
 
@@ -462,10 +639,78 @@ static int sfp_sm_mod_probe(struct sfp *sfp)
 	rev[4] = '\0';
 	memcpy(sn, sfp->id.ext.vendor_sn, 16);
 	sn[16] = '\0';
-	memcpy(date, sfp->id.ext.datecode, 8);
+	date[0] = sfp->id.ext.datecode[4];
+	date[1] = sfp->id.ext.datecode[5];
+	date[2] = '-';
+	date[3] = sfp->id.ext.datecode[2];
+	date[4] = sfp->id.ext.datecode[3];
+	date[5] = '-';
+	date[6] = sfp->id.ext.datecode[0];
+	date[7] = sfp->id.ext.datecode[1];
 	date[8] = '\0';
 
 	dev_info(sfp->dev, "module %s %s rev %s sn %s dc %s\n", vendor, part, rev, sn, date);
+	dev_info(sfp->dev, "  %s connector, encoding %s, nominal bitrate %u.%uGbps +%u%% -%u%%\n",
+		 sfp_connector(sfp->id.base.connector),
+		 sfp_encoding(sfp->id.base.encoding),
+		 sfp->id.base.br_nominal / 10,
+		 sfp->id.base.br_nominal % 10,
+		 sfp->id.ext.br_max, sfp->id.ext.br_min);
+	dev_info(sfp->dev, "  1000BaseSX%c 1000BaseLX%c 1000BaseCX%c 1000BaseT%c 100BaseTLX%c 1000BaseFX%c BaseBX10%c BasePX%c\n",
+		 sfp->id.base.e1000_base_sx ? '+' : '-',
+		 sfp->id.base.e1000_base_lx ? '+' : '-',
+		 sfp->id.base.e1000_base_cx ? '+' : '-',
+		 sfp->id.base.e1000_base_t ? '+' : '-',
+		 sfp->id.base.e100_base_lx ? '+' : '-',
+		 sfp->id.base.e100_base_fx ? '+' : '-',
+		 sfp->id.base.e_base_bx10 ? '+' : '-',
+		 sfp->id.base.e_base_px ? '+' : '-');
+
+	if (!sfp->id.base.sfp_ct_passive && !sfp->id.base.sfp_ct_active &&
+	    !sfp->id.base.e1000_base_t) {
+		char len_9um[16], len_om[16];
+
+		dev_info(sfp->dev, "  Wavelength %unm, fiber lengths:\n",
+			 be16_to_cpup(&sfp->id.base.optical_wavelength));
+
+		if (sfp->id.base.link_len[0] == 255)
+			strcpy(len_9um, ">254km");
+		else if (sfp->id.base.link_len[1] && sfp->id.base.link_len[1] != 255)
+			sprintf(len_9um, "%um",
+				sfp->id.base.link_len[1] * 100);
+		else if (sfp->id.base.link_len[0])
+			sprintf(len_9um, "%ukm", sfp->id.base.link_len[0]);
+		else if (sfp->id.base.link_len[1] == 255)
+			strcpy(len_9um, ">25.4km");
+		else
+			strcpy(len_9um, "unsupported");
+
+		dev_info(sfp->dev, "    9µm SM    : %s\n", len_9um);
+		dev_info(sfp->dev, " 62.5µm MM OM1: %s\n",
+			 sfp_link_len(len_om, sizeof(len_om),
+				      sfp->id.base.link_len[3], 10));
+		dev_info(sfp->dev, "   50µm MM OM2: %s\n",
+			 sfp_link_len(len_om, sizeof(len_om),
+				      sfp->id.base.link_len[2], 10));
+		dev_info(sfp->dev, "   50µm MM OM3: %s\n",
+			 sfp_link_len(len_om, sizeof(len_om),
+				      sfp->id.base.link_len[5], 10));
+		dev_info(sfp->dev, "   50µm MM OM4: %s\n",
+			 sfp_link_len(len_om, sizeof(len_om),
+				      sfp->id.base.link_len[4], 10));
+	} else {
+		char len[16];
+		dev_info(sfp->dev, "  Copper length: %s\n",
+			 sfp_link_len(len, sizeof(len),
+				      sfp->id.base.link_len[4], 1));
+	}
+
+	dev_info(sfp->dev, "  Options: %s\n",
+		 sfp_bitfield(options, sizeof(options), sfp_options,
+			      be16_to_cpu(sfp->id.ext.options)));
+	dev_info(sfp->dev, "  Diagnostics: %s\n",
+		 sfp_bitfield(options, sizeof(options), diagmon,
+			      sfp->id.ext.diagmon));
 
 	/* We only support SFP modules, not the legacy GBIC modules. */
 	if (sfp->id.base.phys_id != SFP_PHYS_ID_SFP ||

---

From: Russell King <[email-masked]>
Date: Mon, 15 Aug 2016 18:57:41 +0100

Convert mvneta to use phylink, which models the MAC to PHY link in
a generic, reusable form.

Signed-off-by: Russell King <[email-masked]>
---
 drivers/net/ethernet/marvell/Kconfig  |   2 +-
 drivers/net/ethernet/marvell/mvneta.c | 455 +++++++++++++++++-----------------
 2 files changed, 226 insertions(+), 231 deletions(-)

diff --git a/drivers/net/ethernet/marvell/Kconfig b/drivers/net/ethernet/marvell/Kconfig
index 2664827ddecd..423124d4d56f 100644
--- a/drivers/net/ethernet/marvell/Kconfig
+++ b/drivers/net/ethernet/marvell/Kconfig
@@ -57,7 +57,7 @@ config MVNETA
 	tristate "Marvell Armada 370/38x/XP network interface support"
 	depends on PLAT_ORION
 	select MVMDIO
-	select FIXED_PHY
+	select PHYLINK
 	---help---
 	  This driver supports the network interface units in the
 	  Marvell ARMADA XP, ARMADA 370 and ARMADA 38x SoC family.
diff --git a/drivers/net/ethernet/marvell/mvneta.c b/drivers/net/ethernet/marvell/mvneta.c
index d41c28d00b57..10793b20a425 100644
--- a/drivers/net/ethernet/marvell/mvneta.c
+++ b/drivers/net/ethernet/marvell/mvneta.c
@@ -28,6 +28,7 @@
 #include <linux/of_mdio.h>
 #include <linux/of_net.h>
 #include <linux/phy.h>
+#include <linux/phylink.h>
 #include <linux/platform_device.h>
 #include <linux/skbuff.h>
 #include <net/hwbm.h>
@@ -188,6 +189,7 @@
 #define MVNETA_GMAC_CTRL_0                       0x2c00
 #define      MVNETA_GMAC_MAX_RX_SIZE_SHIFT       2
 #define      MVNETA_GMAC_MAX_RX_SIZE_MASK        0x7ffc
+#define      MVNETA_GMAC0_PORT_1000BASE_X        BIT(1)
 #define      MVNETA_GMAC0_PORT_ENABLE            BIT(0)
 #define MVNETA_GMAC_CTRL_2                       0x2c08
 #define      MVNETA_GMAC2_INBAND_AN_ENABLE       BIT(0)
@@ -203,13 +205,19 @@
 #define      MVNETA_GMAC_TX_FLOW_CTRL_ENABLE     BIT(5)
 #define      MVNETA_GMAC_RX_FLOW_CTRL_ACTIVE     BIT(6)
 #define      MVNETA_GMAC_TX_FLOW_CTRL_ACTIVE     BIT(7)
+#define      MVNETA_GMAC_AN_COMPLETE             BIT(11)
+#define      MVNETA_GMAC_SYNC_OK                 BIT(14)
 #define MVNETA_GMAC_AUTONEG_CONFIG               0x2c0c
 #define      MVNETA_GMAC_FORCE_LINK_DOWN         BIT(0)
 #define      MVNETA_GMAC_FORCE_LINK_PASS         BIT(1)
 #define      MVNETA_GMAC_INBAND_AN_ENABLE        BIT(2)
+#define      MVNETA_GMAC_AN_BYPASS_ENABLE        BIT(3)
+#define      MVNETA_GMAC_INBAND_RESTART_AN       BIT(4)
 #define      MVNETA_GMAC_CONFIG_MII_SPEED        BIT(5)
 #define      MVNETA_GMAC_CONFIG_GMII_SPEED       BIT(6)
 #define      MVNETA_GMAC_AN_SPEED_EN             BIT(7)
+#define      MVNETA_GMAC_CONFIG_FLOW_CTRL        BIT(8)
+#define      MVNETA_GMAC_ADVERT_SYM_FLOW_CTRL    BIT(9)
 #define      MVNETA_GMAC_AN_FLOW_CTRL_EN         BIT(11)
 #define      MVNETA_GMAC_CONFIG_FULL_DUPLEX      BIT(12)
 #define      MVNETA_GMAC_AN_DUPLEX_EN            BIT(13)
@@ -398,15 +406,9 @@ struct mvneta_port {
 	u16 tx_ring_size;
 	u16 rx_ring_size;
 
-	struct mii_bus *mii_bus;
-	struct phy_device *phy_dev;
-	phy_interface_t phy_interface;
-	struct device_node *phy_node;
-	unsigned int link;
-	unsigned int duplex;
-	unsigned int speed;
+	struct device_node *dn;
 	unsigned int tx_csum_limit;
-	unsigned int use_inband_status:1;
+	struct phylink *phylink;
 
 	struct mvneta_bm *bm_priv;
 	struct mvneta_bm_pool *pool_long;
@@ -1238,44 +1240,6 @@ static void mvneta_set_other_mcast_table(struct mvneta_port *pp, int queue)
 		mvreg_write(pp, MVNETA_DA_FILT_OTH_MCAST + offset, val);
 }
 
-static void mvneta_set_autoneg(struct mvneta_port *pp, int enable)
-{
-	u32 val;
-
-	if (enable) {
-		val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG);
-		val &= ~(MVNETA_GMAC_FORCE_LINK_PASS |
-			 MVNETA_GMAC_FORCE_LINK_DOWN |
-			 MVNETA_GMAC_AN_FLOW_CTRL_EN);
-		val |= MVNETA_GMAC_INBAND_AN_ENABLE |
-		       MVNETA_GMAC_AN_SPEED_EN |
-		       MVNETA_GMAC_AN_DUPLEX_EN;
-		mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val);
-
-		val = mvreg_read(pp, MVNETA_GMAC_CLOCK_DIVIDER);
-		val |= MVNETA_GMAC_1MS_CLOCK_ENABLE;
-		mvreg_write(pp, MVNETA_GMAC_CLOCK_DIVIDER, val);
-
-		val = mvreg_read(pp, MVNETA_GMAC_CTRL_2);
-		val |= MVNETA_GMAC2_INBAND_AN_ENABLE;
-		mvreg_write(pp, MVNETA_GMAC_CTRL_2, val);
-	} else {
-		val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG);
-		val &= ~(MVNETA_GMAC_INBAND_AN_ENABLE |
-		       MVNETA_GMAC_AN_SPEED_EN |
-		       MVNETA_GMAC_AN_DUPLEX_EN);
-		mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val);
-
-		val = mvreg_read(pp, MVNETA_GMAC_CLOCK_DIVIDER);
-		val &= ~MVNETA_GMAC_1MS_CLOCK_ENABLE;
-		mvreg_write(pp, MVNETA_GMAC_CLOCK_DIVIDER, val);
-
-		val = mvreg_read(pp, MVNETA_GMAC_CTRL_2);
-		val &= ~MVNETA_GMAC2_INBAND_AN_ENABLE;
-		mvreg_write(pp, MVNETA_GMAC_CTRL_2, val);
-	}
-}
-
 static void mvneta_percpu_unmask_interrupt(void *arg)
 {
 	struct mvneta_port *pp = arg;
@@ -1423,7 +1387,6 @@ static void mvneta_defaults_set(struct mvneta_port *pp)
 	val &= ~MVNETA_PHY_POLLING_ENABLE;
 	mvreg_write(pp, MVNETA_UNIT_CONTROL, val);
 
-	mvneta_set_autoneg(pp, pp->use_inband_status);
 	mvneta_set_ucast_table(pp, -1);
 	mvneta_set_special_mcast_table(pp, -1);
 	mvneta_set_other_mcast_table(pp, -1);
@@ -2616,26 +2579,11 @@ static irqreturn_t mvneta_isr(int irq, void *dev_id)
 	return IRQ_HANDLED;
 }
 
-static int mvneta_fixed_link_update(struct mvneta_port *pp,
-				    struct phy_device *phy)
+static void mvneta_link_change(struct mvneta_port *pp)
 {
-	struct fixed_phy_status status;
-	struct fixed_phy_status changed = {};
 	u32 gmac_stat = mvreg_read(pp, MVNETA_GMAC_STATUS);
 
-	status.link = !!(gmac_stat & MVNETA_GMAC_LINK_UP);
-	if (gmac_stat & MVNETA_GMAC_SPEED_1000)
-		status.speed = SPEED_1000;
-	else if (gmac_stat & MVNETA_GMAC_SPEED_100)
-		status.speed = SPEED_100;
-	else
-		status.speed = SPEED_10;
-	status.duplex = !!(gmac_stat & MVNETA_GMAC_FULL_DUPLEX);
-	changed.link = 1;
-	changed.speed = 1;
-	changed.duplex = 1;
-	fixed_phy_update_state(phy, &status, &changed);
-	return 0;
+	phylink_mac_change(pp->phylink, !!(gmac_stat & MVNETA_GMAC_LINK_UP));
 }
 
 /* NAPI handler
@@ -2664,12 +2612,11 @@ static int mvneta_poll(struct napi_struct *napi, int budget)
 		u32 cause_misc = mvreg_read(pp, MVNETA_INTR_MISC_CAUSE);
 
 		mvreg_write(pp, MVNETA_INTR_MISC_CAUSE, 0);
-		if (pp->use_inband_status && (cause_misc &
-				(MVNETA_CAUSE_PHY_STATUS_CHANGE |
-				 MVNETA_CAUSE_LINK_CHANGE |
-				 MVNETA_CAUSE_PSC_SYNC_CHANGE))) {
-			mvneta_fixed_link_update(pp, pp->phy_dev);
-		}
+
+		if (cause_misc & (MVNETA_CAUSE_PHY_STATUS_CHANGE |
+				  MVNETA_CAUSE_LINK_CHANGE |
+				  MVNETA_CAUSE_PSC_SYNC_CHANGE))
+			mvneta_link_change(pp);
 	}
 
 	/* Release Tx descriptors */
@@ -2985,7 +2932,7 @@ static void mvneta_start_dev(struct mvneta_port *pp)
 		    MVNETA_CAUSE_LINK_CHANGE |
 		    MVNETA_CAUSE_PSC_SYNC_CHANGE);
 
-	phy_start(pp->phy_dev);
+	phylink_start(pp->phylink);
 	netif_tx_start_all_queues(pp->dev);
 }
 
@@ -2993,7 +2940,7 @@ static void mvneta_stop_dev(struct mvneta_port *pp)
 {
 	unsigned int cpu;
 
-	phy_stop(pp->phy_dev);
+	phylink_stop(pp->phylink);
 
 	for_each_online_cpu(cpu) {
 		struct mvneta_pcpu_port *port = per_cpu_ptr(pp->ports, cpu);
@@ -3163,99 +3110,213 @@ static int mvneta_set_mac_addr(struct net_device *dev, void *addr)
 	return 0;
 }
 
-static void mvneta_adjust_link(struct net_device *ndev)
+static int mvneta_mac_support(struct net_device *ndev, unsigned int mode,
+			      struct phylink_link_state *state)
+{
+	switch (mode) {
+	case MLO_AN_8023Z:
+		state->supported = SUPPORTED_1000baseT_Full |
+				   SUPPORTED_Autoneg | SUPPORTED_Pause;
+		state->advertising = ADVERTISED_1000baseT_Full |
+				     ADVERTISED_Autoneg | ADVERTISED_Pause;
+		state->an_enabled = 1;
+		break;
+
+	case MLO_AN_FIXED:
+		break;
+
+	default:
+		state->supported = PHY_10BT_FEATURES |
+				   PHY_100BT_FEATURES |
+				   SUPPORTED_1000baseT_Full |
+				   SUPPORTED_Autoneg;
+		state->advertising = ADVERTISED_10baseT_Half |
+				     ADVERTISED_10baseT_Full |
+				     ADVERTISED_100baseT_Half |
+				     ADVERTISED_100baseT_Full |
+				     ADVERTISED_1000baseT_Full |
+				     ADVERTISED_Autoneg;
+		state->an_enabled = 1;
+		break;
+	}
+	return 0;
+}
+
+static int mvneta_mac_link_state(struct net_device *ndev,
+				 struct phylink_link_state *state)
 {
 	struct mvneta_port *pp = netdev_priv(ndev);
-	struct phy_device *phydev = pp->phy_dev;
-	int status_change = 0;
+	u32 gmac_stat;
+
+	gmac_stat = mvreg_read(pp, MVNETA_GMAC_STATUS);
 
-	if (phydev->link) {
-		if ((pp->speed != phydev->speed) ||
-		    (pp->duplex != phydev->duplex)) {
-			u32 val;
+	if (gmac_stat & MVNETA_GMAC_SPEED_1000)
+		state->speed = SPEED_1000;
+	else if (gmac_stat & MVNETA_GMAC_SPEED_100)
+		state->speed = SPEED_100;
+	else
+		state->speed = SPEED_10;
 
-			val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG);
-			val &= ~(MVNETA_GMAC_CONFIG_MII_SPEED |
-				 MVNETA_GMAC_CONFIG_GMII_SPEED |
-				 MVNETA_GMAC_CONFIG_FULL_DUPLEX);
+	state->an_complete = !!(gmac_stat & MVNETA_GMAC_AN_COMPLETE);
+	state->sync = !!(gmac_stat & MVNETA_GMAC_SYNC_OK);
+	state->link = !!(gmac_stat & MVNETA_GMAC_LINK_UP);
+	state->duplex = !!(gmac_stat & MVNETA_GMAC_FULL_DUPLEX);
 
-			if (phydev->duplex)
-				val |= MVNETA_GMAC_CONFIG_FULL_DUPLEX;
+	return 1;
+}
 
-			if (phydev->speed == SPEED_1000)
-				val |= MVNETA_GMAC_CONFIG_GMII_SPEED;
-			else if (phydev->speed == SPEED_100)
-				val |= MVNETA_GMAC_CONFIG_MII_SPEED;
+static void mvneta_mac_an_restart(struct net_device *ndev, unsigned int mode)
+{
+	struct mvneta_port *pp = netdev_priv(ndev);
 
-			mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val);
+	if (mode == MLO_AN_8023Z) {
+		u32 gmac_an = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG);
 
-			pp->duplex = phydev->duplex;
-			pp->speed  = phydev->speed;
-		}
+		mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG,
+			    gmac_an | MVNETA_GMAC_INBAND_RESTART_AN);
+		mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG,
+			    gmac_an & ~MVNETA_GMAC_INBAND_RESTART_AN);
 	}
+}
 
-	if (phydev->link != pp->link) {
-		if (!phydev->link) {
-			pp->duplex = -1;
-			pp->speed = 0;
-		}
+static void mvneta_mac_config(struct net_device *ndev, unsigned int mode,
+	const struct phylink_link_state *state)
+{
+	struct mvneta_port *pp = netdev_priv(ndev);
+	u32 new_ctrl0, gmac_ctrl0 = mvreg_read(pp, MVNETA_GMAC_CTRL_0);
+	u32 new_ctrl2, gmac_ctrl2 = mvreg_read(pp, MVNETA_GMAC_CTRL_2);
+	u32 new_clk, gmac_clk = mvreg_read(pp, MVNETA_GMAC_CLOCK_DIVIDER);
+	u32 new_an, gmac_an = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG);
+
+	new_ctrl0 = gmac_ctrl0 & ~MVNETA_GMAC0_PORT_1000BASE_X;
+	new_ctrl2 = gmac_ctrl2 & ~MVNETA_GMAC2_INBAND_AN_ENABLE;
+	new_clk = gmac_clk & ~MVNETA_GMAC_1MS_CLOCK_ENABLE;
+	new_an = gmac_an & ~(MVNETA_GMAC_INBAND_AN_ENABLE |
+			     MVNETA_GMAC_INBAND_RESTART_AN |
+			     MVNETA_GMAC_CONFIG_MII_SPEED |
+			     MVNETA_GMAC_CONFIG_GMII_SPEED |
+			     MVNETA_GMAC_AN_SPEED_EN |
+			     MVNETA_GMAC_ADVERT_SYM_FLOW_CTRL |
+			     MVNETA_GMAC_CONFIG_FLOW_CTRL |
+			     MVNETA_GMAC_AN_FLOW_CTRL_EN |
+			     MVNETA_GMAC_CONFIG_FULL_DUPLEX |
+			     MVNETA_GMAC_AN_DUPLEX_EN);
+
+	if (state->advertising & ADVERTISED_Pause)
+		new_an |= MVNETA_GMAC_ADVERT_SYM_FLOW_CTRL;
+
+	switch (mode) {
+	case MLO_AN_SGMII:
+		/* SGMII mode receives the state from the PHY */
+		new_ctrl2 |= MVNETA_GMAC2_INBAND_AN_ENABLE;
+		new_clk |= MVNETA_GMAC_1MS_CLOCK_ENABLE;
+		new_an = (new_an & ~(MVNETA_GMAC_FORCE_LINK_DOWN |
+				     MVNETA_GMAC_FORCE_LINK_PASS)) |
+			 MVNETA_GMAC_INBAND_AN_ENABLE |
+			 MVNETA_GMAC_AN_SPEED_EN |
+			 MVNETA_GMAC_AN_DUPLEX_EN;
+		break;
+
+	case MLO_AN_8023Z:
+		/* 802.3z negotiation - only 1000base-X */
+		new_ctrl0 |= MVNETA_GMAC0_PORT_1000BASE_X;
+		new_clk |= MVNETA_GMAC_1MS_CLOCK_ENABLE;
+		new_an = (new_an & ~(MVNETA_GMAC_FORCE_LINK_DOWN |
+				     MVNETA_GMAC_FORCE_LINK_PASS)) |
+			 MVNETA_GMAC_INBAND_AN_ENABLE |
+			 MVNETA_GMAC_CONFIG_GMII_SPEED |
+			 /* The MAC only supports FD mode */
+			 MVNETA_GMAC_CONFIG_FULL_DUPLEX;
+
+		if (state->an_enabled)
+			new_an |= MVNETA_GMAC_AN_FLOW_CTRL_EN;
+		break;
 
-		pp->link = phydev->link;
-		status_change = 1;
+	default:
+		/* Phy or fixed speed */
+		if (state->duplex)
+			new_an |= MVNETA_GMAC_CONFIG_FULL_DUPLEX;
+
+		if (state->speed == SPEED_1000)
+			new_an |= MVNETA_GMAC_CONFIG_GMII_SPEED;
+		else if (state->speed == SPEED_100)
+			new_an |= MVNETA_GMAC_CONFIG_MII_SPEED;
+		break;
 	}
 
-	if (status_change) {
-		if (phydev->link) {
-			if (!pp->use_inband_status) {
-				u32 val = mvreg_read(pp,
-						  MVNETA_GMAC_AUTONEG_CONFIG);
-				val &= ~MVNETA_GMAC_FORCE_LINK_DOWN;
-				val |= MVNETA_GMAC_FORCE_LINK_PASS;
-				mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG,
-					    val);
-			}
-			mvneta_port_up(pp);
-		} else {
-			if (!pp->use_inband_status) {
-				u32 val = mvreg_read(pp,
-						  MVNETA_GMAC_AUTONEG_CONFIG);
-				val &= ~MVNETA_GMAC_FORCE_LINK_PASS;
-				val |= MVNETA_GMAC_FORCE_LINK_DOWN;
-				mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG,
-					    val);
-			}
-			mvneta_port_down(pp);
-		}
-		phy_print_status(phydev);
+	/* Armada 370 documentation says we can only change the port mode
+	 * and in-band enable when the link is down, so force it down
+	 * while making these changes. We also do this for GMAC_CTRL2 */
+	if ((new_ctrl0 ^ gmac_ctrl0) & MVNETA_GMAC0_PORT_1000BASE_X ||
+	    (new_ctrl2 ^ gmac_ctrl2) & MVNETA_GMAC2_INBAND_AN_ENABLE ||
+	    (new_an  ^ gmac_an) & MVNETA_GMAC_INBAND_AN_ENABLE) {
+		mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG,
+			    (gmac_an & ~MVNETA_GMAC_FORCE_LINK_PASS) |
+			    MVNETA_GMAC_FORCE_LINK_DOWN);
 	}
+
+	if (new_ctrl0 != gmac_ctrl0)
+		mvreg_write(pp, MVNETA_GMAC_CTRL_0, new_ctrl0);
+	if (new_ctrl2 != gmac_ctrl2)
+		mvreg_write(pp, MVNETA_GMAC_CTRL_2, new_ctrl2);
+	if (new_clk != gmac_clk)
+		mvreg_write(pp, MVNETA_GMAC_CLOCK_DIVIDER, new_clk);
+	if (new_an != gmac_an)
+		mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, new_an);
 }
 
-static int mvneta_mdio_probe(struct mvneta_port *pp)
+static void mvneta_mac_link_down(struct net_device *ndev, unsigned int mode)
+{
+	struct mvneta_port *pp = netdev_priv(ndev);
+	u32 val;
+
+	mvneta_port_down(pp);
+
+	if (mode == MLO_AN_PHY || mode == MLO_AN_FIXED) {
+		val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG);
+		val &= ~MVNETA_GMAC_FORCE_LINK_PASS;
+		val |= MVNETA_GMAC_FORCE_LINK_DOWN;
+		mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val);
+	}
+}
+
+static void mvneta_mac_link_up(struct net_device *ndev, unsigned int mode,
+			       struct phy_device *phy)
 {
-	struct phy_device *phy_dev;
+	struct mvneta_port *pp = netdev_priv(ndev);
+	u32 val;
 
-	phy_dev = of_phy_connect(pp->dev, pp->phy_node, mvneta_adjust_link, 0,
-				 pp->phy_interface);
-	if (!phy_dev) {
-		netdev_err(pp->dev, "could not find the PHY\n");
-		return -ENODEV;
+	if (mode == MLO_AN_PHY || mode == MLO_AN_FIXED) {
+		val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG);
+		val &= ~MVNETA_GMAC_FORCE_LINK_DOWN;
+		val |= MVNETA_GMAC_FORCE_LINK_PASS;
+		mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val);
 	}
 
-	phy_dev->supported &= PHY_GBIT_FEATURES;
-	phy_dev->advertising = phy_dev->supported;
+	mvneta_port_up(pp);
+}
 
-	pp->phy_dev = phy_dev;
-	pp->link    = 0;
-	pp->duplex  = 0;
-	pp->speed   = 0;
+static const struct phylink_mac_ops mvneta_phylink_ops = {
+	.mac_get_support = mvneta_mac_support,
+	.mac_link_state = mvneta_mac_link_state,
+	.mac_an_restart = mvneta_mac_an_restart,
+	.mac_config = mvneta_mac_config,
+	.mac_link_down = mvneta_mac_link_down,
+	.mac_link_up = mvneta_mac_link_up,
+};
 
-	return 0;
+static int mvneta_mdio_probe(struct mvneta_port *pp)
+{
+	int err = phylink_of_phy_connect(pp->phylink, pp->dn);
+	if (err)
+		netdev_err(pp->dev, "could not attach PHY\n");
+
+	return err;
 }
 
 static void mvneta_mdio_remove(struct mvneta_port *pp)
 {
-	phy_disconnect(pp->phy_dev);
-	pp->phy_dev = NULL;
+	phylink_disconnect_phy(pp->phylink);
 }
 
 /* Electing a CPU must be done in an atomic way: it should be done
@@ -3497,10 +3558,7 @@ static int mvneta_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
 {
 	struct mvneta_port *pp = netdev_priv(dev);
 
-	if (!pp->phy_dev)
-		return -ENOTSUPP;
-
-	return phy_mii_ioctl(pp->phy_dev, ifr, cmd);
+	return phylink_mii_ioctl(pp->phylink, ifr, cmd);
 }
 
 /* Ethtool methods */
@@ -3510,54 +3568,15 @@ int mvneta_ethtool_get_settings(struct net_device *dev, struct ethtool_cmd *cmd)
 {
 	struct mvneta_port *pp = netdev_priv(dev);
 
-	if (!pp->phy_dev)
-		return -ENODEV;
-
-	return phy_ethtool_gset(pp->phy_dev, cmd);
+	return phylink_ethtool_get_settings(pp->phylink, cmd);
 }
 
 /* Set settings (phy address, speed) for ethtools */
 int mvneta_ethtool_set_settings(struct net_device *dev, struct ethtool_cmd *cmd)
 {
 	struct mvneta_port *pp = netdev_priv(dev);
-	struct phy_device *phydev = pp->phy_dev;
-
-	if (!phydev)
-		return -ENODEV;
-
-	if ((cmd->autoneg == AUTONEG_ENABLE) != pp->use_inband_status) {
-		u32 val;
-
-		mvneta_set_autoneg(pp, cmd->autoneg == AUTONEG_ENABLE);
-
-		if (cmd->autoneg == AUTONEG_DISABLE) {
-			val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG);
-			val &= ~(MVNETA_GMAC_CONFIG_MII_SPEED |
-				 MVNETA_GMAC_CONFIG_GMII_SPEED |
-				 MVNETA_GMAC_CONFIG_FULL_DUPLEX);
-
-			if (phydev->duplex)
-				val |= MVNETA_GMAC_CONFIG_FULL_DUPLEX;
-
-			if (phydev->speed == SPEED_1000)
-				val |= MVNETA_GMAC_CONFIG_GMII_SPEED;
-			else if (phydev->speed == SPEED_100)
-				val |= MVNETA_GMAC_CONFIG_MII_SPEED;
 
-			mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val);
-		}
-
-		pp->use_inband_status = (cmd->autoneg == AUTONEG_ENABLE);
-		netdev_info(pp->dev, "autoneg status set to %i\n",
-			    pp->use_inband_status);
-
-		if (netif_running(dev)) {
-			mvneta_port_down(pp);
-			mvneta_port_up(pp);
-		}
-	}
-
-	return phy_ethtool_sset(pp->phy_dev, cmd);
+	return phylink_ethtool_set_settings(pp->phylink, cmd);
 }
 
 /* Set interrupt coalescing for ethtools */
@@ -3665,26 +3684,28 @@ static void mvneta_ethtool_update_stats(struct mvneta_port *pp)
 {
 	const struct mvneta_statistic *s;
 	void __iomem *base = pp->base;
-	u32 high, low, val;
-	u64 val64;
+	u32 high, low;
+	u64 val;
 	int i;
 
 	for (i = 0, s = mvneta_statistics;
 	     s < mvneta_statistics + ARRAY_SIZE(mvneta_statistics);
 	     s++, i++) {
+		val = 0;
+
 		switch (s->type) {
 		case T_REG_32:
 			val = readl_relaxed(base + s->offset);
-			pp->ethtool_stats[i] += val;
 			break;
 		case T_REG_64:
 			/* Docs say to read low 32-bit then high */
 			low = readl_relaxed(base + s->offset);
 			high = readl_relaxed(base + s->offset + 4);
-			val64 = (u64)high << 32 | low;
-			pp->ethtool_stats[i] += val64;
+			val = (u64)high << 32 | low;
 			break;
 		}
+
+		pp->ethtool_stats[i] += val;
 	}
 }
 
@@ -3960,14 +3981,13 @@ static int mvneta_probe(struct platform_device *pdev)
 	const struct mbus_dram_target_info *dram_target_info;
 	struct resource *res;
 	struct device_node *dn = pdev->dev.of_node;
-	struct device_node *phy_node;
 	struct device_node *bm_node;
 	struct mvneta_port *pp;
 	struct net_device *dev;
+	struct phylink *phylink;
 	const char *dt_mac_addr;
 	char hw_mac_addr[ETH_ALEN];
 	const char *mac_from;
-	const char *managed;
 	int tx_csum_limit;
 	int phy_mode;
 	int err;
@@ -3983,31 +4003,11 @@ static int mvneta_probe(struct platform_device *pdev)
 		goto err_free_netdev;
 	}
 
-	phy_node = of_parse_phandle(dn, "phy", 0);
-	if (!phy_node) {
-		if (!of_phy_is_fixed_link(dn)) {
-			dev_err(&pdev->dev, "no PHY specified\n");
-			err = -ENODEV;
-			goto err_free_irq;
-		}
-
-		err = of_phy_register_fixed_link(dn);
-		if (err < 0) {
-			dev_err(&pdev->dev, "cannot register fixed PHY\n");
-			goto err_free_irq;
-		}
-
-		/* In the case of a fixed PHY, the DT node associated
-		 * to the PHY is the Ethernet MAC DT node.
-		 */
-		phy_node = of_node_get(dn);
-	}
-
 	phy_mode = of_get_phy_mode(dn);
 	if (phy_mode < 0) {
 		dev_err(&pdev->dev, "incorrect phy-mode\n");
 		err = -EINVAL;
-		goto err_put_phy_node;
+		goto err_free_irq;
 	}
 
 	dev->tx_queue_len = MVNETA_MAX_TXD;
@@ -4018,12 +4018,7 @@ static int mvneta_probe(struct platform_device *pdev)
 
 	pp = netdev_priv(dev);
 	spin_lock_init(&pp->lock);
-	pp->phy_node = phy_node;
-	pp->phy_interface = phy_mode;
-
-	err = of_property_read_string(dn, "managed", &managed);
-	pp->use_inband_status = (err == 0 &&
-				 strcmp(managed, "in-band-status") == 0);
+	pp->dn = dn;
 	pp->cpu_notifier.notifier_call = mvneta_percpu_notifier;
 
 	pp->rxq_def = rxq_def;
@@ -4035,7 +4030,7 @@ static int mvneta_probe(struct platform_device *pdev)
 		pp->clk = devm_clk_get(&pdev->dev, NULL);
 	if (IS_ERR(pp->clk)) {
 		err = PTR_ERR(pp->clk);
-		goto err_put_phy_node;
+		goto err_free_irq;
 	}
 
 	clk_prepare_enable(pp->clk);
@@ -4143,6 +4138,14 @@ static int mvneta_probe(struct platform_device *pdev)
 	dev->priv_flags |= IFF_UNICAST_FLT | IFF_LIVE_ADDR_CHANGE;
 	dev->gso_max_segs = MVNETA_MAX_TSO_SEGS;
 
+	phylink = phylink_create(dev, dn, phy_mode, &mvneta_phylink_ops);
+	if (IS_ERR(phylink)) {
+		err = PTR_ERR(phylink);
+		goto err_free_stats;
+	}
+
+	pp->phylink = phylink;
+
 	err = register_netdev(dev);
 	if (err < 0) {
 		dev_err(&pdev->dev, "failed to register\n");
@@ -4154,14 +4157,6 @@ static int mvneta_probe(struct platform_device *pdev)
 
 	platform_set_drvdata(pdev, pp->dev);
 
-	if (pp->use_inband_status) {
-		struct phy_device *phy = of_phy_find_device(dn);
-
-		mvneta_fixed_link_update(pp, phy);
-
-		put_device(&phy->mdio.dev);
-	}
-
 	return 0;
 
 err_netdev:
@@ -4172,14 +4167,14 @@ static int mvneta_probe(struct platform_device *pdev)
 				       1 << pp->id);
 	}
 err_free_stats:
+	if (pp->phylink)
+		phylink_destroy(pp->phylink);
 	free_percpu(pp->stats);
 err_free_ports:
 	free_percpu(pp->ports);
 err_clk:
 	clk_disable_unprepare(pp->clk_bus);
 	clk_disable_unprepare(pp->clk);
-err_put_phy_node:
-	of_node_put(phy_node);
 err_free_irq:
 	irq_dispose_mapping(dev->irq);
 err_free_netdev:
@@ -4199,7 +4194,7 @@ static int mvneta_remove(struct platform_device *pdev)
 	free_percpu(pp->ports);
 	free_percpu(pp->stats);
 	irq_dispose_mapping(dev->irq);
-	of_node_put(pp->phy_node);
+	phylink_destroy(pp->phylink);
 	free_netdev(dev);
 
 	if (pp->bm_priv) {

---

From: Russell King <[email-masked]>
Date: Mon, 15 Aug 2016 18:57:41 +0100

mvneta is the only user of fixed_phy_update_state(), which has been
converted to use phylink instead.  Remove fixed_phy_update_state().

Reviewed-by: Florian Fainelli <[email-masked]>
Signed-off-by: Russell King <[email-masked]>
---
 drivers/net/phy/fixed_phy.c | 31 -------------------------------
 include/linux/phy_fixed.h   |  9 ---------
 2 files changed, 40 deletions(-)

diff --git a/drivers/net/phy/fixed_phy.c b/drivers/net/phy/fixed_phy.c
index c649c101bbab..e9cd2fdc2d68 100644
--- a/drivers/net/phy/fixed_phy.c
+++ b/drivers/net/phy/fixed_phy.c
@@ -115,37 +115,6 @@ int fixed_phy_set_link_update(struct phy_device *phydev,
 }
 EXPORT_SYMBOL_GPL(fixed_phy_set_link_update);
 
-int fixed_phy_update_state(struct phy_device *phydev,
-			   const struct fixed_phy_status *status,
-			   const struct fixed_phy_status *changed)
-{
-	struct fixed_mdio_bus *fmb = &platform_fmb;
-	struct fixed_phy *fp;
-
-	if (!phydev || phydev->mdio.bus != fmb->mii_bus)
-		return -EINVAL;
-
-	list_for_each_entry(fp, &fmb->phys, node) {
-		if (fp->addr == phydev->mdio.addr) {
-			write_seqcount_begin(&fp->seqcount);
-#define _UPD(x) if (changed->x) \
-	fp->status.x = status->x
-			_UPD(link);
-			_UPD(speed);
-			_UPD(duplex);
-			_UPD(pause);
-			_UPD(asym_pause);
-#undef _UPD
-			fixed_phy_update(fp);
-			write_seqcount_end(&fp->seqcount);
-			return 0;
-		}
-	}
-
-	return -ENOENT;
-}
-EXPORT_SYMBOL(fixed_phy_update_state);
-
 int fixed_phy_add(unsigned int irq, int phy_addr,
 		  struct fixed_phy_status *status,
 		  int link_gpio)
diff --git a/include/linux/phy_fixed.h b/include/linux/phy_fixed.h
index 1d41ec44e39d..43a83fa75040 100644
--- a/include/linux/phy_fixed.h
+++ b/include/linux/phy_fixed.h
@@ -23,9 +23,6 @@ extern void fixed_phy_unregister(struct phy_device *phydev);
 extern int fixed_phy_set_link_update(struct phy_device *phydev,
 			int (*link_update)(struct net_device *,
 					   struct fixed_phy_status *));
-extern int fixed_phy_update_state(struct phy_device *phydev,
-			   const struct fixed_phy_status *status,
-			   const struct fixed_phy_status *changed);
 #else
 static inline int fixed_phy_add(unsigned int irq, int phy_id,
 				struct fixed_phy_status *status,
@@ -49,12 +46,6 @@ static inline int fixed_phy_set_link_update(struct phy_device *phydev,
 {
 	return -ENODEV;
 }
-static inline int fixed_phy_update_state(struct phy_device *phydev,
-			   const struct fixed_phy_status *status,
-			   const struct fixed_phy_status *changed)
-{
-	return -ENODEV;
-}
 #endif /* CONFIG_FIXED_PHY */
 
 #endif /* __PHY_FIXED_H */

---

From: Russell King <[email-masked]>
Date: Mon, 15 Aug 2016 18:57:41 +0100

Add ethtool nway_reset support to phylink, to allow userspace to
request a re-negotiation of the link.

Reviewed-by: Florian Fainelli <[email-masked]>
Signed-off-by: Russell King <[email-masked]>
---
 drivers/net/phy/phylink.c | 14 ++++++++++++++
 include/linux/phylink.h   |  1 +
 2 files changed, 15 insertions(+)

diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index 2053a3ecaf45..fd016656be5e 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -675,6 +675,20 @@ int phylink_ethtool_set_settings(struct phylink *pl, struct ethtool_cmd *cmd)
 }
 EXPORT_SYMBOL_GPL(phylink_ethtool_set_settings);
 
+int phylink_ethtool_nway_reset(struct phylink *pl)
+{
+	int ret = 0;
+
+	mutex_lock(&pl->config_mutex);
+	if (pl->phydev)
+		ret = genphy_restart_aneg(pl->phydev);
+	phylink_mac_an_restart(pl);
+	mutex_unlock(&pl->config_mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(phylink_ethtool_nway_reset);
+
 /* This emulates MII registers for a fixed-mode phy operating as per the
  * passed in state. "aneg" defines if we report negotiation is possible.
  *
diff --git a/include/linux/phylink.h b/include/linux/phylink.h
index c7a665a538c1..ad3c85508d19 100644
--- a/include/linux/phylink.h
+++ b/include/linux/phylink.h
@@ -65,6 +65,7 @@ void phylink_stop(struct phylink *);
 
 int phylink_ethtool_get_settings(struct phylink *, struct ethtool_cmd *);
 int phylink_ethtool_set_settings(struct phylink *, struct ethtool_cmd *);
+int phylink_ethtool_nway_reset(struct phylink *);
 int phylink_mii_ioctl(struct phylink *, struct ifreq *, int);
 
 void phylink_set_link_port(struct phylink *pl, u32 support, u8 port);

---

From: Russell King <[email-masked]>
Date: Mon, 15 Aug 2016 18:57:41 +0100

Add ethtool nway_reset support to mvneta via phylink, so that userspace
can request the link in whatever mode to be renegotiated via
ethtool -r ethX.

Signed-off-by: Russell King <[email-masked]>
---
 drivers/net/ethernet/marvell/mvneta.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/drivers/net/ethernet/marvell/mvneta.c b/drivers/net/ethernet/marvell/mvneta.c
index 10793b20a425..75f0653a2289 100644
--- a/drivers/net/ethernet/marvell/mvneta.c
+++ b/drivers/net/ethernet/marvell/mvneta.c
@@ -3579,6 +3579,13 @@ int mvneta_ethtool_set_settings(struct net_device *dev, struct ethtool_cmd *cmd)
 	return phylink_ethtool_set_settings(pp->phylink, cmd);
 }
 
+static int mvneta_ethtool_nway_reset(struct net_device *dev)
+{
+	struct mvneta_port *pp = netdev_priv(dev);
+
+	return phylink_ethtool_nway_reset(pp->phylink);
+}
+
 /* Set interrupt coalescing for ethtools */
 static int mvneta_ethtool_set_coalesce(struct net_device *dev,
 				       struct ethtool_coalesce *c)
@@ -3844,6 +3851,7 @@ const struct ethtool_ops mvneta_eth_tool_ops = {
 	.get_link       = ethtool_op_get_link,
 	.get_settings   = mvneta_ethtool_get_settings,
 	.set_settings   = mvneta_ethtool_set_settings,
+	.nway_reset	= mvneta_ethtool_nway_reset,
 	.set_coalesce   = mvneta_ethtool_set_coalesce,
 	.get_coalesce   = mvneta_ethtool_get_coalesce,
 	.get_drvinfo    = mvneta_ethtool_get_drvinfo,

---

From: Russell King <[email-masked]>
Date: Mon, 15 Aug 2016 18:57:41 +0100

Add flow control support, including ethtool support, to phylink.  We
add support to allow ethtool to get and set the current flow control
settings, and the 802.3 specified resolution for the local and remote
link partner abilities.

Signed-off-by: Russell King <[email-masked]>
---
 drivers/net/phy/phylink.c | 133 +++++++++++++++++++++++++++++++++++++++++++++-
 include/linux/phylink.h   |   8 +++
 2 files changed, 139 insertions(+), 2 deletions(-)

diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index fd016656be5e..087d44a0a6fb 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -94,6 +94,9 @@ static int phylink_parse_fixedlink(struct phylink *pl, struct device_node *np)
 
 		if (of_property_read_bool(fixed_node, "full-duplex"))
 			pl->link_config.duplex = DUPLEX_FULL;
+
+		/* We treat the "pause" and "asym-pause" terminology as
+		 * defining the link partner's ability. */
 		if (of_property_read_bool(fixed_node, "pause"))
 			pl->link_config.pause |= MLO_PAUSE_SYM;
 		if (of_property_read_bool(fixed_node, "asym-pause"))
@@ -230,6 +233,56 @@ static void phylink_get_fixed_state(struct phylink *pl, struct phylink_link_stat
 		state->link = !!gpiod_get_value(pl->link_gpio);
 }
 
+/* Flow control is resolved according to our and the link partners
+ * advertisments using the following drawn from the 802.3 specs:
+ *  Local device  Link partner
+ *  Pause AsymDir Pause AsymDir Result
+ *    1     X       1     X     TX+RX
+ *    0     1       1     1     RX
+ *    1     1       0     1     TX
+ */
+static void phylink_resolve_flow(struct phylink *pl,
+	struct phylink_link_state *state)
+{
+	int new_pause = 0;
+
+	if (pl->link_config.pause & MLO_PAUSE_AN) {
+		int pause = 0;
+
+		if (pl->link_config.advertising & ADVERTISED_Pause)
+			pause |= MLO_PAUSE_SYM;
+		if (pl->link_config.advertising & ADVERTISED_Asym_Pause)
+			pause |= MLO_PAUSE_ASYM;
+
+		pause &= state->pause;
+
+		if (pause & MLO_PAUSE_SYM)
+			new_pause = MLO_PAUSE_TX | MLO_PAUSE_RX;
+		else if (pause & MLO_PAUSE_ASYM)
+			new_pause = state->pause & MLO_PAUSE_SYM ?
+				 MLO_PAUSE_RX : MLO_PAUSE_TX;
+	} else {
+		new_pause = pl->link_config.pause & MLO_PAUSE_TXRX_MASK;
+	}
+
+	state->pause &= ~MLO_PAUSE_TXRX_MASK;
+	state->pause |= new_pause;
+}
+
+static const char *phylink_pause_to_str(int pause)
+{
+	switch (pause & MLO_PAUSE_TXRX_MASK) {
+	case MLO_PAUSE_TX | MLO_PAUSE_RX:
+		return "rx/tx";
+	case MLO_PAUSE_TX:
+		return "tx";
+	case MLO_PAUSE_RX:
+		return "rx";
+	default:
+		return "off";
+	}
+}
+
 extern const char *phy_speed_to_str(int speed);
 
 static void phylink_resolve(struct work_struct *w)
@@ -245,6 +298,7 @@ static void phylink_resolve(struct work_struct *w)
 		switch (pl->link_an_mode) {
 		case MLO_AN_PHY:
 			link_state = pl->phy_state;
+			phylink_resolve_flow(pl, &link_state);
 			break;
 
 		case MLO_AN_FIXED:
@@ -253,9 +307,12 @@ static void phylink_resolve(struct work_struct *w)
 
 		case MLO_AN_SGMII:
 			phylink_get_mac_state(pl, &link_state);
-			if (pl->phydev)
+			if (pl->phydev) {
 				link_state.link = link_state.link &&
 						  pl->phy_state.link;
+				link_state.pause |= pl->phy_state.pause;
+				phylink_resolve_flow(pl, &link_state);
+			}
 			break;
 
 		case MLO_AN_8023Z:
@@ -285,7 +342,7 @@ static void phylink_resolve(struct work_struct *w)
 				    "Link is Up - %s/%s - flow control %s\n",
 				    phy_speed_to_str(link_state.speed),
 				    link_state.duplex ? "Full" : "Half",
-				    link_state.pause ? "rx/tx" : "off");
+				    phylink_pause_to_str(link_state.pause));
 		}
 	}
 	mutex_unlock(&pl->state_mutex);
@@ -314,6 +371,7 @@ struct phylink *phylink_create(struct net_device *ndev, struct device_node *np,
 	pl->link_interface = iface;
 	pl->link_port_support = SUPPORTED_MII;
 	pl->link_port = PORT_MII;
+	pl->link_config.pause = MLO_PAUSE_AN;
 	pl->ops = ops;
 	__set_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state);
 
@@ -495,6 +553,7 @@ void phylink_start(struct phylink *pl)
 	 * a fixed-link to start with the correct parameters, and also
 	 * ensures that we set the appropriate advertisment for Serdes links.
 	 */
+	phylink_resolve_flow(pl, &pl->link_config);
 	phylink_mac_config(pl, &pl->link_config);
 
 	clear_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state);
@@ -689,6 +748,76 @@ int phylink_ethtool_nway_reset(struct phylink *pl)
 }
 EXPORT_SYMBOL_GPL(phylink_ethtool_nway_reset);
 
+void phylink_ethtool_get_pauseparam(struct phylink *pl,
+				    struct ethtool_pauseparam *pause)
+{
+	mutex_lock(&pl->config_mutex);
+
+	pause->autoneg = !!(pl->link_config.pause & MLO_PAUSE_AN);
+	pause->rx_pause = !!(pl->link_config.pause & MLO_PAUSE_RX);
+	pause->tx_pause = !!(pl->link_config.pause & MLO_PAUSE_TX);
+
+	mutex_unlock(&pl->config_mutex);
+}
+EXPORT_SYMBOL_GPL(phylink_ethtool_get_pauseparam);
+
+static int __phylink_ethtool_set_pauseparam(struct phylink *pl,
+					    struct ethtool_pauseparam *pause)
+{
+	struct phylink_link_state *config = &pl->link_config;
+
+	if (!(config->supported & (SUPPORTED_Pause | SUPPORTED_Asym_Pause)))
+		return -EOPNOTSUPP;
+
+	if (!(config->supported & SUPPORTED_Asym_Pause) &&
+	    !pause->autoneg && pause->rx_pause != pause->tx_pause)
+		return -EINVAL;
+
+	config->pause &= ~(MLO_PAUSE_AN | MLO_PAUSE_TXRX_MASK);
+
+	if (pause->autoneg)
+		config->pause |= MLO_PAUSE_AN;
+	if (pause->rx_pause)
+		config->pause |= MLO_PAUSE_RX;
+	if (pause->tx_pause)
+		config->pause |= MLO_PAUSE_TX;
+
+	switch (pl->link_an_mode) {
+	case MLO_AN_PHY:
+		/* Silently mark the carrier down, and then trigger a resolve */
+		netif_carrier_off(pl->netdev);
+		phylink_run_resolve(pl);
+		break;
+
+	case MLO_AN_FIXED:
+		/* Should we allow fixed links to change against the config? */
+		phylink_resolve_flow(pl, config);
+		phylink_mac_config(pl, config);
+		break;
+
+	case MLO_AN_SGMII:
+	case MLO_AN_8023Z:
+		phylink_mac_config(pl, config);
+		phylink_mac_an_restart(pl);
+		break;
+	}
+
+	return 0;
+}
+
+int phylink_ethtool_set_pauseparam(struct phylink *pl,
+				   struct ethtool_pauseparam *pause)
+{
+	int ret;
+
+	mutex_lock(&pl->config_mutex);
+	ret = __phylink_ethtool_set_pauseparam(pl, pause);
+	mutex_unlock(&pl->config_mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(phylink_ethtool_set_pauseparam);
+
 /* This emulates MII registers for a fixed-mode phy operating as per the
  * passed in state. "aneg" defines if we report negotiation is possible.
  *
diff --git a/include/linux/phylink.h b/include/linux/phylink.h
index ad3c85508d19..a23c772cc3f9 100644
--- a/include/linux/phylink.h
+++ b/include/linux/phylink.h
@@ -13,6 +13,10 @@ enum {
 	MLO_PAUSE_NONE,
 	MLO_PAUSE_ASYM = BIT(0),
 	MLO_PAUSE_SYM = BIT(1),
+	MLO_PAUSE_RX = BIT(2),
+	MLO_PAUSE_TX = BIT(3),
+	MLO_PAUSE_TXRX_MASK = MLO_PAUSE_TX | MLO_PAUSE_RX,
+	MLO_PAUSE_AN = BIT(4),
 
 	MLO_AN_PHY = 0,
 	MLO_AN_FIXED,
@@ -66,6 +70,10 @@ void phylink_stop(struct phylink *);
 int phylink_ethtool_get_settings(struct phylink *, struct ethtool_cmd *);
 int phylink_ethtool_set_settings(struct phylink *, struct ethtool_cmd *);
 int phylink_ethtool_nway_reset(struct phylink *);
+void phylink_ethtool_get_pauseparam(struct phylink *,
+				    struct ethtool_pauseparam *);
+int phylink_ethtool_set_pauseparam(struct phylink *,
+				   struct ethtool_pauseparam *);
 int phylink_mii_ioctl(struct phylink *, struct ifreq *, int);

---

From: Russell King <[email-masked]>
Date: Mon, 15 Aug 2016 18:57:41 +0100

Add flow control support to mvneta, including the ethtool hooks.  This
uses the phylink code to calculate the result of autonegotiation where
a phy is attached, and to handle the ethtool settings.

Signed-off-by: Russell King <[email-masked]>
---
 drivers/net/ethernet/marvell/mvneta.c | 28 +++++++++++++++++++++++++++-
 1 file changed, 27 insertions(+), 1 deletion(-)

diff --git a/drivers/net/ethernet/marvell/mvneta.c b/drivers/net/ethernet/marvell/mvneta.c
index 75f0653a2289..14bcfc6a24aa 100644
--- a/drivers/net/ethernet/marvell/mvneta.c
+++ b/drivers/net/ethernet/marvell/mvneta.c
@@ -3162,6 +3162,12 @@ static int mvneta_mac_link_state(struct net_device *ndev,
 	state->link = !!(gmac_stat & MVNETA_GMAC_LINK_UP);
 	state->duplex = !!(gmac_stat & MVNETA_GMAC_FULL_DUPLEX);
 
+	state->pause = 0;
+	if (gmac_stat & MVNETA_GMAC_RX_FLOW_CTRL_ENABLE)
+		state->pause |= MLO_PAUSE_RX;
+	if (gmac_stat & MVNETA_GMAC_TX_FLOW_CTRL_ENABLE)
+		state->pause |= MLO_PAUSE_TX;
+
 	return 1;
 }
 
@@ -3204,6 +3210,8 @@ static void mvneta_mac_config(struct net_device *ndev, unsigned int mode,
 
 	if (state->advertising & ADVERTISED_Pause)
 		new_an |= MVNETA_GMAC_ADVERT_SYM_FLOW_CTRL;
+	if (state->pause & MLO_PAUSE_TXRX_MASK)
+		new_an |= MVNETA_GMAC_CONFIG_FLOW_CTRL;
 
 	switch (mode) {
 	case MLO_AN_SGMII:
@@ -3228,7 +3236,7 @@ static void mvneta_mac_config(struct net_device *ndev, unsigned int mode,
 			 /* The MAC only supports FD mode */
 			 MVNETA_GMAC_CONFIG_FULL_DUPLEX;
 
-		if (state->an_enabled)
+		if (state->pause & MLO_PAUSE_AN && state->an_enabled)
 			new_an |= MVNETA_GMAC_AN_FLOW_CTRL_EN;
 		break;
 
@@ -3675,6 +3683,22 @@ static int mvneta_ethtool_set_ringparam(struct net_device *dev,
 	return 0;
 }
 
+static void mvneta_ethtool_get_pauseparam(struct net_device *dev,
+					  struct ethtool_pauseparam *pause)
+{
+	struct mvneta_port *pp = netdev_priv(dev);
+
+	phylink_ethtool_get_pauseparam(pp->phylink, pause);
+}
+
+static int mvneta_ethtool_set_pauseparam(struct net_device *dev,
+					 struct ethtool_pauseparam *pause)
+{
+	struct mvneta_port *pp = netdev_priv(dev);
+
+	return phylink_ethtool_set_pauseparam(pp->phylink, pause);
+}
+
 static void mvneta_ethtool_get_strings(struct net_device *netdev, u32 sset,
 				       u8 *data)
 {
@@ -3857,6 +3881,8 @@ const struct ethtool_ops mvneta_eth_tool_ops = {
 	.get_drvinfo    = mvneta_ethtool_get_drvinfo,
 	.get_ringparam  = mvneta_ethtool_get_ringparam,
 	.set_ringparam	= mvneta_ethtool_set_ringparam,
+	.get_pauseparam	= mvneta_ethtool_get_pauseparam,
+	.set_pauseparam	= mvneta_ethtool_set_pauseparam,
 	.get_strings	= mvneta_ethtool_get_strings,
 	.get_ethtool_stats = mvneta_ethtool_get_stats,
 	.get_sset_count	= mvneta_ethtool_get_sset_count,

---

From: Russell King <[email-masked]>
Date: Mon, 15 Aug 2016 18:57:41 +0100

Enable flow control support for PHY connections by indicating our
support via the ethtool capabilities.  phylink takes care of the
appropriate handling.

Reviewed-by: Florian Fainelli <[email-masked]>
Signed-off-by: Russell King <[email-masked]>
---
 drivers/net/ethernet/marvell/mvneta.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/net/ethernet/marvell/mvneta.c b/drivers/net/ethernet/marvell/mvneta.c
index 14bcfc6a24aa..a8d2e735b85d 100644
--- a/drivers/net/ethernet/marvell/mvneta.c
+++ b/drivers/net/ethernet/marvell/mvneta.c
@@ -3129,12 +3129,14 @@ static int mvneta_mac_support(struct net_device *ndev, unsigned int mode,
 		state->supported = PHY_10BT_FEATURES |
 				   PHY_100BT_FEATURES |
 				   SUPPORTED_1000baseT_Full |
+				   SUPPORTED_Pause |
 				   SUPPORTED_Autoneg;
 		state->advertising = ADVERTISED_10baseT_Half |
 				     ADVERTISED_10baseT_Full |
 				     ADVERTISED_100baseT_Half |
 				     ADVERTISED_100baseT_Full |
 				     ADVERTISED_1000baseT_Full |
+				     ADVERTISED_Pause |
 				     ADVERTISED_Autoneg;
 		state->an_enabled = 1;
 		break;

---

From: Russell King <[email-masked]>
Date: Mon, 15 Aug 2016 18:57:41 +0100

From: Russell King <[email-masked]>

Allow symetric flow control to be enabled for fixed link connections as
well as other types of connections by setting the supported and
advertised capability bits.

Signed-off-by: Russell King <[email-masked]>
---
 drivers/net/ethernet/marvell/mvneta.c | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

diff --git a/drivers/net/ethernet/marvell/mvneta.c b/drivers/net/ethernet/marvell/mvneta.c
index a8d2e735b85d..37933c73e142 100644
--- a/drivers/net/ethernet/marvell/mvneta.c
+++ b/drivers/net/ethernet/marvell/mvneta.c
@@ -3116,9 +3116,9 @@ static int mvneta_mac_support(struct net_device *ndev, unsigned int mode,
 	switch (mode) {
 	case MLO_AN_8023Z:
 		state->supported = SUPPORTED_1000baseT_Full |
-				   SUPPORTED_Autoneg | SUPPORTED_Pause;
+				   SUPPORTED_Autoneg;
 		state->advertising = ADVERTISED_1000baseT_Full |
-				     ADVERTISED_Autoneg | ADVERTISED_Pause;
+				     ADVERTISED_Autoneg;
 		state->an_enabled = 1;
 		break;
 
@@ -3129,18 +3129,21 @@ static int mvneta_mac_support(struct net_device *ndev, unsigned int mode,
 		state->supported = PHY_10BT_FEATURES |
 				   PHY_100BT_FEATURES |
 				   SUPPORTED_1000baseT_Full |
-				   SUPPORTED_Pause |
 				   SUPPORTED_Autoneg;
 		state->advertising = ADVERTISED_10baseT_Half |
 				     ADVERTISED_10baseT_Full |
 				     ADVERTISED_100baseT_Half |
 				     ADVERTISED_100baseT_Full |
 				     ADVERTISED_1000baseT_Full |
-				     ADVERTISED_Pause |
 				     ADVERTISED_Autoneg;
 		state->an_enabled = 1;
 		break;
 	}
+
+	/* All modes support flow control */
+	state->supported |= SUPPORTED_Pause;
+	state->advertising |= ADVERTISED_Pause;
+
 	return 0;
 }

---

From: Russell King <[email-masked]>
Date: Mon, 15 Aug 2016 18:57:41 +0100

Add EEE hooks to phylink to allow the phylib EEE functions for the
connected phy to be safely accessed.

Signed-off-by: Russell King <[email-masked]>
---
 drivers/net/phy/phylink.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++-
 include/linux/phylink.h   |  7 +++++-
 2 files changed, 63 insertions(+), 2 deletions(-)

diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index 087d44a0a6fb..107b15d91316 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -334,7 +334,8 @@ static void phylink_resolve(struct work_struct *w)
 			if (pl->phydev)
 				phylink_mac_config(pl, &link_state);
 
-			pl->ops->mac_link_up(ndev, pl->link_an_mode);
+			pl->ops->mac_link_up(ndev, pl->link_an_mode,
+					     pl->phydev);
 
 			netif_carrier_on(ndev);
 
@@ -818,6 +819,61 @@ int phylink_ethtool_set_pauseparam(struct phylink *pl,
 }
 EXPORT_SYMBOL_GPL(phylink_ethtool_set_pauseparam);
 
+int phylink_init_eee(struct phylink *pl, bool clk_stop_enable)
+{
+	int ret = -EPROTONOSUPPORT;
+
+	mutex_lock(&pl->config_mutex);
+	if (pl->phydev)
+		ret = phy_init_eee(pl->phydev, clk_stop_enable);
+	mutex_unlock(&pl->config_mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(phylink_init_eee);
+
+int phylink_get_eee_err(struct phylink *pl)
+{
+	int ret = 0;
+
+	mutex_lock(&pl->config_mutex);
+	if (pl->phydev)
+		ret = phy_get_eee_err(pl->phydev);
+	mutex_unlock(&pl->config_mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(phylink_get_eee_err);
+
+int phylink_ethtool_get_eee(struct phylink *pl, struct ethtool_eee *eee)
+{
+	int ret = -EOPNOTSUPP;
+
+	mutex_lock(&pl->config_mutex);
+	if (pl->phydev)
+		ret = phy_ethtool_get_eee(pl->phydev, eee);
+	mutex_unlock(&pl->config_mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(phylink_ethtool_get_eee);
+
+int phylink_ethtool_set_eee(struct phylink *pl, struct ethtool_eee *eee)
+{
+	int ret = -EOPNOTSUPP;
+
+	mutex_lock(&pl->config_mutex);
+	if (pl->phydev) {
+		ret = phy_ethtool_set_eee(pl->phydev, eee);
+		if (ret == 0 && eee->eee_enabled)
+			phy_start_aneg(pl->phydev);
+	}
+	mutex_unlock(&pl->config_mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(phylink_ethtool_set_eee);
+
 /* This emulates MII registers for a fixed-mode phy operating as per the
  * passed in state. "aneg" defines if we report negotiation is possible.
  *
diff --git a/include/linux/phylink.h b/include/linux/phylink.h
index a23c772cc3f9..361fbe9222b2 100644
--- a/include/linux/phylink.h
+++ b/include/linux/phylink.h
@@ -51,7 +51,8 @@ struct phylink_mac_ops {
 	void (*mac_an_restart)(struct net_device *, unsigned int mode);
 
 	void (*mac_link_down)(struct net_device *, unsigned int mode);
-	void (*mac_link_up)(struct net_device *, unsigned int mode);
+	void (*mac_link_up)(struct net_device *, unsigned int mode,
+			    struct phy_device *);
 };
 
 struct phylink *phylink_create(struct net_device *, struct device_node *,
@@ -74,6 +75,10 @@ void phylink_ethtool_get_pauseparam(struct phylink *,
 				    struct ethtool_pauseparam *);
 int phylink_ethtool_set_pauseparam(struct phylink *,
 				   struct ethtool_pauseparam *);
+int phylink_init_eee(struct phylink *, bool);
+int phylink_get_eee_err(struct phylink *);
+int phylink_ethtool_get_eee(struct phylink *, struct ethtool_eee *);
+int phylink_ethtool_set_eee(struct phylink *, struct ethtool_eee *);
 int phylink_mii_ioctl(struct phylink *, struct ifreq *, int);

---

From: Russell King <[email-masked]>
Date: Mon, 15 Aug 2016 18:57:41 +0100

Add EEE support to mvneta.  This allows us to enable the low power idle
support at MAC level if there is a PHY attached through phylink which
supports LPI.  The appropriate ethtool support is provided to allow the
feature to be controlled, including ethtool statistics for EEE wakeup
errors.

Signed-off-by: Russell King <[email-masked]>
---
 drivers/net/ethernet/marvell/mvneta.c | 87 +++++++++++++++++++++++++++++++++++
 1 file changed, 87 insertions(+)

diff --git a/drivers/net/ethernet/marvell/mvneta.c b/drivers/net/ethernet/marvell/mvneta.c
index 37933c73e142..ea90ed6d34dc 100644
--- a/drivers/net/ethernet/marvell/mvneta.c
+++ b/drivers/net/ethernet/marvell/mvneta.c
@@ -243,6 +243,12 @@
 #define MVNETA_TXQ_TOKEN_SIZE_REG(q)             (0x3e40 + ((q) << 2))
 #define      MVNETA_TXQ_TOKEN_SIZE_MAX           0x7fffffff
 
+#define MVNETA_LPI_CTRL_0                        0x2cc0
+#define MVNETA_LPI_CTRL_1                        0x2cc4
+#define      MVNETA_LPI_REQUEST_ENABLE           BIT(0)
+#define MVNETA_LPI_CTRL_2                        0x2cc8
+#define MVNETA_LPI_STATUS                        0x2ccc
+
 #define MVNETA_CAUSE_TXQ_SENT_DESC_ALL_MASK	 0xff
 
 /* Descriptor ring Macros */
@@ -316,6 +322,11 @@
 #define MVNETA_RX_GET_BM_POOL_ID(rxd) \
 	(((rxd)->status & MVNETA_RXD_BM_POOL_MASK) >> MVNETA_RXD_BM_POOL_SHIFT)
 
+enum {
+	ETHTOOL_STAT_EEE_WAKEUP,
+	ETHTOOL_MAX_STATS,
+};
+
 struct mvneta_statistic {
 	unsigned short offset;
 	unsigned short type;
@@ -324,6 +335,7 @@ struct mvneta_statistic {
 
 #define T_REG_32	32
 #define T_REG_64	64
+#define T_SW		1
 
 static const struct mvneta_statistic mvneta_statistics[] = {
 	{ 0x3000, T_REG_64, "good_octets_received", },
@@ -358,6 +370,7 @@ static const struct mvneta_statistic mvneta_statistics[] = {
 	{ 0x304c, T_REG_32, "broadcast_frames_sent", },
 	{ 0x3054, T_REG_32, "fc_sent", },
 	{ 0x300c, T_REG_32, "internal_mac_transmit_err", },
+	{ ETHTOOL_STAT_EEE_WAKEUP, T_SW, "eee_wakeup_errors", },
 };
 
 struct mvneta_pcpu_stats {
@@ -415,6 +428,10 @@ struct mvneta_port {
 	struct mvneta_bm_pool *pool_short;
 	int bm_win_id;
 
+	bool eee_enabled;
+	bool eee_active;
+	bool tx_lpi_enabled;
+
 	u64 ethtool_stats[ARRAY_SIZE(mvneta_statistics)];
 
 	u32 indir[MVNETA_RSS_LU_TABLE_SIZE];
@@ -3278,6 +3295,18 @@ static void mvneta_mac_config(struct net_device *ndev, unsigned int mode,
 		mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, new_an);
 }
 
+static void mvneta_set_eee(struct mvneta_port *pp, bool enable)
+{
+	u32 lpi_ctl1;
+
+	lpi_ctl1 = mvreg_read(pp, MVNETA_LPI_CTRL_1);
+	if (enable)
+		lpi_ctl1 |= MVNETA_LPI_REQUEST_ENABLE;
+	else
+		lpi_ctl1 &= ~MVNETA_LPI_REQUEST_ENABLE;
+	mvreg_write(pp, MVNETA_LPI_CTRL_1, lpi_ctl1);
+}
+
 static void mvneta_mac_link_down(struct net_device *ndev, unsigned int mode)
 {
 	struct mvneta_port *pp = netdev_priv(ndev);
@@ -3291,6 +3320,9 @@ static void mvneta_mac_link_down(struct net_device *ndev, unsigned int mode)
 		val |= MVNETA_GMAC_FORCE_LINK_DOWN;
 		mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val);
 	}
+
+	pp->eee_active = false;
+	mvneta_set_eee(pp, false);
 }
 
 static void mvneta_mac_link_up(struct net_device *ndev, unsigned int mode,
@@ -3307,6 +3339,11 @@ static void mvneta_mac_link_up(struct net_device *ndev, unsigned int mode,
 	}
 
 	mvneta_port_up(pp);
+
+	if (phy && pp->eee_enabled) {
+		pp->eee_active = phy_init_eee(phy, 0) >= 0;
+		mvneta_set_eee(pp, pp->eee_active && pp->tx_lpi_enabled);
+	}
 }
 
 static const struct phylink_mac_ops mvneta_phylink_ops = {
@@ -3739,6 +3776,13 @@ static void mvneta_ethtool_update_stats(struct mvneta_port *pp)
 			high = readl_relaxed(base + s->offset + 4);
 			val = (u64)high << 32 | low;
 			break;
+		case T_SW:
+			switch (s->offset) {
+			case ETHTOOL_STAT_EEE_WAKEUP:
+				val = phylink_get_eee_err(pp->phylink);
+				break;
+			}
+			break;
 		}
 
 		pp->ethtool_stats[i] += val;
@@ -3864,6 +3908,47 @@ static int mvneta_ethtool_get_rxfh(struct net_device *dev, u32 *indir, u8 *key,
 	return 0;
 }
 
+static int mvneta_ethtool_get_eee(struct net_device *dev,
+				  struct ethtool_eee *eee)
+{
+	struct mvneta_port *pp = netdev_priv(dev);
+	u32 lpi_ctl0;
+
+	lpi_ctl0 = mvreg_read(pp, MVNETA_LPI_CTRL_0);
+
+	eee->eee_enabled = pp->eee_enabled;
+	eee->eee_active = pp->eee_active;
+	eee->tx_lpi_enabled = pp->tx_lpi_enabled;
+	eee->tx_lpi_timer = (lpi_ctl0) >> 8; // * scale;
+
+	return phylink_ethtool_get_eee(pp->phylink, eee);
+}
+
+static int mvneta_ethtool_set_eee(struct net_device *dev,
+				  struct ethtool_eee *eee)
+{
+	struct mvneta_port *pp = netdev_priv(dev);
+	u32 lpi_ctl0;
+
+	/* The Armada 37x documents do not give limits for this other than
+	 * it being an 8-bit register. */
+	if (eee->tx_lpi_enabled &&
+	    (eee->tx_lpi_timer < 0 || eee->tx_lpi_timer > 255))
+		return -EINVAL;
+
+	lpi_ctl0 = mvreg_read(pp, MVNETA_LPI_CTRL_0);
+	lpi_ctl0 &= ~(0xff << 8);
+	lpi_ctl0 |= eee->tx_lpi_timer << 8;
+	mvreg_write(pp, MVNETA_LPI_CTRL_0, lpi_ctl0);
+
+	pp->eee_enabled = eee->eee_enabled;
+	pp->tx_lpi_enabled = eee->tx_lpi_enabled;
+
+	mvneta_set_eee(pp, eee->tx_lpi_enabled && eee->eee_enabled);
+
+	return phylink_ethtool_set_eee(pp->phylink, eee);
+}
+
 static const struct net_device_ops mvneta_netdev_ops = {
 	.ndo_open            = mvneta_open,
 	.ndo_stop            = mvneta_stop,
@@ -3895,6 +3980,8 @@ const struct ethtool_ops mvneta_eth_tool_ops = {
 	.get_rxnfc	= mvneta_ethtool_get_rxnfc,
 	.get_rxfh	= mvneta_ethtool_get_rxfh,
 	.set_rxfh	= mvneta_ethtool_set_rxfh,
+	.get_eee	= mvneta_ethtool_get_eee,
+	.set_eee	= mvneta_ethtool_set_eee,
 };
 
 /* Initialize hw */

---

From: Russell King <[email-masked]>
Date: Mon, 15 Aug 2016 18:57:41 +0100

Add support for reading module EEPROMs through phylink.

Reviewed-by: Florian Fainelli <[email-masked]>
Signed-off-by: Russell King <[email-masked]>
---
 drivers/net/phy/phylink.c | 66 +++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/phylink.h   | 12 +++++++++
 2 files changed, 78 insertions(+)

diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index 107b15d91316..6a18947e151c 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -60,6 +60,9 @@ struct phylink {
 	struct work_struct resolve;
 
 	bool mac_link_up;
+
+	const struct phylink_module_ops *module_ops;
+	void *module_data;
 };
 
 static const char *phylink_an_mode_str(unsigned int mode)
@@ -819,6 +822,36 @@ int phylink_ethtool_set_pauseparam(struct phylink *pl,
 }
 EXPORT_SYMBOL_GPL(phylink_ethtool_set_pauseparam);
 
+int phylink_ethtool_get_module_info(struct phylink *pl,
+				    struct ethtool_modinfo *modinfo)
+{
+	int ret = -EOPNOTSUPP;
+
+	mutex_lock(&pl->config_mutex);
+	if (pl->module_ops)
+		ret = pl->module_ops->get_module_info(pl->module_data,
+						      modinfo);
+	mutex_unlock(&pl->config_mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(phylink_ethtool_get_module_info);
+
+int phylink_ethtool_get_module_eeprom(struct phylink *pl,
+				      struct ethtool_eeprom *ee, u8 *buf)
+{
+	int ret = -EOPNOTSUPP;
+
+	mutex_lock(&pl->config_mutex);
+	if (pl->module_ops)
+		ret = pl->module_ops->get_module_eeprom(pl->module_data, ee,
+							buf);
+	mutex_unlock(&pl->config_mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(phylink_ethtool_get_module_eeprom);
+
 int phylink_init_eee(struct phylink *pl, bool clk_stop_enable)
 {
 	int ret = -EPROTONOSUPPORT;
@@ -1016,6 +1049,39 @@ EXPORT_SYMBOL_GPL(phylink_mii_ioctl);

+int phylink_register_module(struct phylink *pl, void *data,
+			    const struct phylink_module_ops *ops)
+{
+	int ret = -EBUSY;
+
+	mutex_lock(&pl->config_mutex);
+	if (!pl->module_ops) {
+		pl->module_ops = ops;
+		pl->module_data = data;
+		ret = 0;
+	}
+	mutex_unlock(&pl->config_mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(phylink_register_module);
+
+int phylink_unregister_module(struct phylink *pl, void *data)
+{
+	int ret = -EINVAL;
+
+	mutex_lock(&pl->config_mutex);
+	if (pl->module_data == data) {
+		pl->module_ops = NULL;
+		pl->module_data = NULL;
+		ret = 0;
+	}
+	mutex_unlock(&pl->config_mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(phylink_unregister_module);
+
 void phylink_disable(struct phylink *pl)
 {
 	set_bit(PHYLINK_DISABLE_LINK, &pl->phylink_disable_state);
diff --git a/include/linux/phylink.h b/include/linux/phylink.h
index 361fbe9222b2..01d442b08e62 100644
--- a/include/linux/phylink.h
+++ b/include/linux/phylink.h
@@ -55,6 +55,11 @@ struct phylink_mac_ops {
 			    struct phy_device *);
 };
 
+struct phylink_module_ops {
+	int (*get_module_info)(void *, struct ethtool_modinfo *);
+	int (*get_module_eeprom)(void *, struct ethtool_eeprom *, u8 *);
+};
+
 struct phylink *phylink_create(struct net_device *, struct device_node *,
 	phy_interface_t iface, const struct phylink_mac_ops *ops);
 void phylink_destroy(struct phylink *);
@@ -75,12 +80,19 @@ void phylink_ethtool_get_pauseparam(struct phylink *,
 				    struct ethtool_pauseparam *);
 int phylink_ethtool_set_pauseparam(struct phylink *,
 				   struct ethtool_pauseparam *);
+int phylink_ethtool_get_module_info(struct phylink *, struct ethtool_modinfo *);
+int phylink_ethtool_get_module_eeprom(struct phylink *,
+				      struct ethtool_eeprom *, u8 *);
 int phylink_init_eee(struct phylink *, bool);
 int phylink_get_eee_err(struct phylink *);
 int phylink_ethtool_get_eee(struct phylink *, struct ethtool_eee *);
 int phylink_ethtool_set_eee(struct phylink *, struct ethtool_eee *);
 int phylink_mii_ioctl(struct phylink *, struct ifreq *, int);
 
+int phylink_register_module(struct phylink *, void *,
+			    const struct phylink_module_ops *);
+int phylink_unregister_module(struct phylink *, void *);
+
 void phylink_set_link_port(struct phylink *pl, u32 support, u8 port);
 int phylink_set_link_an_mode(struct phylink *pl, unsigned int mode);
 void phylink_disable(struct phylink *pl);

---

From: Russell King <[email-masked]>
Date: Mon, 15 Aug 2016 18:57:41 +0100

Signed-off-by: Russell King <[email-masked]>
---
 drivers/net/ethernet/marvell/mvneta.c | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/drivers/net/ethernet/marvell/mvneta.c b/drivers/net/ethernet/marvell/mvneta.c
index ea90ed6d34dc..ea972f921651 100644
--- a/drivers/net/ethernet/marvell/mvneta.c
+++ b/drivers/net/ethernet/marvell/mvneta.c
@@ -3908,6 +3908,22 @@ static int mvneta_ethtool_get_rxfh(struct net_device *dev, u32 *indir, u8 *key,
 	return 0;
 }
 
+static int mvneta_ethtool_get_module_info(struct net_device *dev,
+					  struct ethtool_modinfo *modinfo)
+{
+	struct mvneta_port *pp = netdev_priv(dev);
+
+	return phylink_ethtool_get_module_info(pp->phylink, modinfo);
+}
+
+static int mvneta_ethtool_get_module_eeprom(struct net_device *dev,
+					    struct ethtool_eeprom *ee, u8 *buf)
+{
+	struct mvneta_port *pp = netdev_priv(dev);
+
+	return phylink_ethtool_get_module_eeprom(pp->phylink, ee, buf);
+}
+
 static int mvneta_ethtool_get_eee(struct net_device *dev,
 				  struct ethtool_eee *eee)
 {
@@ -3980,6 +3996,8 @@ const struct ethtool_ops mvneta_eth_tool_ops = {
 	.get_rxnfc	= mvneta_ethtool_get_rxnfc,
 	.get_rxfh	= mvneta_ethtool_get_rxfh,
 	.set_rxfh	= mvneta_ethtool_set_rxfh,
+	.get_module_info = mvneta_ethtool_get_module_info,
+	.get_module_eeprom = mvneta_ethtool_get_module_eeprom,
 	.get_eee	= mvneta_ethtool_get_eee,
 	.set_eee	= mvneta_ethtool_set_eee,
 };

---

From: Russell King <[email-masked]>
Date: Mon, 15 Aug 2016 18:57:41 +0100

Signed-off-by: Russell King <[email-masked]>
---
 drivers/net/phy/sfp.c | 19 +++++++++++--------
 1 file changed, 11 insertions(+), 8 deletions(-)

diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c
index 6f32e945df50..de6251701340 100644
--- a/drivers/net/phy/sfp.c
+++ b/drivers/net/phy/sfp.c
@@ -901,11 +901,9 @@ static void sfp_sm_event(struct sfp *sfp, unsigned int event)
 	mutex_unlock(&sfp->sm_mutex);
 }
 
-#if 0
-static int sfp_phy_module_info(struct phy_device *phy,
-			       struct ethtool_modinfo *modinfo)
+static int sfp_module_info(void *priv, struct ethtool_modinfo *modinfo)
 {
-	struct sfp *sfp = phy->priv;
+	struct sfp *sfp = priv;
 
 	/* locking... and check module is present */
 
@@ -919,10 +917,9 @@ static int sfp_phy_module_info(struct phy_device *phy,
 	return 0;
 }
 
-static int sfp_phy_module_eeprom(struct phy_device *phy,
-	struct ethtool_eeprom *ee, u8 *data)
+static int sfp_module_eeprom(void *priv, struct ethtool_eeprom *ee, u8 *data)
 {
-	struct sfp *sfp = phy->priv;
+	struct sfp *sfp = priv;
 	unsigned int first, last, len;
 	int ret;
 
@@ -953,7 +950,11 @@ static int sfp_phy_module_eeprom(struct phy_device *phy,
 	}
 	return 0;
 }
-#endif
+
+static const struct phylink_module_ops sfp_module_ops = {
+	.get_module_info = sfp_module_info,
+	.get_module_eeprom = sfp_module_eeprom,
+};
 
 static void sfp_timeout(struct work_struct *work)
 {
@@ -1029,6 +1030,7 @@ static int sfp_netdev_notify(struct notifier_block *nb, unsigned long act, void
 	case NETDEV_UNREGISTER:
 		if (sfp->mod_phy && sfp->phylink)
 			phylink_disconnect_phy(sfp->phylink);
+		phylink_unregister_module(sfp->phylink, sfp);
 		sfp->phylink = NULL;
 		dev_put(sfp->ndev);
 		sfp->ndev = NULL;
@@ -1145,6 +1147,7 @@ static int sfp_probe(struct platform_device *pdev)
 		}
 
 		phylink_disable(sfp->phylink);
+		phylink_register_module(sfp->phylink, sfp, &sfp_module_ops);
 	}
 
 	sfp->state = sfp_get_state(sfp);

---

From: Russell King <[email-masked]>
Date: Mon, 15 Aug 2016 18:57:41 +0100

The Marvell PHYs support pause frame advertisments, so we should not be
masking their support off.  Add the necessary flag to the Marvell PHY
to allow any MAC level pause frame support to be advertised.

Reviewed-by: Florian Fainelli <[email-masked]>
Signed-off-by: Russell King <[email-masked]>
---
 drivers/net/phy/marvell.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/net/phy/marvell.c b/drivers/net/phy/marvell.c
index c2dcf02df202..d379efc34a62 100644
--- a/drivers/net/phy/marvell.c
+++ b/drivers/net/phy/marvell.c
@@ -1671,7 +1671,8 @@ static struct phy_driver marvell_drivers[] = {
 		.phy_id = MARVELL_PHY_ID_88E1510,
 		.phy_id_mask = MARVELL_PHY_ID_MASK,
 		.name = "Marvell 88E1510",
-		.features = PHY_GBIT_FEATURES | SUPPORTED_FIBRE,
+		.features = PHY_GBIT_FEATURES | SUPPORTED_FIBRE |
+				SUPPORTED_Pause,
 		.flags = PHY_HAS_INTERRUPT,
 		.probe = marvell_probe,
 		.config_init = &m88e1510_config_init,

---

From: Russell King <[email-masked]>
Date: Mon, 15 Aug 2016 18:57:42 +0100

Signed-off-by: Russell King <[email-masked]>
---
 drivers/net/phy/marvell.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/net/phy/marvell.c b/drivers/net/phy/marvell.c
index d379efc34a62..b0ac5f15534b 100644
--- a/drivers/net/phy/marvell.c
+++ b/drivers/net/phy/marvell.c
@@ -1523,7 +1523,7 @@ static struct phy_driver marvell_drivers[] = {
 		.phy_id = MARVELL_PHY_ID_88E1111,
 		.phy_id_mask = MARVELL_PHY_ID_MASK,
 		.name = "Marvell 88E1111",
-		.features = PHY_GBIT_FEATURES,
+		.features = PHY_GBIT_FEATURES | SUPPORTED_Pause,
 		.flags = PHY_HAS_INTERRUPT,
 		.probe = marvell_probe,
 		.config_init = &m88e1111_config_init,

---

From: Russell King <[email-masked]>
Date: Mon, 15 Aug 2016 18:57:42 +0100

diff --git a/drivers/net/phy/marvell.c b/drivers/net/phy/marvell.c
index b0ac5f15534b..d0ad2c38ebfb 100644
--- a/drivers/net/phy/marvell.c
+++ b/drivers/net/phy/marvell.c
@@ -1691,7 +1691,7 @@ static struct phy_driver marvell_drivers[] = {
 		.phy_id = MARVELL_PHY_ID_88E1540,
 		.phy_id_mask = MARVELL_PHY_ID_MASK,
 		.name = "Marvell 88E1540",
-		.features = PHY_GBIT_FEATURES,
+		.features = PHY_GBIT_FEATURES | SUPPORTED_Pause,
 		.flags = PHY_HAS_INTERRUPT,
 		.probe = marvell_probe,
 		.config_init = &marvell_config_init,

---

From: Russell King <[email-masked]>
Date: Mon, 15 Aug 2016 18:57:42 +0100

From: Russell King <[email-masked]>
---
Patch suppressed due to merge commit

---

From: Russell King <[email-masked]>
Date: Mon, 15 Aug 2016 18:57:42 +0100

Currently, the ports as seen from the rear number as:

	eth0 sfp lan5 lan4 lan3 lan2 lan1 lan6

which is illogical - this came about because the rev 2.0 boards have the
LEDs on the front for the DSA switch (lan5-1) reversed.  Rev 2.1 boards
fixed the LED issue, and the Clearfog case numbers the lan ports
increasing from left to right.

Maintaining this illogical numbering causes confusion, with reports that
"my link isn't coming up" and "my connection negotiates 10base-Half"
both of which are due to people thinking that the port next to the SFP
is lan1.

Fix this by renumbering the ports to match people's expectations.

Signed-off-by: Russell King <[email-masked]>
---
 arch/arm/boot/dts/armada-388-clearfog.dts | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/arch/arm/boot/dts/armada-388-clearfog.dts b/arch/arm/boot/dts/armada-388-clearfog.dts
index 2e0556af6e5e..d3e6bd805006 100644
--- a/arch/arm/boot/dts/armada-388-clearfog.dts
+++ b/arch/arm/boot/dts/armada-388-clearfog.dts
@@ -390,12 +390,12 @@
 
 			port@0 {
 				reg = <0>;
-				label = "lan1";
+				label = "lan5";
 			};
 
 			port@1 {
 				reg = <1>;
-				label = "lan2";
+				label = "lan4";
 			};
 
 			port@2 {
@@ -405,12 +405,12 @@
 
 			port@3 {
 				reg = <3>;
-				label = "lan4";
+				label = "lan2";
 			};
 
 			port@4 {
 				reg = <4>;
-				label = "lan5";
+				label = "lan1";
 			};
 
 			port@5 {

---

From: Russell King <[email-masked]>
Date: Mon, 15 Aug 2016 18:57:42 +0100

Add SFP module support for Clearfog using the SFP phylink support.

Signed-off-by: Russell King <[email-masked]>
---
 arch/arm/boot/dts/armada-388-clearfog.dts | 44 ++++++++-----------------------
 1 file changed, 11 insertions(+), 33 deletions(-)

diff --git a/arch/arm/boot/dts/armada-388-clearfog.dts b/arch/arm/boot/dts/armada-388-clearfog.dts
index d3e6bd805006..2e2c731e69d8 100644
--- a/arch/arm/boot/dts/armada-388-clearfog.dts
+++ b/arch/arm/boot/dts/armada-388-clearfog.dts
@@ -90,16 +90,12 @@
 			};
 
 			ethernet@34000 {
+				managed = "in-band-status";
 				phy-mode = "sgmii";
 				buffer-manager = <&bm>;
 				bm,pool-long = <3>;
 				bm,pool-short = <1>;
 				status = "okay";
-
-				fixed-link {
-					speed = <1000>;
-					full-duplex;
-				};
 			};
 
 			i2c@11000 {
@@ -183,34 +179,6 @@
 						output-low;
 						line-name = "m.2 devslp";
 					};
-					sfp_los {
-						/* SFP loss of signal */
-						gpio-hog;
-						gpios = <12 GPIO_ACTIVE_HIGH>;
-						input;
-						line-name = "sfp-los";
-					};
-					sfp_tx_fault {
-						/* SFP laser fault */
-						gpio-hog;
-						gpios = <13 GPIO_ACTIVE_HIGH>;
-						input;
-						line-name = "sfp-tx-fault";
-					};
-					sfp_tx_disable {
-						/* SFP transmit disable */
-						gpio-hog;
-						gpios = <14 GPIO_ACTIVE_HIGH>;
-						output-low;
-						line-name = "sfp-tx-disable";
-					};
-					sfp_mod_def0 {
-						/* SFP module present */
-						gpio-hog;
-						gpios = <15 GPIO_ACTIVE_LOW>;
-						input;
-						line-name = "sfp-mod-def0";
-					};
 				};
 
 				/* The MCP3021 is 100kHz clock only */
@@ -374,6 +342,16 @@
 		};
 	};
 
+	sfp: sfp {
+		compatible = "sff,sfp";
+		i2c-bus = <&i2c1>;
+		los-gpio = <&expander0 12 GPIO_ACTIVE_HIGH>;
+		moddef0-gpio = <&expander0 15 GPIO_ACTIVE_LOW>;
+		sfp,ethernet = <&eth2>;
+		tx-disable-gpio = <&expander0 14 GPIO_ACTIVE_HIGH>;
+		tx-fault-gpio = <&expander0 13 GPIO_ACTIVE_HIGH>;
+	};
+
 	dsa@0 {
 		compatible = "marvell,dsa";
 		dsa,ethernet = <&eth1>;

---

From: Russell King <[email-masked]>
Date: Mon, 15 Aug 2016 18:57:42 +0100

Signed-off-by: Russell King <[email-masked]>
---
 arch/arm/boot/dts/armada-388-clearfog.dts | 50 +++++++++++++++++++++++++++++++
 1 file changed, 50 insertions(+)

diff --git a/arch/arm/boot/dts/armada-388-clearfog.dts b/arch/arm/boot/dts/armada-388-clearfog.dts
index 2e2c731e69d8..c25ae16c2310 100644
--- a/arch/arm/boot/dts/armada-388-clearfog.dts
+++ b/arch/arm/boot/dts/armada-388-clearfog.dts
@@ -422,3 +422,53 @@
 		};
 	};
 };
+/*
++#define A38x_CUSTOMER_BOARD_1_MPP16_23         0x10460011
+MPP18: gpio		?
+MPP19: gpio		?
+MPP20: ua1:txd		?
+MPP21: sd0:cmd		x sd0
+MPP22: gpio		x mikro int
+MPP23: spi0:sck		?
++#define A38x_CUSTOMER_BOARD_1_MPP24_31         0x22043333
+MPP24: ua1:rxd		x mikro rx
+MPP25: ua1:txd		x mikro tx
+MPP26: i2c1:sck		x mikro sck
+MPP27: i2c1:sda		x mikro sda
+MPP28: sd0:clk		x sd0
+MPP29: gpio		x mikro rst
+MPP30: ge1:txd2
+MPP31: ge1:txd3
++#define A38x_CUSTOMER_BOARD_1_MPP32_39         0x44400002
+MPP32: ge1:txctl
+MPP33: gpio		?
+MPP34: gpio		x rear button
+MPP35: gpio		?
+MPP36: gpio		?
+MPP37: sd0:d3		??
+MPP38: sd0:d0		x sd0
+MPP39: sd0:d1		x sd0
++#define A38x_CUSTOMER_BOARD_1_MPP40_47         0x41144004
+MPP40: sd0:d2		x sd0
+MPP41: gpio		x switch reset
+MPP42: gpio		? sw1-1
+MPP43: spi1:cs2		x mikro cs
+MPP44: sata3:prsnt
+MPP45: ref:clk_out0	?
+MPP46: ref:clk_out1	?
+MPP47: 4		??
++#define A38x_CUSTOMER_BOARD_1_MPP48_55         0x45333333
+MPP48: tdm:pclk
+MPP49: tdm:fsync
+MPP50: tdm:drx
+MPP51: tdm:dtx
+MPP52: tdm:int
+MPP53: tdm:rst
+MPP54: sd0:d3		??
+MPP55: spi1:cs1		x slic
++#define A38x_CUSTOMER_BOARD_1_MPP56_63         0x00004444
+MPP56: spi1:mosi	x mikro mosi
+MPP57: spi1:sck		x mikro sck
+MPP58: spi1:miso	x mikro miso
+MPP59: spi1:cs0		x w25q32
+*/

---

From: Russell King <[email-masked]>
Date: Mon, 15 Aug 2016 18:57:42 +0100

Signed-off-by: Russell King <[email-masked]>
---
 arch/arm/boot/dts/armada-388-clearfog.dts |  36 +++++-----
 drivers/pci/host/pci-mvebu.c              | 109 ++++++++++++++++++++++++++++--
 drivers/pci/pcie/aspm.c                   |   2 +
 drivers/pci/pcie/portdrv_core.c           |   2 +
 4 files changed, 126 insertions(+), 23 deletions(-)

diff --git a/arch/arm/boot/dts/armada-388-clearfog.dts b/arch/arm/boot/dts/armada-388-clearfog.dts
index c25ae16c2310..72cc39bfda52 100644
--- a/arch/arm/boot/dts/armada-388-clearfog.dts
+++ b/arch/arm/boot/dts/armada-388-clearfog.dts
@@ -423,13 +423,13 @@
 	};
 };
 /*
-+#define A38x_CUSTOMER_BOARD_1_MPP16_23         0x10460011
-MPP18: gpio		?
-MPP19: gpio		?
-MPP20: ua1:txd		?
++#define A38x_CUSTOMER_BOARD_1_MPP16_23         0x00400011
+MPP18: gpio		? (pca9655 int?)
+MPP19: gpio		? (clkreq?)
+MPP20: gpio		? (sd0 detect)
 MPP21: sd0:cmd		x sd0
 MPP22: gpio		x mikro int
-MPP23: spi0:sck		?
+MPP23: gpio		x switch irq
 +#define A38x_CUSTOMER_BOARD_1_MPP24_31         0x22043333
 MPP24: ua1:rxd		x mikro rx
 MPP25: ua1:txd		x mikro tx
@@ -437,15 +437,15 @@ MPP26: i2c1:sck		x mikro sck
 MPP27: i2c1:sda		x mikro sda
 MPP28: sd0:clk		x sd0
 MPP29: gpio		x mikro rst
-MPP30: ge1:txd2
-MPP31: ge1:txd3
+MPP30: ge1:txd2		? (config)
+MPP31: ge1:txd3		? (config)
 +#define A38x_CUSTOMER_BOARD_1_MPP32_39         0x44400002
-MPP32: ge1:txctl
-MPP33: gpio		?
-MPP34: gpio		x rear button
-MPP35: gpio		?
-MPP36: gpio		?
-MPP37: sd0:d3		??
+MPP32: ge1:txctl	? (unused)
+MPP33: gpio		? (pic_com0)
+MPP34: gpio		x rear button (pic_com1)
+MPP35: gpio		? (pic_com2)
+MPP36: gpio		? (unused)
+MPP37: sd0:d3		x sd0
 MPP38: sd0:d0		x sd0
 MPP39: sd0:d1		x sd0
 +#define A38x_CUSTOMER_BOARD_1_MPP40_47         0x41144004
@@ -453,18 +453,18 @@ MPP40: sd0:d2		x sd0
 MPP41: gpio		x switch reset
 MPP42: gpio		? sw1-1
 MPP43: spi1:cs2		x mikro cs
-MPP44: sata3:prsnt
+MPP44: sata3:prsnt	? (unused)
 MPP45: ref:clk_out0	?
-MPP46: ref:clk_out1	?
-MPP47: 4		??
-+#define A38x_CUSTOMER_BOARD_1_MPP48_55         0x45333333
+MPP46: ref:clk_out1	x switch clk
+MPP47: 4		? (unused)
++#define A38x_CUSTOMER_BOARD_1_MPP48_55         0x40333333
 MPP48: tdm:pclk
 MPP49: tdm:fsync
 MPP50: tdm:drx
 MPP51: tdm:dtx
 MPP52: tdm:int
 MPP53: tdm:rst
-MPP54: sd0:d3		??
+MPP54: gpio		? (pwm)
 MPP55: spi1:cs1		x slic
 +#define A38x_CUSTOMER_BOARD_1_MPP56_63         0x00004444
 MPP56: spi1:mosi	x mikro mosi
diff --git a/drivers/pci/host/pci-mvebu.c b/drivers/pci/host/pci-mvebu.c
index 307f81d6b479..c4456511cb3a 100644
--- a/drivers/pci/host/pci-mvebu.c
+++ b/drivers/pci/host/pci-mvebu.c
@@ -53,7 +53,14 @@
 	 PCIE_CONF_ADDR_EN)
 #define PCIE_CONF_DATA_OFF	0x18fc
 #define PCIE_MASK_OFF		0x1910
+#define  PCIE_MASK_PM_PME		BIT(28)
 #define  PCIE_MASK_ENABLE_INTS          0x0f000000
+#define  PCIE_MASK_ERR_COR		BIT(18)
+#define  PCIE_MASK_ERR_NONFATAL		BIT(17)
+#define  PCIE_MASK_ERR_FATAL		BIT(16)
+#define  PCIE_MASK_FERR_DET		BIT(10)
+#define  PCIE_MASK_NFERR_DET		BIT(9)
+#define  PCIE_MASK_CORERR_DET		BIT(8)
 #define PCIE_CTRL_OFF		0x1a00
 #define  PCIE_CTRL_X1_MODE		0x0001
 #define PCIE_STAT_OFF		0x1a04
@@ -457,6 +464,54 @@ static void mvebu_pcie_handle_membase_change(struct mvebu_pcie_port *port)
 			       MVEBU_MBUS_NO_REMAP);
 }
 
+static void mvebu_pcie_handle_irq_change(struct mvebu_pcie_port *port)
+{
+	u32 reg, old;
+	u16 devctl, rtctl;
+
+	/*
+	 * Errors from downstream devices:
+	 *  bridge control register SERR: enables reception of errors
+	 * Errors from this device, or received errors:
+	 *  command SERR: enables ERR_NONFATAL and ERR_FATAL messages
+	 *   => when enabled, these conditions also flag SERR in status register
+	 *  devctl CERE: enables ERR_CORR messages
+	 *  devctl NFERE: enables ERR_NONFATAL messages
+	 *  devctl FERE: enables ERR_FATAL messages
+	 * Enabled messages then have three paths:
+	 *  1. rtctl: enables system error indication
+	 *  2. root error status register updated
+	 *  3. root error command register: forwarding via MSI
+	 */
+	old = mvebu_readl(port, PCIE_MASK_OFF);
+	reg = old & ~(PCIE_MASK_PM_PME | PCIE_MASK_FERR_DET |
+		      PCIE_MASK_NFERR_DET | PCIE_MASK_CORERR_DET |
+		      PCIE_MASK_ERR_COR | PCIE_MASK_ERR_NONFATAL |
+		      PCIE_MASK_ERR_FATAL);
+
+	devctl = port->bridge.pcie_devctl;
+	if (devctl & PCI_EXP_DEVCTL_FERE)
+		reg |= PCIE_MASK_FERR_DET | PCIE_MASK_ERR_FATAL;
+	if (devctl & PCI_EXP_DEVCTL_NFERE)
+		reg |= PCIE_MASK_NFERR_DET | PCIE_MASK_ERR_NONFATAL;
+	if (devctl & PCI_EXP_DEVCTL_CERE)
+		reg |= PCIE_MASK_CORERR_DET | PCIE_MASK_ERR_COR;
+	if (port->bridge.command & PCI_COMMAND_SERR)
+		reg |= PCIE_MASK_FERR_DET | PCIE_MASK_NFERR_DET |
+		       PCIE_MASK_ERR_FATAL | PCIE_MASK_ERR_NONFATAL;
+
+	if (!(port->bridge.bridgectrl & PCI_BRIDGE_CTL_SERR))
+		reg &= ~(PCIE_MASK_ERR_COR | PCIE_MASK_ERR_NONFATAL |
+			 PCIE_MASK_ERR_FATAL);
+
+	rtctl = port->bridge.pcie_rtctl;
+	if (rtctl & PCI_EXP_RTCTL_PMEIE)
+		reg |= PCIE_MASK_PM_PME;
+
+	if (old != reg)
+		mvebu_writel(port, reg, PCIE_MASK_OFF);
+}
+
 /*
  * Initialize the configuration space of the PCI-to-PCI bridge
  * associated with the given PCIe interface.
@@ -480,6 +535,7 @@ static void mvebu_sw_pci_bridge_init(struct mvebu_pcie_port *port)
 
 	/* Add capabilities */
 	bridge->status = PCI_STATUS_CAP_LIST;
+	bridge->bridgectrl = PCI_BRIDGE_CTL_SERR;
 }
 
 /*
@@ -552,7 +608,7 @@ static int mvebu_sw_pci_bridge_read(struct mvebu_pcie_port *port,
 
 	case PCI_INTERRUPT_LINE:
 		/* LINE PIN MIN_GNT MAX_LAT */
-		*value = 0;
+		*value = bridge->bridgectrl << 16;
 		break;
 
 	case PCISWCAP_EXP_LIST_ID:
@@ -601,6 +657,16 @@ static int mvebu_sw_pci_bridge_read(struct mvebu_pcie_port *port,
 		*value = mvebu_readl(port, PCIE_RC_RTSTA);
 		break;
 
+	case 0x100 ... 0x128:
+		*value = mvebu_readl(port, where & ~3);
+		break;
+
+	case 0x100 + PCI_ERR_ROOT_COMMAND:
+	case 0x100 + PCI_ERR_ROOT_STATUS:
+	case 0x100 + PCI_ERR_ROOT_ERR_SRC:
+		*value = 0;
+		break;
+
 	/* PCIe requires the v2 fields to be hard-wired to zero */
 	case PCISWCAP_EXP_DEVCAP2:
 	case PCISWCAP_EXP_DEVCTL2:
@@ -631,7 +697,7 @@ static int mvebu_sw_pci_bridge_write(struct mvebu_pcie_port *port,
 				     unsigned int where, int size, u32 value)
 {
 	struct mvebu_sw_pci_bridge *bridge = &port->bridge;
-	u32 mask, reg;
+	u32 mask, reg, old;
 	int err;
 
 	if (size == 4)
@@ -651,8 +717,7 @@ static int mvebu_sw_pci_bridge_write(struct mvebu_pcie_port *port,
 
 	switch (where & ~3) {
 	case PCI_COMMAND:
-	{
-		u32 old = bridge->command;
+		old = bridge->command;
 
 		if (!mvebu_has_ioport(port))
 			value &= ~PCI_COMMAND_IO;
@@ -662,8 +727,9 @@ static int mvebu_sw_pci_bridge_write(struct mvebu_pcie_port *port,
 			mvebu_pcie_handle_iobase_change(port);
 		if ((old ^ bridge->command) & PCI_COMMAND_MEMORY)
 			mvebu_pcie_handle_membase_change(port);
+		if ((old ^ bridge->command) & PCI_COMMAND_SERR)
+			mvebu_pcie_handle_irq_change(port);
 		break;
-	}
 
 	case PCI_BASE_ADDRESS_0 ... PCI_BASE_ADDRESS_1:
 		bridge->bar[((where & ~3) - PCI_BASE_ADDRESS_0) / 4] = value;
@@ -692,6 +758,17 @@ static int mvebu_sw_pci_bridge_write(struct mvebu_pcie_port *port,
 		mvebu_pcie_handle_iobase_change(port);
 		break;
 
+	case PCI_INTERRUPT_LINE:
+		value >>= 16;
+		old = bridge->bridgectrl;
+		/* PCIe only has three bits here */
+		bridge->bridgectrl = value & (PCI_BRIDGE_CTL_BUS_RESET |
+					      PCI_BRIDGE_CTL_SERR |
+					      PCI_BRIDGE_CTL_PARITY);
+		if ((old ^ bridge->bridgectrl) & PCI_BRIDGE_CTL_SERR)
+			mvebu_pcie_handle_irq_change(port);
+		break;
+
 	case PCI_PRIMARY_BUS:
 		bridge->primary_bus             = value & 0xff;
 		bridge->secondary_bus           = (value >> 8) & 0xff;
@@ -701,6 +778,14 @@ static int mvebu_sw_pci_bridge_write(struct mvebu_pcie_port *port,
 		break;
 
 	case PCISWCAP_EXP_DEVCTL:
+		old = bridge->pcie_devctl;
+		bridge->pcie_devctl = value & (PCI_EXP_DEVCTL_FERE |
+					       PCI_EXP_DEVCTL_NFERE |
+					       PCI_EXP_DEVCTL_CERE |
+					       PCI_EXP_DEVCTL_URRE);
+		if (bridge->pcie_devctl ^ old)
+			mvebu_pcie_handle_irq_change(port);
+
 		/*
 		 * Armada370 data says these bits must always
 		 * be zero when in root complex mode.
@@ -741,10 +826,24 @@ static int mvebu_sw_pci_bridge_write(struct mvebu_pcie_port *port,
 		mvebu_writel(port, value, PCIE_CAP_PCIEXP + PCI_EXP_LNKCTL);
 		break;
 
+	case PCISWCAP_EXP_RTCTL:
+		old = bridge->pcie_rtctl;
+		bridge->pcie_rtctl = value & (PCI_EXP_RTCTL_SECEE |
+					      PCI_EXP_RTCTL_SENFEE |
+					      PCI_EXP_RTCTL_SEFEE |
+					      PCI_EXP_RTCTL_PMEIE);
+		if (bridge->pcie_rtctl ^ old)
+			mvebu_pcie_handle_irq_change(port);
+		break;
+
 	case PCISWCAP_EXP_RTSTA:
 		mvebu_writel(port, value, PCIE_RC_RTSTA);
 		break;
 
+	case 0x100 ... 0x128:
+		mvebu_writel(port, value, where & ~3);
+		break;
+
 	default:
 		break;
 	}
diff --git a/drivers/pci/pcie/aspm.c b/drivers/pci/pcie/aspm.c
index 0ec649d961d7..767c34179700 100644
--- a/drivers/pci/pcie/aspm.c
+++ b/drivers/pci/pcie/aspm.c
@@ -356,8 +356,10 @@ static void pcie_aspm_cap_init(struct pcie_link_state *link, int blacklist)
 
 	/* Get upstream/downstream components' register state */
 	pcie_get_aspm_reg(parent, &upreg);
+dev_info(&parent->dev, "up support %x enabled %x\n", upreg.support, upreg.enabled);
 	child = list_entry(linkbus->devices.next, struct pci_dev, bus_list);
 	pcie_get_aspm_reg(child, &dwreg);
+dev_info(&parent->dev, "dn support %x enabled %x\n", dwreg.support, dwreg.enabled);
 
 	/*
 	 * Setup L0s state
diff --git a/drivers/pci/pcie/portdrv_core.c b/drivers/pci/pcie/portdrv_core.c
index e9270b4026f3..3845512a661b 100644
--- a/drivers/pci/pcie/portdrv_core.c
+++ b/drivers/pci/pcie/portdrv_core.c
@@ -367,6 +367,7 @@ int pcie_port_device_register(struct pci_dev *dev)
 
 	/* Get and check PCI Express port services */
 	capabilities = get_port_device_capability(dev);
+dev_info(&dev->dev, "PCIe capabilities: 0x%x\n", capabilities);
 	if (!capabilities)
 		return 0;
 
@@ -379,6 +380,7 @@ int pcie_port_device_register(struct pci_dev *dev)
 	 * if that is to be used.
 	 */
 	status = init_service_irqs(dev, irqs, capabilities);
+dev_info(&dev->dev, "init_service_irqs: %d\n", status);
 	if (status) {
 		capabilities &= PCIE_PORT_SERVICE_VC | PCIE_PORT_SERVICE_HP;
 		if (!capabilities)

---

From: Russell King <[email-masked]>
Date: Mon, 15 Aug 2016 18:57:42 +0100

From: Russell King <[email-masked]>
---
 drivers/pci/host/pci-mvebu.c | 22 ++++++++++++++++++++--
 1 file changed, 20 insertions(+), 2 deletions(-)

diff --git a/drivers/pci/host/pci-mvebu.c b/drivers/pci/host/pci-mvebu.c
index c4456511cb3a..a2afffaa42c9 100644
--- a/drivers/pci/host/pci-mvebu.c
+++ b/drivers/pci/host/pci-mvebu.c
@@ -67,6 +67,12 @@
 #define  PCIE_STAT_BUS                  0xff00
 #define  PCIE_STAT_DEV                  0x1f0000
 #define  PCIE_STAT_LINK_DOWN		BIT(0)
+#define PCIE_SSPL		0x1a0c
+#define  PCIE_SSPL_MSGEN		BIT(14)
+#define  PCIE_SSPL_SPLS(x)		(((x) & 3) << 8)
+#define  PCIE_SSPL_SPLS_VAL(x)		(((x) >> 8) & 3)
+#define  PCIE_SSPL_SPLV(x)		((x) & 0xff)
+#define  PCIE_SSPL_SPLV_VAL(x)		((x) & 0xff)
 #define PCIE_RC_RTSTA		0x1a14
 #define PCIE_DEBUG_CTRL         0x1a60
 #define  PCIE_DEBUG_SOFT_RESET		BIT(20)
@@ -121,7 +127,6 @@ struct mvebu_sw_pci_bridge {
 	u16 bridgectrl;
 
 	/* PCI express capability */
-	u32 pcie_sltcap;
 	u16 pcie_devctl;
 	u16 pcie_rtctl;
 };
@@ -642,8 +647,12 @@ static int mvebu_sw_pci_bridge_read(struct mvebu_pcie_port *port,
 		break;
 
 	case PCISWCAP_EXP_SLTCAP:
-		*value = bridge->pcie_sltcap;
+	{
+		u32 tmp = mvebu_readl(port, PCIE_SSPL);
+		*value = PCIE_SSPL_SPLS_VAL(tmp) << 15 |
+			 PCIE_SSPL_SPLV_VAL(tmp) << 7;
 		break;
+	}
 
 	case PCISWCAP_EXP_SLTCTL:
 		*value = PCI_EXP_SLTSTA_PDS << 16;
@@ -826,6 +835,15 @@ static int mvebu_sw_pci_bridge_write(struct mvebu_pcie_port *port,
 		mvebu_writel(port, value, PCIE_CAP_PCIEXP + PCI_EXP_LNKCTL);
 		break;
 
+	case PCISWCAP_EXP_SLTCAP:
+	{
+		u32 sspl = PCIE_SSPL_SPLV((value & PCI_EXP_SLTCAP_SPLV) >> 7) |
+			   PCIE_SSPL_SPLS((value & PCI_EXP_SLTCAP_SPLS) >> 15) |
+			   PCIE_SSPL_MSGEN;
+		mvebu_writel(port, sspl, PCIE_SSPL);
+		break;
+	}
+
 	case PCISWCAP_EXP_RTCTL:
 		old = bridge->pcie_rtctl;
 		bridge->pcie_rtctl = value & (PCI_EXP_RTCTL_SECEE |

---

From: Russell King <[email-masked]>
Date: Mon, 15 Aug 2016 18:57:42 +0100

From: Russell King <[email-masked]>
---
 include/linux/sfp.h | 32 --------------------------------
 1 file changed, 32 deletions(-)

diff --git a/include/linux/sfp.h b/include/linux/sfp.h
index 87b88e8d9712..14f237fe42cd 100644
--- a/include/linux/sfp.h
+++ b/include/linux/sfp.h
@@ -188,38 +188,6 @@ struct __packed sfp_eeprom_id {
 
 /* SFP EEPROM registers */
 enum {
-	SFP_PHYS_ID			= 0x00,
-	SFP_PHYS_EXT_ID			= 0x01,
-	SFP_CONNECTOR			= 0x02,
-	SFP_COMPLIANCE			= 0x03,
-	SFP_ENCODING			= 0x0b,
-	SFP_BR_NOMINAL			= 0x0c,
-	SFP_RATE_ID			= 0x0d,
-	SFP_LINK_LEN_SM_KM		= 0x0e,
-	SFP_LINK_LEN_SM_100M		= 0x0f,
-	SFP_LINK_LEN_50UM_OM2_10M	= 0x10,
-	SFP_LINK_LEN_62_5UM_OM1_10M	= 0x11,
-	SFP_LINK_LEN_COPPER_1M		= 0x12,
-	SFP_LINK_LEN_50UM_OM4_10M	= 0x12,
-	SFP_LINK_LEN_50UM_OM3_10M	= 0x13,
-	SFP_VENDOR_NAME			= 0x14,
-	SFP_VENDOR_OUI			= 0x25,
-	SFP_VENDOR_PN			= 0x28,
-	SFP_VENDOR_REV			= 0x38,
-	SFP_OPTICAL_WAVELENGTH_MSB	= 0x3c,
-	SFP_OPTICAL_WAVELENGTH_LSB	= 0x3d,
-	SFP_CABLE_SPEC			= 0x3c,
-	SFP_CC_BASE			= 0x3f,
-	SFP_OPTIONS			= 0x40,	/* 2 bytes, MSB, LSB */
-	SFP_BR_MAX			= 0x42,
-	SFP_BR_MIN			= 0x43,
-	SFP_VENDOR_SN			= 0x44,
-	SFP_DATECODE			= 0x54,
-	SFP_DIAGMON			= 0x5c,
-	SFP_ENHOPTS			= 0x5d,
-	SFP_SFF8472_COMPLIANCE		= 0x5e,
-	SFP_CC_EXT			= 0x5f,
-
 	SFP_PHYS_ID_SFP			= 0x03,
 	SFP_PHYS_EXT_ID_SFP		= 0x04,
 	SFP_CONNECTOR_UNSPEC		= 0x00,

---

From: Russell King <[email-masked]>
Date: Mon, 15 Aug 2016 18:57:42 +0100

Some PCIe cards (such as ASM1062 based SATA cards) appear to need a
slightly longer delay between reset being released and the first card
access.  Increase the default delay from 20ms to 30ms.

Signed-off-by: Russell King <[email-masked]>
---
 drivers/pci/host/pci-mvebu.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/pci/host/pci-mvebu.c b/drivers/pci/host/pci-mvebu.c
index a2afffaa42c9..f3f344d76b3a 100644
--- a/drivers/pci/host/pci-mvebu.c
+++ b/drivers/pci/host/pci-mvebu.c
@@ -1279,7 +1279,7 @@ static int mvebu_pcie_powerup(struct mvebu_pcie_port *port)
 		return ret;
 
 	if (port->reset_gpio) {
-		u32 reset_udelay = 20000;
+		u32 reset_udelay = 30000;
 
 		of_property_read_u32(port->dn, "reset-delay-us",
 				     &reset_udelay);