# Base git commit: 29b4817d4018
# (Linux 4.8-rc1)
# 
# Author:    Russell King (Mon 29 Feb 11:30:52 GMT 2016)
# Committer: Russell King (Mon  8 Aug 15:59:52 BST 2016)
# 
#   PCI: mvebu: extend PCIe reset duration
# 
#   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
# 
#   52d3df49cd0f49eb0cbd284ac525dbe9c94f7192
#    drivers/pci/host/pci-mvebu.c | 2 +-
#    1 file changed, 1 insertion(+), 1 deletion(-)
# 
# Author:    Russell King (Mon 14 Sep 10:14:57 BST 2015)
# Committer: Russell King (Mon  8 Aug 15:59:51 BST 2016)
# 
#   sfp: removal of defs
# 
#   dac7d2d5840bb7358fc1988a2ea62186f80cbd63
#    include/linux/sfp.h | 32 --------------------------------
#    1 file changed, 32 deletions(-)
# 
# Author:    Russell King (Thu 10 Sep 15:01:44 BST 2015)
# Committer: Russell King (Mon  8 Aug 15:59:50 BST 2016)
# 
#   implement slot capabilities (SSPL)
# 
#   995fd7ed2340b924c63ba17ffee98acdef14ccfc
#    drivers/pci/host/pci-mvebu.c | 22 ++++++++++++++++++++--
#    1 file changed, 20 insertions(+), 2 deletions(-)
# 
# Author:    Russell King (Sun  6 Sep 23:45:17 BST 2015)
# Committer: Russell King (Mon  8 Aug 15:59:50 BST 2016)
# 
#   mvebu/clearfog updates
# 
#   Signed-off-by: Russell King
# 
#   ce722aa824847b5dbdba3ff32508b08e1060f6d5
#    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(-)
# 
# Author:    Russell King (Fri 28 Aug 19:15:51 BST 2015)
# Committer: Russell King (Mon  8 Aug 15:59:49 BST 2016)
# 
#   ARM: dts: Add Clearfog A1 rev 2.0 DT file
# 
#   Signed-off-by: Russell King
# 
#   d91fadfe16744fdc64168cce4582b8db8135b987
#    arch/arm/boot/dts/armada-388-clearfog.dts | 50 +++++++++++++++++++++++++++++++
#    1 file changed, 50 insertions(+)
# 
# Author:    Russell King (Sat 12 Sep 18:43:39 BST 2015)
# Committer: Russell King (Mon  8 Aug 15:59:48 BST 2016)
# 
#   ARM: dts: armada388-clearfog: add SFP module support
# 
#   Add SFP module support for Clearfog using the SFP phylink support.
# 
#   Signed-off-by: Russell King
# 
#   a482b0695d267b70afb80dfbbfe756a6720d39e0
#    arch/arm/boot/dts/armada-388-clearfog.dts | 44 ++++++++-----------------------
#    1 file changed, 11 insertions(+), 33 deletions(-)
# 
# Author:    Russell King (Mon  4 Jul 10:17:28 BST 2016)
# Committer: Russell King (Mon  8 Aug 15:59:48 BST 2016)
# 
#   ARM: dts: armada388-clearfog: number LAN ports properly
# 
#   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
# 
#   aa0a9c273b6a356ea932ac9bbcbbff204677995d
#    arch/arm/boot/dts/armada-388-clearfog.dts | 8 ++++----
#    1 file changed, 4 insertions(+), 4 deletions(-)
# 
# Author:    Russell King (Mon  8 Aug 15:59:46 BST 2016)
# Committer: Russell King (Mon  8 Aug 15:59:46 BST 2016)
# 
#   Merge branches 'mvebu-cpuidle' and 'phy' into clearfog
# 
# Author:    Russell King (Tue 12 Jul 16:45:43 BST 2016)
# Committer: Russell King (Mon  8 Aug 15:58:31 BST 2016)
# 
#   phy: marvell: 88E1540: add flow control support
# 
#   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.
# 
#   Signed-off-by: Russell King
# 
#   39341dbf4dc156a923f75e5dbde33d74accc0d3b
#    drivers/net/phy/marvell.c | 2 +-
#    1 file changed, 1 insertion(+), 1 deletion(-)
# 
# Author:    Russell King (Tue 12 Jul 16:45:43 BST 2016)
# Committer: Russell King (Mon  8 Aug 15:58:31 BST 2016)
# 
#   phy: marvell: 88E1111: add flow control support
# 
#   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.
# 
#   Signed-off-by: Russell King
# 
#   9cddd36dff8a031f65927eead0656ff7889d2d71
#    drivers/net/phy/marvell.c | 2 +-
#    1 file changed, 1 insertion(+), 1 deletion(-)
# 
# Author:    Russell King (Thu  1 Oct 00:34:08 BST 2015)
# Committer: Russell King (Mon  8 Aug 15:58:31 BST 2016)
# 
#   phy: marvell: 88E1512: add flow control support
# 
#   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
#   Signed-off-by: Russell King
# 
#   6b9c6f617465627c247a0eaff562dd7eec01d351
#    drivers/net/phy/marvell.c | 3 ++-
#    1 file changed, 2 insertions(+), 1 deletion(-)
# 
# Author:    Russell King (Thu  8 Oct 23:49:47 BST 2015)
# Committer: Russell King (Mon  8 Aug 15:58:30 BST 2016)
# 
#   sfp/phylink: hook up eeprom functions
# 
#   Signed-off-by: Russell King
# 
#   ad99c50223c78e12c81d794b310c700922ba8363
#    drivers/net/phy/sfp.c | 19 +++++++++++--------
#    1 file changed, 11 insertions(+), 8 deletions(-)
# 
# Author:    Russell King (Thu  1 Oct 23:32:39 BST 2015)
# Committer: Russell King (Mon  8 Aug 15:58:30 BST 2016)
# 
#   net: mvneta: add module EEPROM reading support
# 
#   Signed-off-by: Russell King
# 
#   396d53b687bac6284537896613d2bf4a9c571479
#    drivers/net/ethernet/marvell/mvneta.c | 18 ++++++++++++++++++
#    1 file changed, 18 insertions(+)
# 
# Author:    Russell King (Thu  1 Oct 23:10:05 BST 2015)
# Committer: Russell King (Mon  8 Aug 15:58:30 BST 2016)
# 
#   phylink: add module EEPROM support
# 
#   Add support for reading module EEPROMs through phylink.
# 
#   Reviewed-by: Florian Fainelli
#   Signed-off-by: Russell King
# 
#   0c3477cbca12b91f35ffc3f76ebd7705b6aaa9e9
#    drivers/net/phy/phylink.c | 66 +++++++++++++++++++++++++++++++++++++++++++++++
#    include/linux/phylink.h   | 12 +++++++++
#    2 files changed, 78 insertions(+)
# 
# Author:    Russell King (Tue 29 Sep 15:17:39 BST 2015)
# Committer: Russell King (Mon  8 Aug 15:58:30 BST 2016)
# 
#   net: mvneta: add EEE support
# 
#   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
# 
#   e6e6a99e0a839859c8649bc89115fd754f8651d8
#    drivers/net/ethernet/marvell/mvneta.c | 87 +++++++++++++++++++++++++++++++++++
#    1 file changed, 87 insertions(+)
# 
# Author:    Russell King (Thu  1 Oct 21:19:53 BST 2015)
# Committer: Russell King (Mon  8 Aug 15:58:29 BST 2016)
# 
#   phylink: add EEE support
# 
#   Add EEE hooks to phylink to allow the phylib EEE functions for the
#   connected phy to be safely accessed.
# 
#   Signed-off-by: Russell King
# 
#   ecbb95bca1550297c84407da7c0b61333d861b39
#    drivers/net/phy/phylink.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++-
#    include/linux/phylink.h   |  7 +++++-
#    2 files changed, 63 insertions(+), 2 deletions(-)
# 
# Author:    Russell King (Tue 12 Jul 00:04:13 BST 2016)
# Committer: Russell King (Mon  8 Aug 15:58:29 BST 2016)
# 
#   net: mvneta: enable flow control for fixed connections
# 
#   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
# 
#   0e5bc6e10ac488f152c2f458d9fd5b2a6ca76416
#    drivers/net/ethernet/marvell/mvneta.c | 11 +++++++----
#    1 file changed, 7 insertions(+), 4 deletions(-)
# 
# Author:    Russell King (Thu  1 Oct 00:34:08 BST 2015)
# Committer: Russell King (Mon  8 Aug 15:58:29 BST 2016)
# 
#   net: mvneta: enable flow control for PHY connections
# 
#   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
#   Signed-off-by: Russell King
# 
#   be33da37b6087ff0b50f222b48e6c293e19316a7
#    drivers/net/ethernet/marvell/mvneta.c | 2 ++
#    1 file changed, 2 insertions(+)
# 
# Author:    Russell King (Thu  1 Oct 17:41:44 BST 2015)
# Committer: Russell King (Mon  8 Aug 15:58:29 BST 2016)
# 
#   net: mvneta: add flow control support via phylink
# 
#   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
# 
#   5974cf0d112fedf9ef724b3e411fb2b880bedb1b
#    drivers/net/ethernet/marvell/mvneta.c | 28 +++++++++++++++++++++++++++-
#    1 file changed, 27 insertions(+), 1 deletion(-)
# 
# Author:    Russell King (Thu  1 Oct 20:32:07 BST 2015)
# Committer: Russell King (Mon  8 Aug 15:58:28 BST 2016)
# 
#   phylink: add flow control support
# 
#   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
# 
#   4272a0b14d30d711701641d4cfc2c7041ad43e02
#    drivers/net/phy/phylink.c | 133 +++++++++++++++++++++++++++++++++++++++++++++-
#    include/linux/phylink.h   |   8 +++
#    2 files changed, 139 insertions(+), 2 deletions(-)
# 
# Author:    Russell King (Thu  1 Oct 19:40:31 BST 2015)
# Committer: Russell King (Mon  8 Aug 15:58:28 BST 2016)
# 
#   net: mvneta: add nway_reset support
# 
#   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
# 
#   27d017bec8f3408fa4399ebff0fe6c69a953e695
#    drivers/net/ethernet/marvell/mvneta.c | 8 ++++++++
#    1 file changed, 8 insertions(+)
# 
# Author:    Russell King (Thu  1 Oct 20:27:19 BST 2015)
# Committer: Russell King (Mon  8 Aug 15:58:28 BST 2016)
# 
#   phylink: add ethtool nway_reset support
# 
#   Add ethtool nway_reset support to phylink, to allow userspace to
#   request a re-negotiation of the link.
# 
#   Reviewed-by: Florian Fainelli
#   Signed-off-by: Russell King
# 
#   0c6a9fa59812af907b99afb3fd10e932b69880e8
#    drivers/net/phy/phylink.c | 14 ++++++++++++++
#    include/linux/phylink.h   |  1 +
#    2 files changed, 15 insertions(+)
# 
# Author:    Russell King (Fri  2 Oct 22:46:54 BST 2015)
# Committer: Russell King (Mon  8 Aug 15:58:28 BST 2016)
# 
#   phy: fixed-phy: remove fixed_phy_update_state()
# 
#   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
#   Signed-off-by: Russell King
# 
#   70796de7a4f8d35d37c1e0ed0558498392e35688
#    drivers/net/phy/fixed_phy.c | 31 -------------------------------
#    include/linux/phy_fixed.h   |  9 ---------
#    2 files changed, 40 deletions(-)
# 
# Author:    Russell King (Wed 16 Sep 21:27:10 BST 2015)
# Committer: Russell King (Mon  8 Aug 15:58:27 BST 2016)
# 
#   net: mvneta: convert to phylink
# 
#   Convert mvneta to use phylink, which models the MAC to PHY link in
#   a generic, reusable form.
# 
#   Signed-off-by: Russell King
# 
#   c4881310fe37eb07ddc804a55cf865450f5b2c40
#    drivers/net/ethernet/marvell/Kconfig  |   2 +-
#    drivers/net/ethernet/marvell/mvneta.c | 455 +++++++++++++++++-----------------
#    2 files changed, 226 insertions(+), 231 deletions(-)
# 
# Author:    Russell King (Sun 13 Sep 01:06:31 BST 2015)
# Committer: Russell King (Mon  8 Aug 15:58:27 BST 2016)
# 
#   sfp: display SFP module information
# 
#   Signed-off-by: Russell King
# 
#   fe17c5959d02b194986ab8070b1691c50bf9ff85
#    drivers/net/phy/sfp.c | 247 +++++++++++++++++++++++++++++++++++++++++++++++++-
#    1 file changed, 246 insertions(+), 1 deletion(-)
# 
# Author:    Russell King (Sat 12 Sep 18:43:39 BST 2015)
# Committer: Russell King (Mon  8 Aug 15:58:27 BST 2016)
# 
#   sfp: add phylink based SFP module support
# 
#   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
# 
#   0d3524af8bceb1d2b13b3179960d23fe1c1cbc66
#    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
# 
# Author:    Russell King (Thu 24 Sep 11:01:13 BST 2015)
# Committer: Russell King (Mon  8 Aug 15:58:27 BST 2016)
# 
#   phylink: add hooks for SFP support
# 
#   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
#   Signed-off-by: Russell King
# 
#   df21c0a8a41080beec0f6d25412b0cbbce482e68
#    drivers/net/phy/phylink.c | 82 +++++++++++++++++++++++++++++++++++++++++++++++
#    include/linux/phylink.h   |  6 ++++
#    2 files changed, 88 insertions(+)
# 
# Author:    Russell King (Tue 22 Sep 20:52:18 BST 2015)
# Committer: Russell King (Mon  8 Aug 15:58:26 BST 2016)
# 
#   phylink: add phylink infrastructure
# 
#   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
# 
#   fe5769ee7e6d7d2988dfad4f6266175246c48e11
#    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
# 
# Author:    Russell King (Fri 25 Sep 17:43:52 BST 2015)
# Committer: Russell King (Mon  8 Aug 15:58:26 BST 2016)
# 
#   phy: add I2C mdio bus
# 
#   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
# 
#   ac0e4dcfb90ec7667b15606bb169f8e45d46723b
#    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
# 
# Author:    Russell King (Sun 18 Oct 19:51:10 BST 2015)
# Committer: Russell King (Mon  8 Aug 15:58:26 BST 2016)
# 
#   phy: export phy_speed_to_str() for phylink
# 
#   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
#   Signed-off-by: Russell King
# 
#   e71222fc9b327a9a65b9e3965dfdb92c67d98998
#    drivers/net/phy/phy.c | 3 ++-
#    include/linux/phy.h   | 1 +
#    2 files changed, 3 insertions(+), 1 deletion(-)
# 
# Author:    Russell King (Fri 16 Oct 12:18:41 BST 2015)
# Committer: Russell King (Mon  8 Aug 15:58:26 BST 2016)
# 
#   phy: export phy_start_machine() for phylink
# 
#   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
#   Signed-off-by: Russell King
# 
#   6bac8cde886102ecde4722223f48e2778a3935be
#    drivers/net/phy/phy.c | 1 +
#    1 file changed, 1 insertion(+)
# 
# Author:    Russell King (Fri 18 Sep 14:42:16 BST 2015)
# Committer: Russell King (Mon  8 Aug 15:58:26 BST 2016)
# 
#   phy: provide a hook for link up/link down events
# 
#   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
#   Signed-off-by: Russell King
# 
#   a1152a3cbcdadd57be418bac2d05a602c2b7a944
#    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(-)
# 
# Author:    Russell King (Sat  3 Oct 09:13:05 BST 2015)
# Committer: Russell King (Mon  8 Aug 15:10:02 BST 2016)
# 
#   cpuidle: mvebu: indicate failure to enter deeper sleep states
# 
#   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
# 
#   a0b75738e32f6d401f16686f8af3565865a3cae0
#    drivers/cpuidle/cpuidle-mvebu-v7.c | 6 +++++-
#    1 file changed, 5 insertions(+), 1 deletion(-)
# 
diff --git a/arch/arm/boot/dts/armada-388-clearfog.dts b/arch/arm/boot/dts/armada-388-clearfog.dts
index 2e0556af6e5e..72cc39bfda52 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>;
@@ -390,12 +368,12 @@
 
 			port@0 {
 				reg = <0>;
-				label = "lan1";
+				label = "lan5";
 			};
 
 			port@1 {
 				reg = <1>;
-				label = "lan2";
+				label = "lan4";
 			};
 
 			port@2 {
@@ -405,12 +383,12 @@
 
 			port@3 {
 				reg = <3>;
-				label = "lan4";
+				label = "lan2";
 			};
 
 			port@4 {
 				reg = <4>;
-				label = "lan5";
+				label = "lan1";
 			};
 
 			port@5 {
@@ -444,3 +422,53 @@
 		};
 	};
 };
+/*
++#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: gpio		x switch irq
++#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		? (config)
+MPP31: ge1:txd3		? (config)
++#define A38x_CUSTOMER_BOARD_1_MPP32_39         0x44400002
+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
+MPP40: sd0:d2		x sd0
+MPP41: gpio		x switch reset
+MPP42: gpio		? sw1-1
+MPP43: spi1:cs2		x mikro cs
+MPP44: sata3:prsnt	? (unused)
+MPP45: ref:clk_out0	?
+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: gpio		? (pwm)
+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
+*/
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;
 }
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..ea972f921651 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)
@@ -235,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 */
@@ -308,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;
@@ -316,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", },
@@ -350,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 {
@@ -398,21 +419,19 @@ 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;
 	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];
@@ -1238,44 +1257,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 +1404,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 +2596,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 +2629,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 +2949,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 +2957,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 +3127,246 @@ 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;
+		state->advertising = ADVERTISED_1000baseT_Full |
+				     ADVERTISED_Autoneg;
+		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;
+	}
+
+	/* All modes support flow control */
+	state->supported |= SUPPORTED_Pause;
+	state->advertising |= ADVERTISED_Pause;
+
+	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;
 
-	if (phydev->link) {
-		if ((pp->speed != phydev->speed) ||
-		    (pp->duplex != phydev->duplex)) {
-			u32 val;
+	gmac_stat = mvreg_read(pp, MVNETA_GMAC_STATUS);
 
-			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 (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;
 
-			if (phydev->duplex)
-				val |= 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->speed == SPEED_1000)
-				val |= MVNETA_GMAC_CONFIG_GMII_SPEED;
-			else if (phydev->speed == SPEED_100)
-				val |= MVNETA_GMAC_CONFIG_MII_SPEED;
+	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;
 
-			mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val);
+	return 1;
+}
 
-			pp->duplex = phydev->duplex;
-			pp->speed  = phydev->speed;
-		}
+static void mvneta_mac_an_restart(struct net_device *ndev, unsigned int mode)
+{
+	struct mvneta_port *pp = netdev_priv(ndev);
+
+	if (mode == MLO_AN_8023Z) {
+		u32 gmac_an = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG);
+
+		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;
+	if (state->pause & MLO_PAUSE_TXRX_MASK)
+		new_an |= MVNETA_GMAC_CONFIG_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->pause & MLO_PAUSE_AN && 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_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 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;
+	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);
 	}
 
-	phy_dev->supported &= PHY_GBIT_FEATURES;
-	phy_dev->advertising = phy_dev->supported;
+	pp->eee_active = false;
+	mvneta_set_eee(pp, false);
+}
 
-	pp->phy_dev = phy_dev;
-	pp->link    = 0;
-	pp->duplex  = 0;
-	pp->speed   = 0;
+static void mvneta_mac_link_up(struct net_device *ndev, unsigned int mode,
+			       struct phy_device *phy)
+{
+	struct mvneta_port *pp = netdev_priv(ndev);
+	u32 val;
 
-	return 0;
+	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);
+	}
+
+	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 = {
+	.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,
+};
+
+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 +3608,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 +3618,22 @@ 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);
+	return phylink_ethtool_set_settings(pp->phylink, cmd);
+}
 
-		if (netif_running(dev)) {
-			mvneta_port_down(pp);
-			mvneta_port_up(pp);
-		}
-	}
+static int mvneta_ethtool_nway_reset(struct net_device *dev)
+{
+	struct mvneta_port *pp = netdev_priv(dev);
 
-	return phy_ethtool_sset(pp->phy_dev, cmd);
+	return phylink_ethtool_nway_reset(pp->phylink);
 }
 
 /* Set interrupt coalescing for ethtools */
@@ -3649,6 +3725,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)
 {
@@ -3665,26 +3757,35 @@ 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;
+		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;
 	}
 }
 
@@ -3807,6 +3908,63 @@ 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)
+{
+	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,
@@ -3823,11 +3981,14 @@ 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,
 	.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,
@@ -3835,6 +3996,10 @@ 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,
 };
 
 /* Initialize hw */
@@ -3960,14 +4125,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 +4147,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 +4162,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 +4174,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 +4282,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 +4301,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 +4311,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 +4338,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) {
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index 47a64342cc16..dabf1d4ca4ba 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
@@ -169,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
@@ -190,6 +205,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..7dd7b96df819 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
@@ -25,6 +26,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
@@ -49,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/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/drivers/net/phy/marvell.c b/drivers/net/phy/marvell.c
index c2dcf02df202..d0ad2c38ebfb 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,
@@ -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,
@@ -1690,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,
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
diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c
index c5dc2c363f96..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:			\
@@ -606,6 +607,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
@@ -891,6 +893,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 +944,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 +956,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 +981,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 +991,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 +1019,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 +1032,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 +1052,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 +1068,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..1877889e0439 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;
 
@@ -975,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..6a18947e151c
--- /dev/null
+++ b/drivers/net/phy/phylink.c
@@ -0,0 +1,1151 @@
+/*
+ * 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/list.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)
+
+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;
+
+	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;
+
+	const struct phylink_module_ops *module_ops;
+	void *module_data;
+};
+
+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;
+
+		/* 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"))
+			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);
+}
+
+/* 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)
+{
+	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;
+			phylink_resolve_flow(pl, &link_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;
+				link_state.pause |= pl->phy_state.pause;
+				phylink_resolve_flow(pl, &link_state);
+			}
+			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,
+					     pl->phydev);
+
+			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",
+				    phylink_pause_to_str(link_state.pause));
+		}
+	}
+	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->link_config.pause = MLO_PAUSE_AN;
+	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);
+	}
+
+	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);
+}
+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_resolve_flow(pl, &pl->link_config);
+	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);
+
+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);
+
+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);
+
+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;
+
+	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.
+ *
+ * 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);
+
+
+
+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);
+	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/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c
new file mode 100644
index 000000000000..de6251701340
--- /dev/null
+++ b/drivers/net/phy/sfp.c
@@ -0,0 +1,1234 @@
+#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;
+}
+
+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)
+{
+	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];
+	char options[80];
+	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';
+	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 ||
+	    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);
+}
+
+static int sfp_module_info(void *priv, struct ethtool_modinfo *modinfo)
+{
+	struct sfp *sfp = 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_module_eeprom(void *priv, struct ethtool_eeprom *ee, u8 *data)
+{
+	struct sfp *sfp = 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;
+}
+
+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)
+{
+	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);
+		phylink_unregister_module(sfp->phylink, sfp);
+		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);
+		phylink_register_module(sfp->phylink, sfp, &sfp_module_ops);
+	}
+
+	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/drivers/pci/host/pci-mvebu.c b/drivers/pci/host/pci-mvebu.c
index 307f81d6b479..f3f344d76b3a 100644
--- a/drivers/pci/host/pci-mvebu.c
+++ b/drivers/pci/host/pci-mvebu.c
@@ -53,13 +53,26 @@
 	 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
 #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)
@@ -114,7 +127,6 @@ struct mvebu_sw_pci_bridge {
 	u16 bridgectrl;
 
 	/* PCI express capability */
-	u32 pcie_sltcap;
 	u16 pcie_devctl;
 	u16 pcie_rtctl;
 };
@@ -457,6 +469,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 +540,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 +613,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:
@@ -586,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;
@@ -601,6 +666,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 +706,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 +726,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 +736,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 +767,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 +787,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 +835,33 @@ 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 |
+					      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;
 	}
@@ -1162,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);
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)
diff --git a/include/linux/phy.h b/include/linux/phy.h
index 2d24b283aa2d..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,10 +420,12 @@ struct phy_device {
 
 	struct mutex lock;
 
+	struct phylink *phylink;
 	struct net_device *attached_dev;
 
 	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), \
@@ -811,6 +814,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);
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 */
diff --git a/include/linux/phylink.h b/include/linux/phylink.h
new file mode 100644
index 000000000000..01d442b08e62
--- /dev/null
+++ b/include/linux/phylink.h
@@ -0,0 +1,102 @@
+#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_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,
+	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 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 *);
+
+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_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_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);
+void phylink_enable(struct phylink *pl);
+struct phylink *phylink_lookup_by_netdev(struct net_device *ndev);
+
+#endif
diff --git a/include/linux/sfp.h b/include/linux/sfp.h
new file mode 100644
index 000000000000..14f237fe42cd
--- /dev/null
+++ b/include/linux/sfp.h
@@ -0,0 +1,307 @@
+#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_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
