All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH RFC 00/26] Phylink & SFP support
@ 2015-12-07 17:35 Russell King - ARM Linux
  2015-12-07 17:37 ` [PATCH RFC 01/26] phy: move fixed_phy MII register generation to a library Russell King
                   ` (27 more replies)
  0 siblings, 28 replies; 58+ messages in thread
From: Russell King - ARM Linux @ 2015-12-07 17:35 UTC (permalink / raw)
  To: Florian Fainelli, Thomas Petazzoni; +Cc: netdev

Hi,

SFP modules are hot-pluggable ethernet transceivers; they can be
detected at runtime and accordingly configured.  There are a range of
modules offering many different features.

Some SFP modules have PHYs conventional integrated into them, others
drive a laser diode from the Serdes bus.  Some have monitoring, others
do not.

Some SFP modules want to use SGMII over the Serdes link, others want
to use 1000base-X over the Serdes link.

This makes it non-trivial to support with the existing code structure.
Not wanting to write something specific to the mvneta driver, I decided
to have a go at coming up with something more generic.

My initial attempts were to provide a PHY driver, but I found that
phylib's state machine got in the way, and it was hard to support two
chained PHYs.  Conversely, having a fixed DT specified setup (via
the fixed phy infrastructure) would allow some SFP modules to work, but
not others.  The same is true of the "managed" in-band status (which
is SGMII.)

The result is that I came up with phylink - an infrastructure layer
which sits between the network driver and any attached PHY, and a
SFP module layer detects the SFP module, and configures phylink
accordingly.

Overall, this supports:

* switching the serdes mode at the NIC driver
* controlling autonegotiation and autoneg results
* allowing PHYs to be hotplugged
* allowing SFP modules to be hotplugged with proper link indication
* fixed-mode links without involving phylib
* flow control
* EEE support
* reading SFP module EEPROMs

Overall, phylink supports several link modes, with dynamic switching
possible between these:
* A true fixed link mode, where the parameters are set by DT.
* PHY mode, where we read the negotiation results from the PHY registers
  and pass them to the NIC driver.
* SGMII mode, where the in-band status indicates the speed, duplex and
  flow control settings of the link partner.
* 1000base-X mode, where the in-band status indicates only duplex and
  flow control settings (different, incompatible bit layout from SGMII.)

Ethtool support is included, as well as emulation of the MII registers
for situations where a PHY is not attached, giving compatible emulation
of existing user interfaces where required.

The patches here include modification of mvneta (against 4.4-rc1, so
probably won't apply to current development tips.)  It basically
hooks into the places where the phylib would hook into.

DT wise, the changes needed to support SFP look like this (example
taken from Clearfog):

 			ethernet@34000 {
+				managed = "in-band-status";
 				phy-mode = "sgmii";
 				status = "okay";
-
-				fixed-link {
-					speed = <1000>;
-					full-duplex;
-				};
 			};
...
+	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>;
+	};

These DT changes are omitted from this patch set as the baseline DT
file is not in mainline yet (has been submitted.)

 drivers/net/ethernet/marvell/Kconfig  |    2 +-
 drivers/net/ethernet/marvell/mvneta.c |  537 +++++++++-----
 drivers/net/phy/Kconfig               |   29 +
 drivers/net/phy/Makefile              |    6 +-
 drivers/net/phy/fixed_phy.c           |  178 +----
 drivers/net/phy/marvell.c             |    2 +-
 drivers/net/phy/mdio-i2c.c            |   90 +++
 drivers/net/phy/mdio-i2c.h            |   19 +
 drivers/net/phy/phy.c                 |   46 +-
 drivers/net/phy/phy_device.c          |   15 +
 drivers/net/phy/phylink.c             | 1138 ++++++++++++++++++++++++++++++
 drivers/net/phy/sfp.c                 | 1235 +++++++++++++++++++++++++++++++++
 drivers/net/phy/swphy.c               |  179 +++++
 drivers/net/phy/swphy.h               |    9 +
 include/linux/phy.h                   |    4 +
 include/linux/phy_fixed.h             |    9 -
 include/linux/phylink.h               |  102 +++
 include/linux/sfp.h                   |  338 +++++++++
 18 files changed, 3577 insertions(+), 361 deletions(-)

-- 
FTTC broadband for 0.8mile line: currently at 9.6Mbps down 400kbps up
according to speedtest.net.

^ permalink raw reply	[flat|nested] 58+ messages in thread

* [PATCH RFC 01/26] phy: move fixed_phy MII register generation to a library
  2015-12-07 17:35 [PATCH RFC 00/26] Phylink & SFP support Russell King - ARM Linux
@ 2015-12-07 17:37 ` Russell King
  2016-01-07 19:47   ` Florian Fainelli
  2015-12-07 17:37 ` [PATCH RFC 02/26] phy: convert swphy register generation to tabular form Russell King
                   ` (26 subsequent siblings)
  27 siblings, 1 reply; 58+ messages in thread
From: Russell King @ 2015-12-07 17:37 UTC (permalink / raw)
  To: Florian Fainelli, Thomas Petazzoni; +Cc: netdev

Move the fixed_phy MII register generation to a library to allow other
software phy implementations to use this code.

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
 drivers/net/phy/Kconfig     |   4 ++
 drivers/net/phy/Makefile    |   3 +-
 drivers/net/phy/fixed_phy.c |  95 ++-------------------------------
 drivers/net/phy/swphy.c     | 126 ++++++++++++++++++++++++++++++++++++++++++++
 drivers/net/phy/swphy.h     |   8 +++
 5 files changed, 143 insertions(+), 93 deletions(-)
 create mode 100644 drivers/net/phy/swphy.c
 create mode 100644 drivers/net/phy/swphy.h

diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index 60994a83a0d6..c59f957fc282 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -12,6 +12,9 @@ menuconfig PHYLIB
 
 if PHYLIB
 
+config SWPHY
+	bool
+
 comment "MII PHY device drivers"
 
 config AQUANTIA_PHY
@@ -159,6 +162,7 @@ config MICROCHIP_PHY
 config FIXED_PHY
 	tristate "Driver for MDIO Bus/PHY emulation with fixed speed/link PHYs"
 	depends on PHYLIB
+	select SWPHY
 	---help---
 	  Adds the platform "fixed" MDIO Bus to cover the boards that use
 	  PHYs that are not connected to the real MDIO bus.
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index f31a4e25cf15..31bdf193adbd 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -1,6 +1,7 @@
 # Makefile for Linux PHY drivers
 
-libphy-objs			:= phy.o phy_device.o mdio_bus.o
+libphy-y			:= phy.o phy_device.o mdio_bus.o
+libphy-$(CONFIG_SWPHY)		+= swphy.o
 
 obj-$(CONFIG_PHYLIB)		+= libphy.o
 obj-$(CONFIG_AQUANTIA_PHY)	+= aquantia.o
diff --git a/drivers/net/phy/fixed_phy.c b/drivers/net/phy/fixed_phy.c
index e23bf5b90e17..9a448e7f8f4e 100644
--- a/drivers/net/phy/fixed_phy.c
+++ b/drivers/net/phy/fixed_phy.c
@@ -24,6 +24,8 @@
 #include <linux/of.h>
 #include <linux/gpio.h>
 
+#include "swphy.h"
+
 #define MII_REGS_NUM 29
 
 struct fixed_mdio_bus {
@@ -49,101 +51,10 @@ static struct fixed_mdio_bus platform_fmb = {
 
 static int fixed_phy_update_regs(struct fixed_phy *fp)
 {
-	u16 bmsr = BMSR_ANEGCAPABLE;
-	u16 bmcr = 0;
-	u16 lpagb = 0;
-	u16 lpa = 0;
-
 	if (gpio_is_valid(fp->link_gpio))
 		fp->status.link = !!gpio_get_value_cansleep(fp->link_gpio);
 
-	if (fp->status.duplex) {
-		switch (fp->status.speed) {
-		case 1000:
-			bmsr |= BMSR_ESTATEN;
-			break;
-		case 100:
-			bmsr |= BMSR_100FULL;
-			break;
-		case 10:
-			bmsr |= BMSR_10FULL;
-			break;
-		default:
-			break;
-		}
-	} else {
-		switch (fp->status.speed) {
-		case 1000:
-			bmsr |= BMSR_ESTATEN;
-			break;
-		case 100:
-			bmsr |= BMSR_100HALF;
-			break;
-		case 10:
-			bmsr |= BMSR_10HALF;
-			break;
-		default:
-			break;
-		}
-	}
-
-	if (fp->status.link) {
-		bmsr |= BMSR_LSTATUS | BMSR_ANEGCOMPLETE;
-
-		if (fp->status.duplex) {
-			bmcr |= BMCR_FULLDPLX;
-
-			switch (fp->status.speed) {
-			case 1000:
-				bmcr |= BMCR_SPEED1000;
-				lpagb |= LPA_1000FULL;
-				break;
-			case 100:
-				bmcr |= BMCR_SPEED100;
-				lpa |= LPA_100FULL;
-				break;
-			case 10:
-				lpa |= LPA_10FULL;
-				break;
-			default:
-				pr_warn("fixed phy: unknown speed\n");
-				return -EINVAL;
-			}
-		} else {
-			switch (fp->status.speed) {
-			case 1000:
-				bmcr |= BMCR_SPEED1000;
-				lpagb |= LPA_1000HALF;
-				break;
-			case 100:
-				bmcr |= BMCR_SPEED100;
-				lpa |= LPA_100HALF;
-				break;
-			case 10:
-				lpa |= LPA_10HALF;
-				break;
-			default:
-				pr_warn("fixed phy: unknown speed\n");
-			return -EINVAL;
-			}
-		}
-
-		if (fp->status.pause)
-			lpa |= LPA_PAUSE_CAP;
-
-		if (fp->status.asym_pause)
-			lpa |= LPA_PAUSE_ASYM;
-	}
-
-	fp->regs[MII_PHYSID1] = 0;
-	fp->regs[MII_PHYSID2] = 0;
-
-	fp->regs[MII_BMSR] = bmsr;
-	fp->regs[MII_BMCR] = bmcr;
-	fp->regs[MII_LPA] = lpa;
-	fp->regs[MII_STAT1000] = lpagb;
-
-	return 0;
+	return swphy_update_regs(fp->regs, &fp->status);
 }
 
 static int fixed_mdio_read(struct mii_bus *bus, int phy_addr, int reg_num)
diff --git a/drivers/net/phy/swphy.c b/drivers/net/phy/swphy.c
new file mode 100644
index 000000000000..0551a79a2454
--- /dev/null
+++ b/drivers/net/phy/swphy.c
@@ -0,0 +1,126 @@
+/*
+ * Software PHY emulation
+ *
+ * Code taken from fixed_phy.c by Russell King <rmk+kernel@arm.linux.org.uk>
+ *
+ * Author: Vitaly Bordug <vbordug@ru.mvista.com>
+ *         Anton Vorontsov <avorontsov@ru.mvista.com>
+ *
+ * Copyright (c) 2006-2007 MontaVista Software, Inc.
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ */
+#include <linux/export.h>
+#include <linux/mii.h>
+#include <linux/phy.h>
+#include <linux/phy_fixed.h>
+
+#include "swphy.h"
+
+/**
+ * swphy_update_regs - update MII register array with fixed phy state
+ * @regs: array of 32 registers to update
+ * @state: fixed phy status
+ *
+ * Update the array of MII registers with the fixed phy link, speed,
+ * duplex and pause mode settings.
+ */
+int swphy_update_regs(u16 *regs, const struct fixed_phy_status *state)
+{
+	u16 bmsr = BMSR_ANEGCAPABLE;
+	u16 bmcr = 0;
+	u16 lpagb = 0;
+	u16 lpa = 0;
+
+	if (state->duplex) {
+		switch (state->speed) {
+		case 1000:
+			bmsr |= BMSR_ESTATEN;
+			break;
+		case 100:
+			bmsr |= BMSR_100FULL;
+			break;
+		case 10:
+			bmsr |= BMSR_10FULL;
+			break;
+		default:
+			break;
+		}
+	} else {
+		switch (state->speed) {
+		case 1000:
+			bmsr |= BMSR_ESTATEN;
+			break;
+		case 100:
+			bmsr |= BMSR_100HALF;
+			break;
+		case 10:
+			bmsr |= BMSR_10HALF;
+			break;
+		default:
+			break;
+		}
+	}
+
+	if (state->link) {
+		bmsr |= BMSR_LSTATUS | BMSR_ANEGCOMPLETE;
+
+		if (state->duplex) {
+			bmcr |= BMCR_FULLDPLX;
+
+			switch (state->speed) {
+			case 1000:
+				bmcr |= BMCR_SPEED1000;
+				lpagb |= LPA_1000FULL;
+				break;
+			case 100:
+				bmcr |= BMCR_SPEED100;
+				lpa |= LPA_100FULL;
+				break;
+			case 10:
+				lpa |= LPA_10FULL;
+				break;
+			default:
+				pr_warn("swphy: unknown speed\n");
+				return -EINVAL;
+			}
+		} else {
+			switch (state->speed) {
+			case 1000:
+				bmcr |= BMCR_SPEED1000;
+				lpagb |= LPA_1000HALF;
+				break;
+			case 100:
+				bmcr |= BMCR_SPEED100;
+				lpa |= LPA_100HALF;
+				break;
+			case 10:
+				lpa |= LPA_10HALF;
+				break;
+			default:
+				pr_warn("swphy: unknown speed\n");
+				return -EINVAL;
+			}
+		}
+
+		if (state->pause)
+			lpa |= LPA_PAUSE_CAP;
+
+		if (state->asym_pause)
+			lpa |= LPA_PAUSE_ASYM;
+	}
+
+	regs[MII_PHYSID1] = 0;
+	regs[MII_PHYSID2] = 0;
+
+	regs[MII_BMSR] = bmsr;
+	regs[MII_BMCR] = bmcr;
+	regs[MII_LPA] = lpa;
+	regs[MII_STAT1000] = lpagb;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(swphy_update_regs);
diff --git a/drivers/net/phy/swphy.h b/drivers/net/phy/swphy.h
new file mode 100644
index 000000000000..feaa38ff86a2
--- /dev/null
+++ b/drivers/net/phy/swphy.h
@@ -0,0 +1,8 @@
+#ifndef SWPHY_H
+#define SWPHY_H
+
+struct fixed_phy_status;
+
+int swphy_update_regs(u16 *regs, const struct fixed_phy_status *state);
+
+#endif
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH RFC 02/26] phy: convert swphy register generation to tabular form
  2015-12-07 17:35 [PATCH RFC 00/26] Phylink & SFP support Russell King - ARM Linux
  2015-12-07 17:37 ` [PATCH RFC 01/26] phy: move fixed_phy MII register generation to a library Russell King
@ 2015-12-07 17:37 ` Russell King
  2016-01-07 19:47   ` Florian Fainelli
  2015-12-07 17:37 ` [PATCH RFC 03/26] phy: separate swphy state validation from register generation Russell King
                   ` (25 subsequent siblings)
  27 siblings, 1 reply; 58+ messages in thread
From: Russell King @ 2015-12-07 17:37 UTC (permalink / raw)
  To: Florian Fainelli, Thomas Petazzoni; +Cc: netdev

Convert the swphy register generation to tabular form which allows us
to eliminate multiple switch() statements.  This results in a smaller
object code size, more efficient, and easier to add support for faster
speeds.

Before:

Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00000164  00000000  00000000  00000034  2**2

   text    data     bss     dec     hex filename
    388       0       0     388     184 swphy.o

After:

Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         000000fc  00000000  00000000  00000034  2**2
  5 .rodata       00000028  00000000  00000000  00000138  2**2

   text    data     bss     dec     hex filename
    324       0       0     324     144 swphy.o

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
 drivers/net/phy/swphy.c | 143 ++++++++++++++++++++++++++----------------------
 1 file changed, 78 insertions(+), 65 deletions(-)

diff --git a/drivers/net/phy/swphy.c b/drivers/net/phy/swphy.c
index 0551a79a2454..c88a194b4cb6 100644
--- a/drivers/net/phy/swphy.c
+++ b/drivers/net/phy/swphy.c
@@ -20,6 +20,72 @@
 
 #include "swphy.h"
 
+struct swmii_regs {
+	u16 bmcr;
+	u16 bmsr;
+	u16 lpa;
+	u16 lpagb;
+};
+
+enum {
+	SWMII_SPEED_10 = 0,
+	SWMII_SPEED_100,
+	SWMII_SPEED_1000,
+	SWMII_DUPLEX_HALF = 0,
+	SWMII_DUPLEX_FULL,
+};
+
+/*
+ * These two tables get bitwise-anded together to produce the final result.
+ * This means the speed table must contain both duplex settings, and the
+ * duplex table must contain all speed settings.
+ */
+static const struct swmii_regs speed[] = {
+	[SWMII_SPEED_10] = {
+		.bmcr  = BMCR_FULLDPLX,
+		.lpa   = LPA_10FULL | LPA_10HALF,
+	},
+	[SWMII_SPEED_100] = {
+		.bmcr  = BMCR_FULLDPLX | BMCR_SPEED100,
+		.bmsr  = BMSR_100FULL | BMSR_100HALF,
+		.lpa   = LPA_100FULL | LPA_100HALF,
+	},
+	[SWMII_SPEED_1000] = {
+		.bmcr  = BMCR_FULLDPLX | BMCR_SPEED1000,
+		.bmsr  = BMSR_ESTATEN,
+		.lpagb = LPA_1000FULL | LPA_1000HALF,
+	},
+};
+
+static const struct swmii_regs duplex[] = {
+	[SWMII_DUPLEX_HALF] = {
+		.bmcr  = ~BMCR_FULLDPLX,
+		.bmsr  = BMSR_ESTATEN | BMSR_100HALF,
+		.lpa   = LPA_10HALF | LPA_100HALF,
+		.lpagb = LPA_1000HALF,
+	},
+	[SWMII_DUPLEX_FULL] = {
+		.bmcr  = ~0,
+		.bmsr  = BMSR_ESTATEN | BMSR_100FULL,
+		.lpa   = LPA_10FULL | LPA_100FULL,
+		.lpagb = LPA_1000FULL,
+	},
+};
+
+static int swphy_decode_speed(int speed)
+{
+	switch (speed) {
+	case 1000:
+		return SWMII_SPEED_1000;
+	case 100:
+		return SWMII_SPEED_100;
+	case 10:
+		return SWMII_SPEED_10;
+	default:
+		return -EINVAL;
+	}
+}
+
 /**
  * swphy_update_regs - update MII register array with fixed phy state
  * @regs: array of 32 registers to update
@@ -30,81 +96,28 @@
  */
 int swphy_update_regs(u16 *regs, const struct fixed_phy_status *state)
 {
+	int speed_index, duplex_index;
 	u16 bmsr = BMSR_ANEGCAPABLE;
 	u16 bmcr = 0;
 	u16 lpagb = 0;
 	u16 lpa = 0;
 
-	if (state->duplex) {
-		switch (state->speed) {
-		case 1000:
-			bmsr |= BMSR_ESTATEN;
-			break;
-		case 100:
-			bmsr |= BMSR_100FULL;
-			break;
-		case 10:
-			bmsr |= BMSR_10FULL;
-			break;
-		default:
-			break;
-		}
-	} else {
-		switch (state->speed) {
-		case 1000:
-			bmsr |= BMSR_ESTATEN;
-			break;
-		case 100:
-			bmsr |= BMSR_100HALF;
-			break;
-		case 10:
-			bmsr |= BMSR_10HALF;
-			break;
-		default:
-			break;
-		}
+	speed_index = swphy_decode_speed(state->speed);
+	if (speed_index < 0) {
+		pr_warn("swphy: unknown speed\n");
+		return -EINVAL;
 	}
 
+	duplex_index = state->duplex ? SWMII_DUPLEX_FULL : SWMII_DUPLEX_HALF;
+
+	bmsr |= speed[speed_index].bmsr & duplex[duplex_index].bmsr;
+
 	if (state->link) {
 		bmsr |= BMSR_LSTATUS | BMSR_ANEGCOMPLETE;
 
-		if (state->duplex) {
-			bmcr |= BMCR_FULLDPLX;
-
-			switch (state->speed) {
-			case 1000:
-				bmcr |= BMCR_SPEED1000;
-				lpagb |= LPA_1000FULL;
-				break;
-			case 100:
-				bmcr |= BMCR_SPEED100;
-				lpa |= LPA_100FULL;
-				break;
-			case 10:
-				lpa |= LPA_10FULL;
-				break;
-			default:
-				pr_warn("swphy: unknown speed\n");
-				return -EINVAL;
-			}
-		} else {
-			switch (state->speed) {
-			case 1000:
-				bmcr |= BMCR_SPEED1000;
-				lpagb |= LPA_1000HALF;
-				break;
-			case 100:
-				bmcr |= BMCR_SPEED100;
-				lpa |= LPA_100HALF;
-				break;
-			case 10:
-				lpa |= LPA_10HALF;
-				break;
-			default:
-				pr_warn("swphy: unknown speed\n");
-				return -EINVAL;
-			}
-		}
+		bmcr  |= speed[speed_index].bmcr  & duplex[duplex_index].bmcr;
+		lpa   |= speed[speed_index].lpa   & duplex[duplex_index].lpa;
+		lpagb |= speed[speed_index].lpagb & duplex[duplex_index].lpagb;
 
 		if (state->pause)
 			lpa |= LPA_PAUSE_CAP;
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH RFC 03/26] phy: separate swphy state validation from register generation
  2015-12-07 17:35 [PATCH RFC 00/26] Phylink & SFP support Russell King - ARM Linux
  2015-12-07 17:37 ` [PATCH RFC 01/26] phy: move fixed_phy MII register generation to a library Russell King
  2015-12-07 17:37 ` [PATCH RFC 02/26] phy: convert swphy register generation to tabular form Russell King
@ 2015-12-07 17:37 ` Russell King
  2016-01-07 19:48   ` Florian Fainelli
  2015-12-07 17:37 ` [PATCH RFC 04/26] phy: generate swphy registers on the fly Russell King
                   ` (24 subsequent siblings)
  27 siblings, 1 reply; 58+ messages in thread
From: Russell King @ 2015-12-07 17:37 UTC (permalink / raw)
  To: Florian Fainelli, Thomas Petazzoni; +Cc: netdev

Separate out the generation of MII registers from the state validation.
This allows us to simplify the error handing in fixed_phy() by allowing
earlier error detection.

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
 drivers/net/phy/fixed_phy.c | 15 +++++++--------
 drivers/net/phy/swphy.c     | 33 ++++++++++++++++++++++++++-------
 drivers/net/phy/swphy.h     |  3 ++-
 3 files changed, 35 insertions(+), 16 deletions(-)

diff --git a/drivers/net/phy/fixed_phy.c b/drivers/net/phy/fixed_phy.c
index 9a448e7f8f4e..fe4ca05afc77 100644
--- a/drivers/net/phy/fixed_phy.c
+++ b/drivers/net/phy/fixed_phy.c
@@ -49,12 +49,12 @@ static struct fixed_mdio_bus platform_fmb = {
 	.phys = LIST_HEAD_INIT(platform_fmb.phys),
 };
 
-static int fixed_phy_update_regs(struct fixed_phy *fp)
+static void fixed_phy_update_regs(struct fixed_phy *fp)
 {
 	if (gpio_is_valid(fp->link_gpio))
 		fp->status.link = !!gpio_get_value_cansleep(fp->link_gpio);
 
-	return swphy_update_regs(fp->regs, &fp->status);
+	swphy_update_regs(fp->regs, &fp->status);
 }
 
 static int fixed_mdio_read(struct mii_bus *bus, int phy_addr, int reg_num)
@@ -161,6 +161,10 @@ int fixed_phy_add(unsigned int irq, int phy_addr,
 	struct fixed_mdio_bus *fmb = &platform_fmb;
 	struct fixed_phy *fp;
 
+	ret = swphy_validate_state(status);
+	if (ret < 0)
+		return ret;
+
 	fp = kzalloc(sizeof(*fp), GFP_KERNEL);
 	if (!fp)
 		return -ENOMEM;
@@ -180,17 +184,12 @@ int fixed_phy_add(unsigned int irq, int phy_addr,
 			goto err_regs;
 	}
 
-	ret = fixed_phy_update_regs(fp);
-	if (ret)
-		goto err_gpio;
+	fixed_phy_update_regs(fp);
 
 	list_add_tail(&fp->node, &fmb->phys);
 
 	return 0;
 
-err_gpio:
-	if (gpio_is_valid(fp->link_gpio))
-		gpio_free(fp->link_gpio);
 err_regs:
 	kfree(fp);
 	return ret;
diff --git a/drivers/net/phy/swphy.c b/drivers/net/phy/swphy.c
index c88a194b4cb6..21a9bd8a7830 100644
--- a/drivers/net/phy/swphy.c
+++ b/drivers/net/phy/swphy.c
@@ -87,6 +87,29 @@ static int swphy_decode_speed(int speed)
 }
 
 /**
+ * swphy_validate_state - validate the software phy status
+ * @state: software phy status
+ *
+ * This checks that we can represent the state stored in @state can be
+ * represented in the emulated MII registers.  Returns 0 if it can,
+ * otherwise returns -EINVAL.
+ */
+int swphy_validate_state(const struct fixed_phy_status *state)
+{
+	int err;
+
+	if (state->link) {
+		err = swphy_decode_speed(state->speed);
+		if (err < 0) {
+			pr_warn("swphy: unknown speed\n");
+			return -EINVAL;
+		}
+	}
+	return 0;
+}
+EXPORT_SYMBOL_GPL(swphy_validate_state);
+
+/**
  * swphy_update_regs - update MII register array with fixed phy state
  * @regs: array of 32 registers to update
  * @state: fixed phy status
@@ -94,7 +117,7 @@ static int swphy_decode_speed(int speed)
  * Update the array of MII registers with the fixed phy link, speed,
  * duplex and pause mode settings.
  */
-int swphy_update_regs(u16 *regs, const struct fixed_phy_status *state)
+void swphy_update_regs(u16 *regs, const struct fixed_phy_status *state)
 {
 	int speed_index, duplex_index;
 	u16 bmsr = BMSR_ANEGCAPABLE;
@@ -103,10 +126,8 @@ int swphy_update_regs(u16 *regs, const struct fixed_phy_status *state)
 	u16 lpa = 0;
 
 	speed_index = swphy_decode_speed(state->speed);
-	if (speed_index < 0) {
-		pr_warn("swphy: unknown speed\n");
-		return -EINVAL;
-	}
+	if (WARN_ON(speed_index < 0))
+		return;
 
 	duplex_index = state->duplex ? SWMII_DUPLEX_FULL : SWMII_DUPLEX_HALF;
 
@@ -133,7 +154,5 @@ int swphy_update_regs(u16 *regs, const struct fixed_phy_status *state)
 	regs[MII_BMCR] = bmcr;
 	regs[MII_LPA] = lpa;
 	regs[MII_STAT1000] = lpagb;
-
-	return 0;
 }
 EXPORT_SYMBOL_GPL(swphy_update_regs);
diff --git a/drivers/net/phy/swphy.h b/drivers/net/phy/swphy.h
index feaa38ff86a2..33d2e061896e 100644
--- a/drivers/net/phy/swphy.h
+++ b/drivers/net/phy/swphy.h
@@ -3,6 +3,7 @@
 
 struct fixed_phy_status;
 
-int swphy_update_regs(u16 *regs, const struct fixed_phy_status *state);
+int swphy_validate_state(const struct fixed_phy_status *state);
+void swphy_update_regs(u16 *regs, const struct fixed_phy_status *state);
 
 #endif
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH RFC 04/26] phy: generate swphy registers on the fly
  2015-12-07 17:35 [PATCH RFC 00/26] Phylink & SFP support Russell King - ARM Linux
                   ` (2 preceding siblings ...)
  2015-12-07 17:37 ` [PATCH RFC 03/26] phy: separate swphy state validation from register generation Russell King
@ 2015-12-07 17:37 ` Russell King
  2016-01-07 19:49   ` Florian Fainelli
  2015-12-07 17:37 ` [PATCH RFC 05/26] phy: improve safety of fixed-phy MII register reading Russell King
                   ` (23 subsequent siblings)
  27 siblings, 1 reply; 58+ messages in thread
From: Russell King @ 2015-12-07 17:37 UTC (permalink / raw)
  To: Florian Fainelli, Thomas Petazzoni; +Cc: netdev

Generate software phy registers as and when requested, rather than
duplicating the state in fixed_phy.  This allows us to eliminate
the duplicate storage of of the same data, which is only different
in format.

As fixed_phy_update_regs() no longer updates register state, rename
it to fixed_phy_update().

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
 drivers/net/phy/fixed_phy.c | 31 +++++-------------------------
 drivers/net/phy/swphy.c     | 47 ++++++++++++++++++++++++++++++++-------------
 drivers/net/phy/swphy.h     |  2 +-
 3 files changed, 40 insertions(+), 40 deletions(-)

diff --git a/drivers/net/phy/fixed_phy.c b/drivers/net/phy/fixed_phy.c
index fe4ca05afc77..66555da2ef27 100644
--- a/drivers/net/phy/fixed_phy.c
+++ b/drivers/net/phy/fixed_phy.c
@@ -26,8 +26,6 @@
 
 #include "swphy.h"
 
-#define MII_REGS_NUM 29
-
 struct fixed_mdio_bus {
 	int irqs[PHY_MAX_ADDR];
 	struct mii_bus *mii_bus;
@@ -36,7 +34,6 @@ struct fixed_mdio_bus {
 
 struct fixed_phy {
 	int addr;
-	u16 regs[MII_REGS_NUM];
 	struct phy_device *phydev;
 	struct fixed_phy_status status;
 	int (*link_update)(struct net_device *, struct fixed_phy_status *);
@@ -49,12 +46,10 @@ static struct fixed_mdio_bus platform_fmb = {
 	.phys = LIST_HEAD_INIT(platform_fmb.phys),
 };
 
-static void fixed_phy_update_regs(struct fixed_phy *fp)
+static void fixed_phy_update(struct fixed_phy *fp)
 {
 	if (gpio_is_valid(fp->link_gpio))
 		fp->status.link = !!gpio_get_value_cansleep(fp->link_gpio);
-
-	swphy_update_regs(fp->regs, &fp->status);
 }
 
 static int fixed_mdio_read(struct mii_bus *bus, int phy_addr, int reg_num)
@@ -62,29 +57,15 @@ static int fixed_mdio_read(struct mii_bus *bus, int phy_addr, int reg_num)
 	struct fixed_mdio_bus *fmb = bus->priv;
 	struct fixed_phy *fp;
 
-	if (reg_num >= MII_REGS_NUM)
-		return -1;
-
-	/* We do not support emulating Clause 45 over Clause 22 register reads
-	 * return an error instead of bogus data.
-	 */
-	switch (reg_num) {
-	case MII_MMD_CTRL:
-	case MII_MMD_DATA:
-		return -1;
-	default:
-		break;
-	}
-
 	list_for_each_entry(fp, &fmb->phys, node) {
 		if (fp->addr == phy_addr) {
 			/* Issue callback if user registered it. */
 			if (fp->link_update) {
 				fp->link_update(fp->phydev->attached_dev,
 						&fp->status);
-				fixed_phy_update_regs(fp);
+				fixed_phy_update(fp);
 			}
-			return fp->regs[reg_num];
+			return swphy_read_reg(reg_num, &fp->status);
 		}
 	}
 
@@ -144,7 +125,7 @@ int fixed_phy_update_state(struct phy_device *phydev,
 			_UPD(pause);
 			_UPD(asym_pause);
 #undef _UPD
-			fixed_phy_update_regs(fp);
+			fixed_phy_update(fp);
 			return 0;
 		}
 	}
@@ -169,8 +150,6 @@ int fixed_phy_add(unsigned int irq, int phy_addr,
 	if (!fp)
 		return -ENOMEM;
 
-	memset(fp->regs, 0xFF,  sizeof(fp->regs[0]) * MII_REGS_NUM);
-
 	fmb->irqs[phy_addr] = irq;
 
 	fp->addr = phy_addr;
@@ -184,7 +163,7 @@ int fixed_phy_add(unsigned int irq, int phy_addr,
 			goto err_regs;
 	}
 
-	fixed_phy_update_regs(fp);
+	fixed_phy_update(fp);
 
 	list_add_tail(&fp->node, &fmb->phys);
 
diff --git a/drivers/net/phy/swphy.c b/drivers/net/phy/swphy.c
index 21a9bd8a7830..34f58f2349e9 100644
--- a/drivers/net/phy/swphy.c
+++ b/drivers/net/phy/swphy.c
@@ -20,6 +20,8 @@
 
 #include "swphy.h"
 
+#define MII_REGS_NUM 29
+
 struct swmii_regs {
 	u16 bmcr;
 	u16 bmsr;
@@ -110,14 +112,13 @@ int swphy_validate_state(const struct fixed_phy_status *state)
 EXPORT_SYMBOL_GPL(swphy_validate_state);
 
 /**
- * swphy_update_regs - update MII register array with fixed phy state
- * @regs: array of 32 registers to update
+ * swphy_read_reg - return a MII register from the fixed phy state
+ * @reg: MII register
  * @state: fixed phy status
  *
- * Update the array of MII registers with the fixed phy link, speed,
- * duplex and pause mode settings.
+ * Return the MII @reg register generated from the fixed phy state @state.
  */
-void swphy_update_regs(u16 *regs, const struct fixed_phy_status *state)
+int swphy_read_reg(int reg, const struct fixed_phy_status *state)
 {
 	int speed_index, duplex_index;
 	u16 bmsr = BMSR_ANEGCAPABLE;
@@ -125,9 +126,12 @@ void swphy_update_regs(u16 *regs, const struct fixed_phy_status *state)
 	u16 lpagb = 0;
 	u16 lpa = 0;
 
+	if (reg > MII_REGS_NUM)
+		return -1;
+
 	speed_index = swphy_decode_speed(state->speed);
 	if (WARN_ON(speed_index < 0))
-		return;
+		return 0;
 
 	duplex_index = state->duplex ? SWMII_DUPLEX_FULL : SWMII_DUPLEX_HALF;
 
@@ -147,12 +151,29 @@ void swphy_update_regs(u16 *regs, const struct fixed_phy_status *state)
 			lpa |= LPA_PAUSE_ASYM;
 	}
 
-	regs[MII_PHYSID1] = 0;
-	regs[MII_PHYSID2] = 0;
+	switch (reg) {
+	case MII_BMCR:
+		return bmcr;
+	case MII_BMSR:
+		return bmsr;
+	case MII_PHYSID1:
+	case MII_PHYSID2:
+		return 0;
+	case MII_LPA:
+		return lpa;
+	case MII_STAT1000:
+		return lpagb;
+
+	/*
+	 * We do not support emulating Clause 45 over Clause 22 register
+	 * reads.  Return an error instead of bogus data.
+	 */
+	case MII_MMD_CTRL:
+	case MII_MMD_DATA:
+		return -1;
 
-	regs[MII_BMSR] = bmsr;
-	regs[MII_BMCR] = bmcr;
-	regs[MII_LPA] = lpa;
-	regs[MII_STAT1000] = lpagb;
+	default:
+		return 0xffff;
+	}
 }
-EXPORT_SYMBOL_GPL(swphy_update_regs);
+EXPORT_SYMBOL_GPL(swphy_read_reg);
diff --git a/drivers/net/phy/swphy.h b/drivers/net/phy/swphy.h
index 33d2e061896e..2f09ac324e18 100644
--- a/drivers/net/phy/swphy.h
+++ b/drivers/net/phy/swphy.h
@@ -4,6 +4,6 @@
 struct fixed_phy_status;
 
 int swphy_validate_state(const struct fixed_phy_status *state);
-void swphy_update_regs(u16 *regs, const struct fixed_phy_status *state);
+int swphy_read_reg(int reg, const struct fixed_phy_status *state);
 
 #endif
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH RFC 05/26] phy: improve safety of fixed-phy MII register reading
  2015-12-07 17:35 [PATCH RFC 00/26] Phylink & SFP support Russell King - ARM Linux
                   ` (3 preceding siblings ...)
  2015-12-07 17:37 ` [PATCH RFC 04/26] phy: generate swphy registers on the fly Russell King
@ 2015-12-07 17:37 ` Russell King
  2016-01-07 19:50   ` Florian Fainelli
  2015-12-07 17:37 ` [PATCH RFC 06/26] phy: provide a hook for link up/link down events Russell King
                   ` (22 subsequent siblings)
  27 siblings, 1 reply; 58+ messages in thread
From: Russell King @ 2015-12-07 17:37 UTC (permalink / raw)
  To: Florian Fainelli, Thomas Petazzoni; +Cc: netdev

There is no prevention of a concurrent call to both fixed_mdio_read()
and fixed_phy_update_state(), which can result in the state being
modified while it's being inspected.  Fix this by using a seqcount
to detect modifications, and memcpy()ing the state.

We remain slightly naughty here, calling link_update() and updating
the link status within the read-side loop - which would need rework
of the design to change.

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
 drivers/net/phy/fixed_phy.c | 28 +++++++++++++++++++++-------
 1 file changed, 21 insertions(+), 7 deletions(-)

diff --git a/drivers/net/phy/fixed_phy.c b/drivers/net/phy/fixed_phy.c
index 66555da2ef27..474cc39a5457 100644
--- a/drivers/net/phy/fixed_phy.c
+++ b/drivers/net/phy/fixed_phy.c
@@ -23,6 +23,7 @@
 #include <linux/slab.h>
 #include <linux/of.h>
 #include <linux/gpio.h>
+#include <linux/seqlock.h>
 
 #include "swphy.h"
 
@@ -35,6 +36,7 @@ struct fixed_mdio_bus {
 struct fixed_phy {
 	int addr;
 	struct phy_device *phydev;
+	seqcount_t seqcount;
 	struct fixed_phy_status status;
 	int (*link_update)(struct net_device *, struct fixed_phy_status *);
 	struct list_head node;
@@ -59,13 +61,21 @@ static int fixed_mdio_read(struct mii_bus *bus, int phy_addr, int reg_num)
 
 	list_for_each_entry(fp, &fmb->phys, node) {
 		if (fp->addr == phy_addr) {
-			/* Issue callback if user registered it. */
-			if (fp->link_update) {
-				fp->link_update(fp->phydev->attached_dev,
-						&fp->status);
-				fixed_phy_update(fp);
-			}
-			return swphy_read_reg(reg_num, &fp->status);
+			struct fixed_phy_status state;
+			int s;
+
+			do {
+				s = read_seqcount_begin(&fp->seqcount);
+				/* Issue callback if user registered it. */
+				if (fp->link_update) {
+					fp->link_update(fp->phydev->attached_dev,
+							&fp->status);
+					fixed_phy_update(fp);
+				}
+				state = fp->status;
+			} while (read_seqcount_retry(&fp->seqcount, s));
+
+			return swphy_read_reg(reg_num, &state);
 		}
 	}
 
@@ -117,6 +127,7 @@ int fixed_phy_update_state(struct phy_device *phydev,
 
 	list_for_each_entry(fp, &fmb->phys, node) {
 		if (fp->addr == phydev->addr) {
+			write_seqcount_begin(&fp->seqcount);
 #define _UPD(x) if (changed->x) \
 	fp->status.x = status->x
 			_UPD(link);
@@ -126,6 +137,7 @@ int fixed_phy_update_state(struct phy_device *phydev,
 			_UPD(asym_pause);
 #undef _UPD
 			fixed_phy_update(fp);
+			write_seqcount_end(&fp->seqcount);
 			return 0;
 		}
 	}
@@ -150,6 +162,8 @@ int fixed_phy_add(unsigned int irq, int phy_addr,
 	if (!fp)
 		return -ENOMEM;
 
+	seqcount_init(&fp->seqcount);
+
 	fmb->irqs[phy_addr] = irq;
 
 	fp->addr = phy_addr;
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH RFC 06/26] phy: provide a hook for link up/link down events
  2015-12-07 17:35 [PATCH RFC 00/26] Phylink & SFP support Russell King - ARM Linux
                   ` (4 preceding siblings ...)
  2015-12-07 17:37 ` [PATCH RFC 05/26] phy: improve safety of fixed-phy MII register reading Russell King
@ 2015-12-07 17:37 ` Russell King
  2016-01-07 19:53   ` Florian Fainelli
  2015-12-07 17:37 ` [PATCH RFC 07/26] phy: marvell: 88E1512: add flow control support Russell King
                   ` (21 subsequent siblings)
  27 siblings, 1 reply; 58+ messages in thread
From: Russell King @ 2015-12-07 17:37 UTC (permalink / raw)
  To: Florian Fainelli, Thomas Petazzoni; +Cc: netdev

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.

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
 drivers/net/phy/phy.c        | 42 ++++++++++++++++++++++--------------------
 drivers/net/phy/phy_device.c | 14 ++++++++++++++
 include/linux/phy.h          |  1 +
 3 files changed, 37 insertions(+), 20 deletions(-)

diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c
index adb48abafc87..150497246922 100644
--- a/drivers/net/phy/phy.c
+++ b/drivers/net/phy/phy.c
@@ -802,6 +802,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
@@ -843,8 +853,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;
 		}
 
@@ -856,9 +865,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;
@@ -880,8 +887,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:
@@ -891,13 +897,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 or ignoring
@@ -920,14 +925,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);
@@ -935,8 +938,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;
@@ -956,11 +958,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;
@@ -972,11 +974,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 0bfbabad4431..67a654a1179b 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -441,6 +441,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
@@ -659,6 +672,7 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev,
 		goto error;
 	}
 
+	phydev->phy_link_change = phy_link_change;
 	phydev->attached_dev = dev;
 	dev->phydev = phydev;
 
diff --git a/include/linux/phy.h b/include/linux/phy.h
index 05fde31b6dc6..63e52af00493 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -433,6 +433,7 @@ struct phy_device {
 
 	u8 mdix;
 
+	void (*phy_link_change)(struct phy_device *, bool up, bool do_carrier);
 	void (*adjust_link)(struct net_device *dev);
 };
 #define to_phy_device(d) container_of(d, struct phy_device, dev)
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH RFC 07/26] phy: marvell: 88E1512: add flow control support
  2015-12-07 17:35 [PATCH RFC 00/26] Phylink & SFP support Russell King - ARM Linux
                   ` (5 preceding siblings ...)
  2015-12-07 17:37 ` [PATCH RFC 06/26] phy: provide a hook for link up/link down events Russell King
@ 2015-12-07 17:37 ` Russell King
  2016-01-07 19:53   ` Florian Fainelli
  2015-12-07 17:37 ` [PATCH RFC 08/26] phy: export phy_start_machine() for phylink Russell King
                   ` (20 subsequent siblings)
  27 siblings, 1 reply; 58+ messages in thread
From: Russell King @ 2015-12-07 17:37 UTC (permalink / raw)
  To: Florian Fainelli, Thomas Petazzoni; +Cc: netdev

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 <rmk+kernel@arm.linux.org.uk>
---
 drivers/net/phy/marvell.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/net/phy/marvell.c b/drivers/net/phy/marvell.c
index 5de8d5827536..9a5329bfd0fd 100644
--- a/drivers/net/phy/marvell.c
+++ b/drivers/net/phy/marvell.c
@@ -1142,7 +1142,7 @@ 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,
+		.features = PHY_GBIT_FEATURES | SUPPORTED_Pause,
 		.flags = PHY_HAS_INTERRUPT,
 		.config_aneg = &m88e1510_config_aneg,
 		.read_status = &marvell_read_status,
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH RFC 08/26] phy: export phy_start_machine() for phylink
  2015-12-07 17:35 [PATCH RFC 00/26] Phylink & SFP support Russell King - ARM Linux
                   ` (6 preceding siblings ...)
  2015-12-07 17:37 ` [PATCH RFC 07/26] phy: marvell: 88E1512: add flow control support Russell King
@ 2015-12-07 17:37 ` Russell King
  2016-01-07 19:53   ` Florian Fainelli
  2015-12-07 17:37 ` [PATCH RFC 09/26] phy: export phy_speed_to_str() " Russell King
                   ` (19 subsequent siblings)
  27 siblings, 1 reply; 58+ messages in thread
From: Russell King @ 2015-12-07 17:37 UTC (permalink / raw)
  To: Florian Fainelli, Thomas Petazzoni; +Cc: netdev

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.

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
 drivers/net/phy/phy.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c
index 150497246922..ec9953202f58 100644
--- a/drivers/net/phy/phy.c
+++ b/drivers/net/phy/phy.c
@@ -523,6 +523,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
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH RFC 09/26] phy: export phy_speed_to_str() for phylink
  2015-12-07 17:35 [PATCH RFC 00/26] Phylink & SFP support Russell King - ARM Linux
                   ` (7 preceding siblings ...)
  2015-12-07 17:37 ` [PATCH RFC 08/26] phy: export phy_start_machine() for phylink Russell King
@ 2015-12-07 17:37 ` Russell King
  2016-01-07 19:54   ` Florian Fainelli
  2015-12-07 17:38 ` [PATCH RFC 10/26] phy: add I2C mdio bus Russell King
                   ` (18 subsequent siblings)
  27 siblings, 1 reply; 58+ messages in thread
From: Russell King @ 2015-12-07 17:37 UTC (permalink / raw)
  To: Florian Fainelli, Thomas Petazzoni; +Cc: netdev

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

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
 drivers/net/phy/phy.c | 3 ++-
 include/linux/phy.h   | 1 +
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c
index ec9953202f58..c1be21a84f1d 100644
--- a/drivers/net/phy/phy.c
+++ b/drivers/net/phy/phy.c
@@ -38,7 +38,7 @@
 
 #include <asm/irq.h>
 
-static const char *phy_speed_to_str(int speed)
+const char *phy_speed_to_str(int speed)
 {
 	switch (speed) {
 	case SPEED_10:
@@ -57,6 +57,7 @@ static const char *phy_speed_to_str(int speed)
 		return "Unsupported (update phy.c)";
 	}
 }
+EXPORT_SYMBOL_GPL(phy_speed_to_str);
 
 #define PHY_STATE_STR(_state)			\
 	case PHY_##_state:			\
diff --git a/include/linux/phy.h b/include/linux/phy.h
index 63e52af00493..6b1ec2b99051 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -799,6 +799,7 @@ int phy_ethtool_sset(struct phy_device *phydev, struct ethtool_cmd *cmd);
 int phy_ethtool_gset(struct phy_device *phydev, struct ethtool_cmd *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);
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH RFC 10/26] phy: add I2C mdio bus
  2015-12-07 17:35 [PATCH RFC 00/26] Phylink & SFP support Russell King - ARM Linux
                   ` (8 preceding siblings ...)
  2015-12-07 17:37 ` [PATCH RFC 09/26] phy: export phy_speed_to_str() " Russell King
@ 2015-12-07 17:38 ` Russell King
  2015-12-08 18:15   ` Florian Fainelli
  2015-12-07 17:38 ` [PATCH RFC 11/26] phylink: add phylink infrastructure Russell King
                   ` (17 subsequent siblings)
  27 siblings, 1 reply; 58+ messages in thread
From: Russell King @ 2015-12-07 17:38 UTC (permalink / raw)
  To: Florian Fainelli, Thomas Petazzoni; +Cc: netdev

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.

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
 drivers/net/phy/Kconfig    | 10 ++++++
 drivers/net/phy/Makefile   |  1 +
 drivers/net/phy/mdio-i2c.c | 90 ++++++++++++++++++++++++++++++++++++++++++++++
 drivers/net/phy/mdio-i2c.h | 19 ++++++++++
 4 files changed, 120 insertions(+)
 create mode 100644 drivers/net/phy/mdio-i2c.c
 create mode 100644 drivers/net/phy/mdio-i2c.h

diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index c59f957fc282..c475531a542e 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -187,6 +187,16 @@ config MDIO_GPIO
 	  To compile this driver as a module, choose M here: the module
 	  will be called mdio-gpio.
 
+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 ThunderX SOCs"
 	depends on 64BIT
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index 31bdf193adbd..d5c3ff625fbe 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -25,6 +25,7 @@ obj-$(CONFIG_LSI_ET1011C_PHY)	+= et1011c.o
 obj-$(CONFIG_FIXED_PHY)		+= fixed_phy.o
 obj-$(CONFIG_MDIO_BITBANG)	+= mdio-bitbang.o
 obj-$(CONFIG_MDIO_GPIO)		+= mdio-gpio.o
+obj-$(CONFIG_MDIO_I2C)		+= mdio-i2c.o
 obj-$(CONFIG_NATIONAL_PHY)	+= national.o
 obj-$(CONFIG_DP83640_PHY)	+= dp83640.o
 obj-$(CONFIG_DP83848_PHY)	+= dp83848.o
diff --git a/drivers/net/phy/mdio-i2c.c b/drivers/net/phy/mdio-i2c.c
new file mode 100644
index 000000000000..57b5de8c5a3e
--- /dev/null
+++ b/drivers/net/phy/mdio-i2c.c
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ */
+#include <linux/i2c.h>
+#include <linux/phy.h>
+
+#include "mdio-i2c.h"
+
+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;
+
+	bus_addr = 0x40 + phy_id;
+	if (bus_addr == 0x50 || bus_addr == 0x51)
+		return 0xffff;
+
+	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 bus_addr, ret;
+	u8 data[3];
+
+	bus_addr = 0x40 + phy_id;
+	if (bus_addr == 0x50 || bus_addr == 0x51)
+		return 0;
+
+	data[0] = reg;
+	data[1] = val >> 8;
+	data[2] = val;
+
+	msg.addr = bus_addr;
+	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
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH RFC 11/26] phylink: add phylink infrastructure
  2015-12-07 17:35 [PATCH RFC 00/26] Phylink & SFP support Russell King - ARM Linux
                   ` (9 preceding siblings ...)
  2015-12-07 17:38 ` [PATCH RFC 10/26] phy: add I2C mdio bus Russell King
@ 2015-12-07 17:38 ` Russell King
  2016-01-07 20:09   ` Florian Fainelli
  2015-12-07 17:38 ` [PATCH RFC 12/26] phylink: add hooks for SFP support Russell King
                   ` (16 subsequent siblings)
  27 siblings, 1 reply; 58+ messages in thread
From: Russell King @ 2015-12-07 17:38 UTC (permalink / raw)
  To: Florian Fainelli, Thomas Petazzoni; +Cc: netdev

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 <rmk+kernel@arm.linux.org.uk>
---
 drivers/net/phy/Kconfig      |  10 +
 drivers/net/phy/Makefile     |   1 +
 drivers/net/phy/phy_device.c |   1 +
 drivers/net/phy/phylink.c    | 787 +++++++++++++++++++++++++++++++++++++++++++
 include/linux/phy.h          |   2 +
 include/linux/phylink.h      |  70 ++++
 6 files changed, 871 insertions(+)
 create mode 100644 drivers/net/phy/phylink.c
 create mode 100644 include/linux/phylink.h

diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index c475531a542e..5c634b4bc9bd 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -10,6 +10,16 @@ menuconfig PHYLIB
 	  devices.  This option provides infrastructure for
 	  managing PHY devices.
 
+config PHYLINK
+	tristate
+	depends on NETDEVICES
+	select PHYLIB
+	select SWPHY
+	help
+	  PHYlink models the link between the PHY and MAC, allowing fixed
+	  configuration links, PHYs, and Serdes links with MAC level
+	  autonegotiation modes.
+
 if PHYLIB
 
 config SWPHY
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index d5c3ff625fbe..bc052bb6cee0 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
 libphy-$(CONFIG_SWPHY)		+= swphy.o
 
+obj-$(CONFIG_PHYLINK)		+= phylink.o
 obj-$(CONFIG_PHYLIB)		+= libphy.o
 obj-$(CONFIG_AQUANTIA_PHY)	+= aquantia.o
 obj-$(CONFIG_MARVELL_PHY)	+= marvell.o
diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
index 67a654a1179b..34f2ac29dbed 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -751,6 +751,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..d385eb7c4147
--- /dev/null
+++ b/drivers/net/phy/phylink.c
@@ -0,0 +1,787 @@
+/*
+ * phylink models the MAC to optional PHY connection, supporting
+ * technologies such as SFP cages where the PHY is hot-pluggable.
+ *
+ * Copyright (C) 2015 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/ethtool.h>
+#include <linux/export.h>
+#include <linux/gpio/consumer.h>
+#include <linux/netdevice.h>
+#include <linux/of.h>
+#include <linux/of_mdio.h>
+#include <linux/phy.h>
+#include <linux/phy_fixed.h>
+#include <linux/phylink.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+
+#include "swphy.h"
+
+#define SUPPORTED_INTERFACES \
+	(SUPPORTED_TP | SUPPORTED_MII | SUPPORTED_FIBRE | \
+	 SUPPORTED_BNC | SUPPORTED_AUI | SUPPORTED_Backplane)
+#define ADVERTISED_INTERFACES \
+	(ADVERTISED_TP | ADVERTISED_MII | ADVERTISED_FIBRE | \
+	 ADVERTISED_BNC | ADVERTISED_AUI | ADVERTISED_Backplane)
+
+enum {
+	PHYLINK_DISABLE_STOPPED,
+};
+
+struct phylink {
+	struct net_device *netdev;
+	const struct phylink_mac_ops *ops;
+	struct mutex config_mutex;
+
+	unsigned long phylink_disable_state; /* bitmask of disables */
+	struct phy_device *phydev;
+	phy_interface_t link_interface;	/* PHY_INTERFACE_xxx */
+	u8 link_an_mode;		/* MLO_AN_xxx */
+	u8 link_port;			/* The current non-phy ethtool port */
+	u32 link_port_support;		/* SUPPORTED_xxx ethtool for ports */
+
+	/* The link configuration settings */
+	struct phylink_link_state link_config;
+	struct gpio_desc *link_gpio;
+
+	struct mutex state_mutex;	/* may be taken within config_mutex */
+	struct phylink_link_state phy_state;
+	struct work_struct resolve;
+
+	bool mac_link_up;
+};
+
+static const char *phylink_an_mode_str(unsigned int mode)
+{
+	static const char *modestr[] = {
+		[MLO_AN_PHY] = "phy",
+		[MLO_AN_FIXED] = "fixed",
+		[MLO_AN_SGMII] = "sgmii",
+		[MLO_AN_8023Z] = "802.3z",
+	};
+
+	return mode < ARRAY_SIZE(modestr) ? modestr[mode] : "unknown";
+}
+
+static int phylink_parse_fixedlink(struct phylink *pl, struct device_node *np)
+{
+	struct device_node *fixed_node;
+	int ret, len;
+
+	fixed_node = of_get_child_by_name(np, "fixed-link");
+	if (fixed_node) {
+		struct gpio_desc *desc;
+		u32 speed;
+
+		ret = of_property_read_u32(fixed_node, "speed", &speed);
+
+		pl->link_an_mode = MLO_AN_FIXED;
+		pl->link_config.link = 1;
+		pl->link_config.an_complete = 1;
+		pl->link_config.speed = speed;
+		pl->link_config.duplex = DUPLEX_HALF;
+		pl->link_config.pause = MLO_PAUSE_NONE;
+
+		if (of_property_read_bool(fixed_node, "full-duplex"))
+			pl->link_config.duplex = DUPLEX_FULL;
+		if (of_property_read_bool(fixed_node, "pause"))
+			pl->link_config.pause |= MLO_PAUSE_SYM;
+		if (of_property_read_bool(fixed_node, "asym-pause"))
+			pl->link_config.pause |= MLO_PAUSE_ASYM;
+
+		if (ret == 0) {
+			desc = fwnode_get_named_gpiod(&fixed_node->fwnode,
+						      "link-gpios");
+
+			if (!IS_ERR(desc))
+				pl->link_gpio = desc;
+			else if (desc == ERR_PTR(-EPROBE_DEFER))
+				ret = -EPROBE_DEFER;
+		}
+		of_node_put(fixed_node);
+	} else {
+		const __be32 *fixed_prop;
+
+		fixed_prop = of_get_property(np, "fixed-link", &len);
+		if (fixed_prop && len == 5 * sizeof(*fixed_prop)) {
+			pl->link_config.duplex = be32_to_cpu(fixed_prop[1]) ?
+						DUPLEX_FULL : DUPLEX_HALF;
+			pl->link_config.speed = be32_to_cpu(fixed_prop[2]);
+			pl->link_config.pause = MLO_PAUSE_NONE;
+			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) {
+		/* Generate the supported/advertising masks */
+		if (pl->link_config.pause & MLO_PAUSE_SYM) {
+			pl->link_config.supported |= SUPPORTED_Pause;
+			pl->link_config.advertising |= ADVERTISED_Pause;
+		}
+		if (pl->link_config.pause & MLO_PAUSE_ASYM) {
+			pl->link_config.supported |= SUPPORTED_Asym_Pause;
+			pl->link_config.advertising |= ADVERTISED_Asym_Pause;
+		}
+
+		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) {
+		if (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_mac_state(struct phylink *pl, struct phylink_link_state *state)
+{
+	struct net_device *ndev = pl->netdev;
+
+	state->supported = pl->link_config.supported;
+	state->advertising = pl->link_config.advertising;
+	state->an_enabled = pl->link_config.an_enabled;
+	state->link = 1;
+	state->sync = 1;
+
+	return pl->ops->mac_link_state(ndev, state);
+}
+
+/* The fixed state is... fixed except for the link state,
+ * which may be determined by a GPIO.
+ */
+static void phylink_get_fixed_state(struct phylink *pl, struct phylink_link_state *state)
+{
+	*state = pl->link_config;
+	if (pl->link_gpio)
+		state->link = !!gpiod_get_value(pl->link_gpio);
+}
+
+extern const char *phy_speed_to_str(int speed);
+
+static void phylink_resolve(struct work_struct *w)
+{
+	struct phylink *pl = container_of(w, struct phylink, resolve);
+	struct phylink_link_state link_state;
+	struct net_device *ndev = pl->netdev;
+
+	mutex_lock(&pl->state_mutex);
+	if (pl->phylink_disable_state) {
+		link_state.link = false;
+	} else {
+		switch (pl->link_an_mode) {
+		case MLO_AN_PHY:
+			link_state = pl->phy_state;
+			break;
+
+		case MLO_AN_FIXED:
+			phylink_get_fixed_state(pl, &link_state);
+			break;
+
+		case MLO_AN_SGMII:
+			/* This should be the logical and of phy up and mac up */
+		case MLO_AN_8023Z:
+			phylink_get_mac_state(pl, &link_state);
+			if (pl->phydev)
+				link_state.link = link_state.link &&
+						  pl->phy_state.link;
+			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're using PHY autonegotiation, we need to keep
+			 * the MAC updated with the current link parameters.
+			 */
+			if (pl->link_an_mode == MLO_AN_PHY)
+				pl->ops->mac_config(ndev, MLO_AN_PHY, &link_state);
+
+			pl->ops->mac_link_up(ndev, pl->link_an_mode);
+
+			netif_carrier_on(ndev);
+
+			netdev_info(ndev,
+				    "Link is Up - %s/%s - flow control %s\n",
+				    phy_speed_to_str(link_state.speed),
+				    link_state.duplex ? "Full" : "Half",
+				    link_state.pause ? "rx/tx" : "off");
+		}
+	}
+	mutex_unlock(&pl->state_mutex);
+}
+
+static void phylink_run_resolve(struct phylink *pl)
+{
+	if (!pl->phylink_disable_state)
+		queue_work(system_power_efficient_wq, &pl->resolve);
+}
+
+struct phylink *phylink_create(struct net_device *ndev, struct device_node *np,
+	phy_interface_t iface, const struct phylink_mac_ops *ops)
+{
+	struct phylink *pl;
+	int ret;
+
+	pl = kzalloc(sizeof(*pl), GFP_KERNEL);
+	if (!pl)
+		return ERR_PTR(-ENOMEM);
+
+	mutex_init(&pl->state_mutex);
+	mutex_init(&pl->config_mutex);
+	INIT_WORK(&pl->resolve, phylink_resolve);
+	pl->netdev = ndev;
+	pl->link_interface = iface;
+	pl->link_port_support = SUPPORTED_MII;
+	pl->link_port = PORT_MII;
+	pl->ops = ops;
+	__set_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state);
+
+	ret = phylink_parse_fixedlink(pl, np);
+	if (ret < 0) {
+		kfree(pl);
+		return ERR_PTR(ret);
+	}
+
+	ret = phylink_parse_managed(pl, np);
+	if (ret < 0) {
+		kfree(pl);
+		return ERR_PTR(ret);
+	}
+
+	ret = pl->ops->mac_get_support(pl->netdev, pl->link_an_mode,
+				       &pl->link_config);
+	if (ret) {
+		kfree(pl);
+		return ERR_PTR(ret);
+	}
+
+	return pl;
+}
+EXPORT_SYMBOL_GPL(phylink_create);
+
+void phylink_destroy(struct phylink *pl)
+{
+	cancel_work_sync(&pl->resolve);
+	kfree(pl);
+}
+EXPORT_SYMBOL_GPL(phylink_destroy);
+
+void phylink_phy_change(struct phy_device *phy, bool up, bool do_carrier)
+{
+	struct phylink *pl = phy->phylink;
+
+	mutex_lock(&pl->state_mutex);
+	pl->phy_state.speed = phy->speed;
+	pl->phy_state.duplex = phy->duplex;
+	pl->phy_state.pause = MLO_PAUSE_NONE;
+	if (phy->pause)
+		pl->phy_state.pause |= MLO_PAUSE_SYM;
+	if (phy->asym_pause)
+		pl->phy_state.pause |= MLO_PAUSE_ASYM;
+	pl->phy_state.link = up;
+	mutex_unlock(&pl->state_mutex);
+
+	phylink_run_resolve(pl);
+
+	netdev_dbg(pl->netdev, "phy link %s\n", up ? "up" : "down");
+}
+
+static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy)
+{
+	mutex_lock(&pl->config_mutex);
+	phy->phylink = pl;
+	phy->phy_link_change = phylink_phy_change;
+
+	netdev_info(pl->netdev,
+		    "PHY [%s] driver [%s]\n", dev_name(&phy->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 link AN mode %s\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.
+	 */
+	pl->ops->mac_config(pl->netdev, pl->link_an_mode, &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;
+
+	pl->ops->mac_config(pl->netdev, pl->link_an_mode, &pl->link_config);
+	pl->ops->mac_an_restart(pl->netdev, pl->link_an_mode);
+	mutex_unlock(&pl->state_mutex);
+
+	return ret;
+}
+
+int phylink_ethtool_set_settings(struct phylink *pl, struct ethtool_cmd *cmd)
+{
+	int ret;
+
+	if (cmd->autoneg != AUTONEG_DISABLE && cmd->autoneg != AUTONEG_ENABLE)
+		return -EINVAL;
+
+	mutex_lock(&pl->config_mutex);
+	ret = phylink_ethtool_sset(pl, cmd);
+	mutex_unlock(&pl->config_mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(phylink_ethtool_set_settings);
+
+/* This emulates MII registers for a fixed-mode phy operating as per the
+ * passed in state. "aneg" defines if we report negotiation is possible.
+ *
+ * FIXME: should deal with negotiation state too.
+ */
+static int phylink_mii_emul_read(struct net_device *ndev, unsigned int reg,
+				 struct phylink_link_state *state, bool aneg)
+{
+	struct fixed_phy_status fs;
+	int val;
+
+	fs.link = state->link;
+	fs.speed = state->speed;
+	fs.duplex = state->duplex;
+	fs.pause = state->pause & MLO_PAUSE_SYM;
+	fs.asym_pause = state->pause & MLO_PAUSE_ASYM;
+
+	val = swphy_read_reg(reg, &fs);
+	if (reg == MII_BMSR) {
+		if (!state->an_complete)
+			val &= ~BMSR_ANEGCOMPLETE;
+		if (!aneg)
+			val &= ~BMSR_ANEGCAPABLE;
+	}
+	return val;
+}
+
+static int phylink_mii_read(struct phylink *pl, unsigned int phy_id,
+			    unsigned int reg)
+{
+	struct phylink_link_state state;
+	int val = 0xffff;
+
+	if (pl->phydev && pl->phydev->addr != phy_id)
+		return mdiobus_read(pl->phydev->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->bus, phy_id, reg);
+		break;
+
+	case MLO_AN_SGMII:
+		if (pl->phydev) {
+			val = mdiobus_read(pl->phydev->bus, pl->phydev->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->addr != phy_id) {
+		mdiobus_write(pl->phydev->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->bus, pl->phydev->addr, reg, val);
+		break;
+
+	case MLO_AN_SGMII:
+		if (pl->phydev) {
+			mdiobus_write(pl->phydev->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->addr : 0;
+		/* fallthrough */
+
+	case SIOCGMIIREG:
+		val = phylink_mii_read(pl, mii_data->phy_id, mii_data->reg_num);
+		if (val < 0) {
+			ret = val;
+		} else {
+			mii_data->val_out = val;
+			ret = 0;
+		}
+		break;
+
+	case SIOCSMIIREG:
+		phylink_mii_write(pl, mii_data->phy_id, mii_data->reg_num,
+				  mii_data->val_in);
+		ret = 0;
+		break;
+
+	default:
+		ret = -EOPNOTSUPP;
+		if (pl->phydev)
+			ret = phy_mii_ioctl(pl->phydev, ifr, cmd);
+		break;
+	}
+
+	mutex_unlock(&pl->config_mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(phylink_mii_ioctl);
+
+MODULE_LICENSE("GPL");
diff --git a/include/linux/phy.h b/include/linux/phy.h
index 6b1ec2b99051..1b288782c340 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -147,6 +147,7 @@ static inline const char *phy_modes(phy_interface_t interface)
 #define MII_ADDR_C45 (1<<30)
 
 struct device;
+struct phylink;
 struct sk_buff;
 
 /*
@@ -429,6 +430,7 @@ struct phy_device {
 
 	struct mutex lock;
 
+	struct phylink *phylink;
 	struct net_device *attached_dev;
 
 	u8 mdix;
diff --git a/include/linux/phylink.h b/include/linux/phylink.h
new file mode 100644
index 000000000000..05953c8abc70
--- /dev/null
+++ b/include/linux/phylink.h
@@ -0,0 +1,70 @@
+#ifndef NETDEV_PCS_H
+#define NETDEV_PCS_H
+
+#include <linux/phy.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+
+struct device_node;
+struct ethtool_cmd;
+struct net_device;
+
+enum {
+	MLO_PAUSE_NONE,
+	MLO_PAUSE_ASYM = BIT(0),
+	MLO_PAUSE_SYM = BIT(1),
+
+	MLO_AN_PHY = 0,
+	MLO_AN_FIXED,
+	MLO_AN_SGMII,
+	MLO_AN_8023Z,
+};
+
+struct phylink_link_state {
+	u32 supported;
+	u32 advertising;
+	u32 lp_advertising;
+	int speed;
+	int duplex;
+	int pause;
+	unsigned int link:1;
+	unsigned int sync:1;
+	unsigned int an_enabled:1;
+	unsigned int an_complete:1;
+};
+
+struct phylink_mac_ops {
+	/* Get the ethtool supported mask for the indicated mode */
+	int (*mac_get_support)(struct net_device *, unsigned int mode,
+			       struct phylink_link_state *);
+
+	/* Read the current link state from the hardware */
+	int (*mac_link_state)(struct net_device *, struct phylink_link_state *);
+
+	/* Configure the MAC */
+	void (*mac_config)(struct net_device *, unsigned int mode,
+			   const struct phylink_link_state *);
+	void (*mac_an_restart)(struct net_device *, unsigned int mode);
+
+	void (*mac_link_down)(struct net_device *, unsigned int mode);
+	void (*mac_link_up)(struct net_device *, unsigned int mode);
+};
+
+struct phylink *phylink_create(struct net_device *, struct device_node *,
+	phy_interface_t iface, const struct phylink_mac_ops *ops);
+void phylink_destroy(struct phylink *);
+
+int phylink_connect_phy(struct phylink *, struct phy_device *);
+int phylink_of_phy_connect(struct phylink *, struct device_node *);
+void phylink_disconnect_phy(struct phylink *);
+
+void phylink_mac_change(struct phylink *, bool up);
+
+void phylink_start(struct phylink *);
+void phylink_stop(struct phylink *);
+
+int phylink_ethtool_get_settings(struct phylink *, struct ethtool_cmd *);
+int phylink_ethtool_set_settings(struct phylink *, struct ethtool_cmd *);
+int phylink_mii_ioctl(struct phylink *, struct ifreq *, int);
+
+#endif
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH RFC 12/26] phylink: add hooks for SFP support
  2015-12-07 17:35 [PATCH RFC 00/26] Phylink & SFP support Russell King - ARM Linux
                   ` (10 preceding siblings ...)
  2015-12-07 17:38 ` [PATCH RFC 11/26] phylink: add phylink infrastructure Russell King
@ 2015-12-07 17:38 ` Russell King
  2016-01-07 20:05   ` Florian Fainelli
  2015-12-07 17:38 ` [PATCH RFC 13/26] sfp: add phylink based SFP module support Russell King
                   ` (15 subsequent siblings)
  27 siblings, 1 reply; 58+ messages in thread
From: Russell King @ 2015-12-07 17:38 UTC (permalink / raw)
  To: Florian Fainelli, Thomas Petazzoni; +Cc: netdev

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.

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
 drivers/net/phy/phylink.c | 89 +++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/phylink.h   |  6 ++++
 2 files changed, 95 insertions(+)

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

^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH RFC 13/26] sfp: add phylink based SFP module support
  2015-12-07 17:35 [PATCH RFC 00/26] Phylink & SFP support Russell King - ARM Linux
                   ` (11 preceding siblings ...)
  2015-12-07 17:38 ` [PATCH RFC 12/26] phylink: add hooks for SFP support Russell King
@ 2015-12-07 17:38 ` Russell King
  2016-01-07 20:23   ` Florian Fainelli
  2015-12-07 17:38 ` [PATCH RFC 14/26] sfp: display SFP module information Russell King
                   ` (14 subsequent siblings)
  27 siblings, 1 reply; 58+ messages in thread
From: Russell King @ 2015-12-07 17:38 UTC (permalink / raw)
  To: Florian Fainelli, Thomas Petazzoni; +Cc: netdev

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 <rmk+kernel@arm.linux.org.uk>
---
 drivers/net/phy/Kconfig  |   5 +
 drivers/net/phy/Makefile |   1 +
 drivers/net/phy/sfp.c    | 989 +++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/sfp.h      | 338 ++++++++++++++++
 4 files changed, 1333 insertions(+)
 create mode 100644 drivers/net/phy/sfp.c
 create mode 100644 include/linux/sfp.h

diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index 5c634b4bc9bd..5bdf5c24c6ef 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -179,6 +179,11 @@ config FIXED_PHY
 
 	  Currently tested with mpc866ads and mpc8349e-mitx.
 
+config SFP
+	tristate "SFP cage support"
+	depends on I2C && PHYLINK
+	select MDIO_I2C
+
 config MDIO_BITBANG
 	tristate "Support for bitbanged MDIO buses"
 	help
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index bc052bb6cee0..a7be372fb123 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -45,3 +45,4 @@ obj-$(CONFIG_MDIO_MOXART)	+= mdio-moxart.o
 obj-$(CONFIG_MDIO_BCM_UNIMAC)	+= mdio-bcm-unimac.o
 obj-$(CONFIG_MICROCHIP_PHY)	+= microchip.o
 obj-$(CONFIG_MDIO_BCM_IPROC)	+= mdio-bcm-iproc.o
+obj-$(CONFIG_SFP)		+= sfp.o
diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c
new file mode 100644
index 000000000000..70a375403e55
--- /dev/null
+++ b/drivers/net/phy/sfp.c
@@ -0,0 +1,989 @@
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/netdevice.h>
+#include <linux/of.h>
+#include <linux/of_net.h>
+#include <linux/phylink.h>
+#include <linux/platform_device.h>
+#include <linux/sfp.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#include "mdio-i2c.h"
+#include "swphy.h"
+
+enum {
+	GPIO_MODDEF0,
+	GPIO_LOS,
+	GPIO_TX_FAULT,
+	GPIO_TX_DISABLE,
+	GPIO_RATE_SELECT,
+	GPIO_MAX,
+
+	SFP_F_PRESENT = BIT(GPIO_MODDEF0),
+	SFP_F_LOS = BIT(GPIO_LOS),
+	SFP_F_TX_FAULT = BIT(GPIO_TX_FAULT),
+	SFP_F_TX_DISABLE = BIT(GPIO_TX_DISABLE),
+	SFP_F_RATE_SELECT = BIT(GPIO_RATE_SELECT),
+
+	SFP_E_INSERT = 0,
+	SFP_E_REMOVE,
+	SFP_E_DEV_DOWN,
+	SFP_E_DEV_UP,
+	SFP_E_TX_FAULT,
+	SFP_E_TX_CLEAR,
+	SFP_E_LOS_HIGH,
+	SFP_E_LOS_LOW,
+	SFP_E_TIMEOUT,
+
+	SFP_MOD_EMPTY = 0,
+	SFP_MOD_PROBE,
+	SFP_MOD_PRESENT,
+	SFP_MOD_ERROR,
+
+	SFP_DEV_DOWN = 0,
+	SFP_DEV_UP,
+
+	SFP_S_DOWN = 0,
+	SFP_S_INIT,
+	SFP_S_WAIT_LOS,
+	SFP_S_LINK_UP,
+	SFP_S_TX_FAULT,
+	SFP_S_REINIT,
+	SFP_S_TX_DISABLE,
+};
+
+static const char *gpio_of_names[] = {
+	"moddef0",
+	"los",
+	"tx-fault",
+	"tx-disable",
+	"rate-select",
+};
+
+static const enum gpiod_flags gpio_flags[] = {
+	GPIOD_IN,
+	GPIOD_IN,
+	GPIOD_IN,
+	GPIOD_ASIS,
+	GPIOD_ASIS,
+};
+
+#define T_INIT_JIFFIES	msecs_to_jiffies(300)
+#define T_RESET_US	10
+#define T_FAULT_RECOVER	msecs_to_jiffies(1000)
+
+/* SFP module presence detection is poor: the three MOD DEF signals are
+ * the same length on the PCB, which means it's possible for MOD DEF 0 to
+ * connect before the I2C bus on MOD DEF 1/2.  Try to work around this
+ * design bug by waiting 50ms before probing, and then retry every 250ms.
+ */
+#define T_PROBE_INIT	msecs_to_jiffies(50)
+#define T_PROBE_RETRY	msecs_to_jiffies(250)
+
+/*
+ * SFP modules appear to always have their PHY configured for bus address
+ * 0x56 (which with mdio-i2c, translates to a PHY address of 22).
+ */
+#define SFP_PHY_ADDR	22
+
+/*
+ * Give this long for the PHY to reset.
+ */
+#define T_PHY_RESET_MS	50
+
+static DEFINE_MUTEX(sfp_mutex);
+
+struct sfp {
+	struct device *dev;
+	struct i2c_adapter *i2c;
+	struct mii_bus *i2c_mii;
+	struct net_device *ndev;
+	struct phylink *phylink;
+	struct phy_device *mod_phy;
+
+	unsigned int (*get_state)(struct sfp *);
+	void (*set_state)(struct sfp *, unsigned int);
+	int (*read)(struct sfp *, bool, u8, void *, size_t);
+
+	struct gpio_desc *gpio[GPIO_MAX];
+
+	unsigned int state;
+	struct delayed_work poll;
+	struct delayed_work timeout;
+	struct mutex sm_mutex;
+	unsigned char sm_mod_state;
+	unsigned char sm_dev_state;
+	unsigned short sm_state;
+	unsigned int sm_retries;
+
+	struct sfp_eeprom_id id;
+
+	struct notifier_block netdev_nb;
+};
+
+static unsigned long poll_jiffies;
+
+static unsigned int sfp_gpio_get_state(struct sfp *sfp)
+{
+	unsigned int i, state, v;
+
+	for (i = state = 0; i < GPIO_MAX; i++) {
+		if (gpio_flags[i] != GPIOD_IN || !sfp->gpio[i])
+			continue;
+
+		v = gpiod_get_value_cansleep(sfp->gpio[i]);
+		if (v)
+			state |= BIT(i);
+	}
+
+	return state;
+}
+
+static void sfp_gpio_set_state(struct sfp *sfp, unsigned int state)
+{
+	if (state & SFP_F_PRESENT) {
+		/* If the module is present, drive the signals */
+		if (sfp->gpio[GPIO_TX_DISABLE])
+			gpiod_direction_output(sfp->gpio[GPIO_TX_DISABLE],
+						state & SFP_F_TX_DISABLE);
+		if (state & SFP_F_RATE_SELECT)
+			gpiod_direction_output(sfp->gpio[GPIO_RATE_SELECT],
+						state & SFP_F_RATE_SELECT);
+	} else {
+		/* Otherwise, let them float to the pull-ups */
+		if (sfp->gpio[GPIO_TX_DISABLE])
+			gpiod_direction_input(sfp->gpio[GPIO_TX_DISABLE]);
+		if (state & SFP_F_RATE_SELECT)
+			gpiod_direction_input(sfp->gpio[GPIO_RATE_SELECT]);
+	}
+}
+
+static int sfp__i2c_read(struct i2c_adapter *i2c, u8 bus_addr, u8 dev_addr,
+	void *buf, size_t len)
+{
+	struct i2c_msg msgs[2];
+	int ret;
+
+	msgs[0].addr = bus_addr;
+	msgs[0].flags = 0;
+	msgs[0].len = 1;
+	msgs[0].buf = &dev_addr;
+	msgs[1].addr = bus_addr;
+	msgs[1].flags = I2C_M_RD;
+	msgs[1].len = len;
+	msgs[1].buf = buf;
+
+	ret = i2c_transfer(i2c, msgs, ARRAY_SIZE(msgs));
+	if (ret < 0)
+		return ret;
+
+	return ret == ARRAY_SIZE(msgs) ? len : 0;
+}
+
+static int sfp_i2c_read(struct sfp *sfp, bool a2, u8 addr, void *buf,
+	size_t len)
+{
+	return sfp__i2c_read(sfp->i2c, a2 ? 0x51 : 0x50, addr, buf, len);
+}
+
+static int sfp_i2c_configure(struct sfp *sfp, struct i2c_adapter *i2c)
+{
+	struct mii_bus *i2c_mii;
+	int ret;
+
+	if (!i2c_check_functionality(i2c, I2C_FUNC_I2C))
+		return -EINVAL;
+
+	sfp->i2c = i2c;
+	sfp->read = sfp_i2c_read;
+
+	i2c_mii = mdio_i2c_alloc(sfp->dev, i2c);
+	if (IS_ERR(i2c_mii))
+		return PTR_ERR(i2c_mii);
+
+	i2c_mii->name = "SFP I2C Bus";
+	i2c_mii->phy_mask = ~0;
+
+	ret = mdiobus_register(i2c_mii);
+	if (ret < 0) {
+		mdiobus_free(i2c_mii);
+		return ret;
+	}
+
+	sfp->i2c_mii = i2c_mii;
+
+	return 0;
+}
+
+
+/* Interface */
+static unsigned int sfp_get_state(struct sfp *sfp)
+{
+	return sfp->get_state(sfp);
+}
+
+static void sfp_set_state(struct sfp *sfp, unsigned int state)
+{
+	sfp->set_state(sfp, state);
+}
+
+static int sfp_read(struct sfp *sfp, bool a2, u8 addr, void *buf, size_t len)
+{
+	return sfp->read(sfp, a2, addr, buf, len);
+}
+
+static unsigned int sfp_check(void *buf, size_t len)
+{
+	u8 *p, check;
+
+	for (p = buf, check = 0; len; p++, len--)
+		check += *p;
+
+	return check;
+}
+
+/* Helpers */
+static void sfp_module_tx_disable(struct sfp *sfp)
+{
+	dev_dbg(sfp->dev, "tx disable %u -> %u\n",
+		sfp->state & SFP_F_TX_DISABLE ? 1 : 0, 1);
+	sfp->state |= SFP_F_TX_DISABLE;
+	sfp_set_state(sfp, sfp->state);
+}
+
+static void sfp_module_tx_enable(struct sfp *sfp)
+{
+	dev_dbg(sfp->dev, "tx disable %u -> %u\n",
+		sfp->state & SFP_F_TX_DISABLE ? 1 : 0, 0);
+	sfp->state &= ~SFP_F_TX_DISABLE;
+	sfp_set_state(sfp, sfp->state);
+}
+
+static void sfp_module_tx_fault_reset(struct sfp *sfp)
+{
+	unsigned int state = sfp->state;
+
+	if (state & SFP_F_TX_DISABLE)
+		return;
+
+	sfp_set_state(sfp, state | SFP_F_TX_DISABLE);
+
+	udelay(T_RESET_US);
+
+	sfp_set_state(sfp, state);
+}
+
+/* SFP state machine */
+static void sfp_sm_set_timer(struct sfp *sfp, unsigned int timeout)
+{
+	if (timeout)
+		mod_delayed_work(system_power_efficient_wq, &sfp->timeout,
+				 timeout);
+	else
+		cancel_delayed_work(&sfp->timeout);
+}
+
+static void sfp_sm_next(struct sfp *sfp, unsigned int state,
+			unsigned int timeout)
+{
+	sfp->sm_state = state;
+	sfp_sm_set_timer(sfp, timeout);
+}
+
+static void sfp_sm_ins_next(struct sfp *sfp, unsigned int state, unsigned int timeout)
+{
+	sfp->sm_mod_state = state;
+	sfp_sm_set_timer(sfp, timeout);
+}
+
+static void sfp_sm_phy_detach(struct sfp *sfp)
+{
+	phy_stop(sfp->mod_phy);
+	if (sfp->phylink)
+		phylink_disconnect_phy(sfp->phylink);
+	phy_device_remove(sfp->mod_phy);
+	phy_device_free(sfp->mod_phy);
+	sfp->mod_phy = NULL;
+}
+
+static void sfp_sm_probe_phy(struct sfp *sfp)
+{
+	struct phy_device *phy;
+	int err;
+
+	msleep(T_PHY_RESET_MS);
+
+	phy = mdiobus_scan(sfp->i2c_mii, SFP_PHY_ADDR);
+	if (IS_ERR(phy)) {
+		dev_err(sfp->dev, "mdiobus scan returned %ld\n", PTR_ERR(phy));
+		return;
+	}
+	if (!phy) {
+		dev_info(sfp->dev, "no PHY detected\n");
+		return;
+	}
+
+	err = phylink_connect_phy(sfp->phylink, phy);
+	if (err) {
+		phy_device_remove(phy);
+		phy_device_free(phy);
+		dev_err(sfp->dev, "phylink_connect_phy failed: %d\n", err);
+		return;
+	}
+
+	/* We found a PHY.  Switch the link to PHY mode, so we use phylib
+	 * to feed the negotiation results to the MAC.  This avoids the
+	 * question over which Serdes mode the PHY is operating.
+	 */
+	sfp->mod_phy = phy;
+	phylink_set_link_an_mode(sfp->phylink, MLO_AN_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 && sfp->id.base.e1000_base_t)
+		sfp_sm_probe_phy(sfp);
+}
+
+static int sfp_sm_mod_probe(struct sfp *sfp)
+{
+	/* SFP module inserted - read I2C data */
+	struct sfp_eeprom_id id;
+	char vendor[17];
+	char part[17];
+	char sn[17];
+	char date[9];
+	char rev[5];
+	u8 check;
+	int err;
+
+	err = sfp_read(sfp, false, 0, &id, sizeof(id));
+	if (err < 0) {
+		dev_err(sfp->dev, "failed to read EEPROM: %d\n", err);
+		return -EAGAIN;
+	}
+
+	/* Validate the checksum over the base structure */
+	check = sfp_check(&id.base, sizeof(id.base) - 1);
+	if (check != id.base.cc_base) {
+		dev_err(sfp->dev,
+			"EEPROM base structure checksum failure: 0x%02x\n",
+			check);
+		return -EINVAL;
+	}
+
+	check = sfp_check(&id.ext, sizeof(id.ext) - 1);
+	if (check != id.ext.cc_ext) {
+		dev_err(sfp->dev,
+			"EEPROM extended structure checksum failure: 0x%02x\n",
+			check);
+		memset(&id.ext, 0, sizeof(id.ext));
+	}
+
+	sfp->id = id;
+
+	memcpy(vendor, sfp->id.base.vendor_name, 16);
+	vendor[16] = '\0';
+	memcpy(part, sfp->id.base.vendor_pn, 16);
+	part[16] = '\0';
+	memcpy(rev, sfp->id.base.vendor_rev, 4);
+	rev[4] = '\0';
+	memcpy(sn, sfp->id.ext.vendor_sn, 16);
+	sn[16] = '\0';
+	memcpy(date, sfp->id.ext.datecode, 8);
+	date[8] = '\0';
+
+	dev_info(sfp->dev, "module %s %s rev %s sn %s dc %s\n", vendor, part, rev, sn, date);
+
+	/* We only support SFP modules, not the legacy GBIC modules. */
+	if (sfp->id.base.phys_id != SFP_PHYS_ID_SFP ||
+	    sfp->id.base.phys_ext_id != SFP_PHYS_EXT_ID_SFP) {
+		dev_err(sfp->dev, "module is not SFP - phys id 0x%02x 0x%02x\n",
+			sfp->id.base.phys_id, sfp->id.base.phys_ext_id);
+		return -EINVAL;
+	}
+
+	/*
+	 * What isn't clear from the SFP documentation is whether this
+	 * specifies the encoding expected on the TD/RD lines, or whether
+	 * the TD/RD lines are always 8b10b encoded, but the transceiver
+	 * converts.  Eg, think of a copper SFP supporting 1G/100M/10M
+	 * ethernet: this requires 8b10b encoding for 1G, 4b5b for 100M,
+	 * and manchester for 10M.
+	 */
+	/* 1Gbit ethernet requires 8b10b encoding */
+	if (sfp->id.base.encoding != SFP_ENCODING_8B10B) {
+		dev_err(sfp->dev, "module does not support 8B10B encoding\n");
+		return -EINVAL;
+	}
+
+	if (sfp->phylink) {
+		u32 support;
+		u8 port;
+
+		if (sfp->id.base.e1000_base_t) {
+			support = SUPPORTED_TP;
+			port = PORT_TP;
+		} else {
+			support = SUPPORTED_FIBRE;
+			port = PORT_FIBRE;
+		}
+		phylink_set_link_port(sfp->phylink, support, port);
+
+		/* 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 fiber module, it probably does not have a
+		 * PHY, so switch to 802.3z negotiation mode.
+		 *
+		 * If it's a copper module, try SGMII mode.  If that
+		 * doesn't work, we should try switching to 802.3z mode.
+		 */
+		if (!sfp->id.base.e1000_base_t)
+			phylink_set_link_an_mode(sfp->phylink, MLO_AN_8023Z);
+		else
+			phylink_set_link_an_mode(sfp->phylink, MLO_AN_SGMII);
+	}
+
+	return 0;
+}
+
+static void sfp_sm_mod_remove(struct sfp *sfp)
+{
+	if (sfp->mod_phy)
+		sfp_sm_phy_detach(sfp);
+
+	sfp_module_tx_disable(sfp);
+
+	memset(&sfp->id, 0, sizeof(sfp->id));
+
+	dev_info(sfp->dev, "module removed\n");
+}
+
+static void sfp_sm_event(struct sfp *sfp, unsigned int event)
+{
+	mutex_lock(&sfp->sm_mutex);
+
+	dev_dbg(sfp->dev, "SM: enter %u:%u:%u event %u\n",
+		sfp->sm_mod_state, sfp->sm_dev_state, sfp->sm_state, event);
+
+	/* This state machine tracks the insert/remove state of
+	 * the module, and handles probing the on-board EEPROM.
+	 */
+	switch (sfp->sm_mod_state) {
+	default:
+		if (event == SFP_E_INSERT) {
+			sfp_module_tx_disable(sfp);
+			sfp_sm_ins_next(sfp, SFP_MOD_PROBE, T_PROBE_INIT);
+		}
+		break;
+
+	case SFP_MOD_PROBE:
+		if (event == SFP_E_REMOVE) {
+			sfp_sm_ins_next(sfp, SFP_MOD_EMPTY, 0);
+		} else if (event == SFP_E_TIMEOUT) {
+			int err = sfp_sm_mod_probe(sfp);
+
+			if (err == 0)
+				sfp_sm_ins_next(sfp, SFP_MOD_PRESENT, 0);
+			else if (err == -EAGAIN)
+				sfp_sm_set_timer(sfp, T_PROBE_RETRY);
+			else
+				sfp_sm_ins_next(sfp, SFP_MOD_ERROR, 0);
+		}
+		break;
+
+	case SFP_MOD_PRESENT:
+	case SFP_MOD_ERROR:
+		if (event == SFP_E_REMOVE) {
+			sfp_sm_mod_remove(sfp);
+			sfp_sm_ins_next(sfp, SFP_MOD_EMPTY, 0);
+		}
+		break;
+	}
+
+	/* This state machine tracks the netdev up/down state */
+	switch (sfp->sm_dev_state) {
+	default:
+		if (event == SFP_E_DEV_UP)
+			sfp->sm_dev_state = SFP_DEV_UP;
+		break;
+
+	case SFP_DEV_UP:
+		if (event == SFP_E_DEV_DOWN) {
+			/* If the module has a PHY, avoid raising TX disable
+			 * as this resets the PHY. Otherwise, raise it to
+			 * turn the laser off.
+			 */
+			if (!sfp->mod_phy)
+				sfp_module_tx_disable(sfp);
+			sfp->sm_dev_state = SFP_DEV_DOWN;
+		}
+		break;
+	}
+
+	/* Some events are global */
+	if (sfp->sm_state != SFP_S_DOWN &&
+	    (sfp->sm_mod_state != SFP_MOD_PRESENT ||
+	     sfp->sm_dev_state != SFP_DEV_UP)) {
+		if (sfp->sm_state == SFP_S_LINK_UP &&
+		    sfp->sm_dev_state == SFP_DEV_UP)
+			sfp_sm_link_down(sfp);
+		if (sfp->mod_phy)
+			sfp_sm_phy_detach(sfp);
+		sfp_sm_next(sfp, SFP_S_DOWN, 0);
+		mutex_unlock(&sfp->sm_mutex);
+		return;
+	}
+
+	/* The main state machine */
+	switch (sfp->sm_state) {
+	case SFP_S_DOWN:
+		if (sfp->sm_mod_state == SFP_MOD_PRESENT &&
+		    sfp->sm_dev_state == SFP_DEV_UP)
+			sfp_sm_mod_init(sfp);
+		break;
+
+	case SFP_S_INIT:
+		if (event == SFP_E_TIMEOUT && sfp->state & SFP_F_TX_FAULT)
+			sfp_sm_fault(sfp, true);
+		else if (event == SFP_E_TIMEOUT || event == SFP_E_TX_CLEAR)
+			sfp_sm_link_check_los(sfp);
+		break;
+
+	case SFP_S_WAIT_LOS:
+		if (event == SFP_E_TX_FAULT)
+			sfp_sm_fault(sfp, true);
+		else if (event ==
+			 (sfp->id.ext.options & SFP_OPTIONS_LOS_INVERTED ?
+			  SFP_E_LOS_HIGH : SFP_E_LOS_LOW))
+			sfp_sm_link_up(sfp);
+		break;
+
+	case SFP_S_LINK_UP:
+		if (event == SFP_E_TX_FAULT) {
+			sfp_sm_link_down(sfp);
+			sfp_sm_fault(sfp, true);
+		} else if (event ==
+			   (sfp->id.ext.options & SFP_OPTIONS_LOS_INVERTED ?
+			    SFP_E_LOS_LOW : SFP_E_LOS_HIGH)) {
+			sfp_sm_link_down(sfp);
+			sfp_sm_next(sfp, SFP_S_WAIT_LOS, 0);
+		}
+		break;
+
+	case SFP_S_TX_FAULT:
+		if (event == SFP_E_TIMEOUT) {
+			sfp_module_tx_fault_reset(sfp);
+			sfp_sm_next(sfp, SFP_S_REINIT, T_INIT_JIFFIES);
+		}
+		break;
+
+	case SFP_S_REINIT:
+		if (event == SFP_E_TIMEOUT && sfp->state & SFP_F_TX_FAULT) {
+			sfp_sm_fault(sfp, false);
+		} else if (event == SFP_E_TIMEOUT || event == SFP_E_TX_CLEAR) {
+			dev_info(sfp->dev, "module transmit fault recovered\n");
+			sfp_sm_link_check_los(sfp);
+		}
+		break;
+
+	case SFP_S_TX_DISABLE:
+		break;
+	}
+
+	dev_dbg(sfp->dev, "SM: exit %u:%u:%u\n",
+		sfp->sm_mod_state, sfp->sm_dev_state, sfp->sm_state);
+
+	mutex_unlock(&sfp->sm_mutex);
+}
+
+#if 0
+static int sfp_phy_module_info(struct phy_device *phy,
+			       struct ethtool_modinfo *modinfo)
+{
+	struct sfp *sfp = phy->priv;
+
+	/* locking... and check module is present */
+
+	if (sfp->id.ext.sff8472_compliance) {
+		modinfo->type = ETH_MODULE_SFF_8472;
+		modinfo->eeprom_len = ETH_MODULE_SFF_8472_LEN;
+	} else {
+		modinfo->type = ETH_MODULE_SFF_8079;
+		modinfo->eeprom_len = ETH_MODULE_SFF_8079_LEN;
+	}
+	return 0;
+}
+
+static int sfp_phy_module_eeprom(struct phy_device *phy,
+	struct ethtool_eeprom *ee, u8 *data)
+{
+	struct sfp *sfp = phy->priv;
+	unsigned int first, last, len;
+	int ret;
+
+	if (ee->len == 0)
+		return -EINVAL;
+
+	first = ee->offset;
+	last = ee->offset + ee->len;
+	if (first < ETH_MODULE_SFF_8079_LEN) {
+		len = last;
+		if (len > ETH_MODULE_SFF_8079_LEN)
+			len = ETH_MODULE_SFF_8079_LEN;
+		len -= first;
+
+		ret = sfp->read(sfp, false, first, data, len);
+		if (ret < 0)
+			return ret;
+
+		first += len;
+		data += len;
+	}
+	if (first >= ETH_MODULE_SFF_8079_LEN && last > first) {
+		len = last - first;
+
+		ret = sfp->read(sfp, true, first, data, len);
+		if (ret < 0)
+			return ret;
+	}
+	return 0;
+}
+#endif
+
+static void sfp_timeout(struct work_struct *work)
+{
+	struct sfp *sfp = container_of(work, struct sfp, timeout.work);
+
+	sfp_sm_event(sfp, SFP_E_TIMEOUT);
+}
+
+static void sfp_check_state(struct sfp *sfp)
+{
+	unsigned int state, i, changed;
+
+	state = sfp_get_state(sfp);
+	changed = state ^ sfp->state;
+	changed &= SFP_F_PRESENT | SFP_F_LOS | SFP_F_TX_FAULT;
+
+	for (i = 0; i < GPIO_MAX; i++)
+		if (changed & BIT(i))
+			dev_dbg(sfp->dev, "%s %u -> %u\n", gpio_of_names[i],
+				!!(sfp->state & BIT(i)), !!(state & BIT(i)));
+
+	state |= sfp->state & (SFP_F_TX_DISABLE | SFP_F_RATE_SELECT);
+	sfp->state = state;
+
+	if (changed & SFP_F_PRESENT)
+		sfp_sm_event(sfp, state & SFP_F_PRESENT ?
+				SFP_E_INSERT : SFP_E_REMOVE);
+
+	if (changed & SFP_F_TX_FAULT)
+		sfp_sm_event(sfp, state & SFP_F_TX_FAULT ?
+				SFP_E_TX_FAULT : SFP_E_TX_CLEAR);
+
+	if (changed & SFP_F_LOS)
+		sfp_sm_event(sfp, state & SFP_F_LOS ?
+				SFP_E_LOS_HIGH : SFP_E_LOS_LOW);
+}
+
+static irqreturn_t sfp_irq(int irq, void *data)
+{
+	struct sfp *sfp = data;
+
+	sfp_check_state(sfp);
+
+	return IRQ_HANDLED;
+}
+
+static void sfp_poll(struct work_struct *work)
+{
+	struct sfp *sfp = container_of(work, struct sfp, poll.work);
+
+	sfp_check_state(sfp);
+	mod_delayed_work(system_wq, &sfp->poll, poll_jiffies);
+}
+
+static int sfp_netdev_notify(struct notifier_block *nb, unsigned long act, void *data)
+{
+	struct sfp *sfp = container_of(nb, struct sfp, netdev_nb);
+	struct netdev_notifier_info *info = data;
+	struct net_device *ndev = info->dev;
+
+	if (!sfp->ndev || ndev != sfp->ndev)
+		return NOTIFY_DONE;
+
+	switch (act) {
+	case NETDEV_UP:
+		sfp_sm_event(sfp, SFP_E_DEV_UP);
+		break;
+
+	case NETDEV_GOING_DOWN:
+		sfp_sm_event(sfp, SFP_E_DEV_DOWN);
+		break;
+
+	case NETDEV_UNREGISTER:
+		if (sfp->mod_phy && sfp->phylink)
+			phylink_disconnect_phy(sfp->phylink);
+		sfp->phylink = NULL;
+		dev_put(sfp->ndev);
+		sfp->ndev = NULL;
+		break;
+	}
+	return NOTIFY_OK;
+}
+
+static struct sfp *sfp_alloc(struct device *dev)
+{
+	struct sfp *sfp;
+
+	sfp = kzalloc(sizeof(*sfp), GFP_KERNEL);
+	if (!sfp)
+		return ERR_PTR(-ENOMEM);
+
+	sfp->dev = dev;
+
+	mutex_init(&sfp->sm_mutex);
+	INIT_DELAYED_WORK(&sfp->poll, sfp_poll);
+	INIT_DELAYED_WORK(&sfp->timeout, sfp_timeout);
+
+	sfp->netdev_nb.notifier_call = sfp_netdev_notify;
+
+	return sfp;
+}
+
+static void sfp_destroy(struct sfp *sfp)
+{
+	cancel_delayed_work_sync(&sfp->poll);
+	cancel_delayed_work_sync(&sfp->timeout);
+	if (sfp->i2c_mii) {
+		mdiobus_unregister(sfp->i2c_mii);
+		mdiobus_free(sfp->i2c_mii);
+	}
+	if (sfp->i2c)
+		i2c_put_adapter(sfp->i2c);
+	of_node_put(sfp->dev->of_node);
+	kfree(sfp);
+}
+
+static void sfp_cleanup(void *data)
+{
+	struct sfp *sfp = data;
+
+	sfp_destroy(sfp);
+}
+
+static int sfp_probe(struct platform_device *pdev)
+{
+	struct sfp *sfp;
+	bool poll = false;
+	int irq, err, i;
+
+	sfp = sfp_alloc(&pdev->dev);
+	if (IS_ERR(sfp))
+		return PTR_ERR(sfp);
+
+	platform_set_drvdata(pdev, sfp);
+
+	err = devm_add_action(sfp->dev, sfp_cleanup, sfp);
+	if (err < 0)
+		return err;
+
+	if (pdev->dev.of_node) {
+		struct device_node *node = pdev->dev.of_node;
+		struct device_node *np;
+
+		np = of_parse_phandle(node, "i2c-bus", 0);
+		if (np) {
+			struct i2c_adapter *i2c;
+
+			i2c = of_find_i2c_adapter_by_node(np);
+			of_node_put(np);
+			if (!i2c)
+				return -EPROBE_DEFER;
+
+			err = sfp_i2c_configure(sfp, i2c);
+			if (err < 0) {
+				i2c_put_adapter(i2c);
+				return err;
+			}
+		}
+
+		for (i = 0; i < GPIO_MAX; i++) {
+			sfp->gpio[i] = devm_gpiod_get_optional(sfp->dev,
+					   gpio_of_names[i], gpio_flags[i]);
+			if (IS_ERR(sfp->gpio[i]))
+				return PTR_ERR(sfp->gpio[i]);
+		}
+
+		sfp->get_state = sfp_gpio_get_state;
+		sfp->set_state = sfp_gpio_set_state;
+
+		np = of_parse_phandle(node, "sfp,ethernet", 0);
+		if (!np) {
+			dev_err(sfp->dev, "missing sfp,ethernet property\n");
+			return -EINVAL;
+		}
+
+		sfp->ndev = of_find_net_device_by_node(np);
+		if (!sfp->ndev) {
+			dev_err(sfp->dev, "ethernet device not found\n");
+			return -EPROBE_DEFER;
+		}
+
+		dev_hold(sfp->ndev);
+		put_device(&sfp->ndev->dev);
+
+		sfp->phylink = phylink_lookup_by_netdev(sfp->ndev);
+		if (!sfp->phylink) {
+			dev_err(sfp->dev, "ethernet device not found\n");
+			return -EPROBE_DEFER;
+		}
+
+		phylink_disable(sfp->phylink);
+	}
+
+	sfp->state = sfp_get_state(sfp);
+	if (sfp->gpio[GPIO_TX_DISABLE] &&
+	    gpiod_get_value_cansleep(sfp->gpio[GPIO_TX_DISABLE]))
+		sfp->state |= SFP_F_TX_DISABLE;
+	if (sfp->gpio[GPIO_RATE_SELECT] &&
+	    gpiod_get_value_cansleep(sfp->gpio[GPIO_RATE_SELECT]))
+		sfp->state |= SFP_F_RATE_SELECT;
+	sfp_set_state(sfp, sfp->state);
+	sfp_module_tx_disable(sfp);
+	if (sfp->state & SFP_F_PRESENT)
+		sfp_sm_event(sfp, SFP_E_INSERT);
+
+	for (i = 0; i < GPIO_MAX; i++) {
+		if (gpio_flags[i] != GPIOD_IN || !sfp->gpio[i])
+			continue;
+
+		irq = gpiod_to_irq(sfp->gpio[i]);
+		if (!irq) {
+			poll = true;
+			continue;
+		}
+
+		err = devm_request_threaded_irq(sfp->dev, irq, NULL, sfp_irq,
+						IRQF_ONESHOT |
+						IRQF_TRIGGER_RISING |
+						IRQF_TRIGGER_FALLING,
+						dev_name(sfp->dev), sfp);
+		if (err)
+			poll = true;
+	}
+
+	if (poll)
+		mod_delayed_work(system_wq, &sfp->poll, poll_jiffies);
+
+	register_netdevice_notifier(&sfp->netdev_nb);
+
+	return 0;
+}
+
+static int sfp_remove(struct platform_device *pdev)
+{
+	struct sfp *sfp = platform_get_drvdata(pdev);
+
+	unregister_netdevice_notifier(&sfp->netdev_nb);
+	if (sfp->ndev)
+		dev_put(sfp->ndev);
+
+	return 0;
+}
+
+static const struct of_device_id sfp_of_match[] = {
+	{ .compatible = "sff,sfp", },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, sfp_of_match);
+
+static struct platform_driver sfp_driver = {
+	.probe = sfp_probe,
+	.remove = sfp_remove,
+	.driver = {
+		.name = "sfp",
+		.of_match_table = sfp_of_match,
+	},
+};
+
+static int sfp_init(void)
+{
+	poll_jiffies = msecs_to_jiffies(100);
+
+	return platform_driver_register(&sfp_driver);
+}
+module_init(sfp_init);
+
+static void sfp_exit(void)
+{
+	platform_driver_unregister(&sfp_driver);
+}
+module_exit(sfp_exit);
+
+MODULE_ALIAS("platform:sfp");
+MODULE_AUTHOR("Russell King");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/sfp.h b/include/linux/sfp.h
new file mode 100644
index 000000000000..15be090170c0
--- /dev/null
+++ b/include/linux/sfp.h
@@ -0,0 +1,338 @@
+#ifndef LINUX_SFP_H
+#define LINUX_SFP_H
+
+struct __packed sfp_eeprom_base {
+	u8 phys_id;
+	u8 phys_ext_id;
+	u8 connector;
+#if defined __BIG_ENDIAN_BITFIELD
+	u8 e10g_base_er:1;
+	u8 e10g_base_lrm:1;
+	u8 e10g_base_lr:1;
+	u8 e10g_base_sr:1;
+	u8 if_1x_sx:1;
+	u8 if_1x_lx:1;
+	u8 if_1x_copper_active:1;
+	u8 if_1x_copper_passive:1;
+
+	u8 escon_mmf_1310_led:1;
+	u8 escon_smf_1310_laser:1;
+	u8 sonet_oc192_short_reach:1;
+	u8 sonet_reach_bit1:1;
+	u8 sonet_reach_bit2:1;
+	u8 sonet_oc48_long_reach:1;
+	u8 sonet_oc48_intermediate_reach:1;
+	u8 sonet_oc48_short_reach:1;
+
+	u8 unallocated_5_7:1;
+	u8 sonet_oc12_smf_long_reach:1;
+	u8 sonet_oc12_smf_intermediate_reach:1;
+	u8 sonet_oc12_short_reach:1;
+	u8 unallocated_5_3:1;
+	u8 sonet_oc3_smf_long_reach:1;
+	u8 sonet_oc3_smf_intermediate_reach:1;
+	u8 sonet_oc3_short_reach:1;
+
+	u8 e_base_px:1;
+	u8 e_base_bx10:1;
+	u8 e100_base_fx:1;
+	u8 e100_base_lx:1;
+	u8 e1000_base_t:1;
+	u8 e1000_base_cx:1;
+	u8 e1000_base_lx:1;
+	u8 e1000_base_sx:1;
+
+	u8 fc_ll_v:1;
+	u8 fc_ll_s:1;
+	u8 fc_ll_i:1;
+	u8 fc_ll_l:1;
+	u8 fc_ll_m:1;
+	u8 fc_tech_sa:1;
+	u8 fc_tech_lc:1;
+	u8 fc_tech_electrical_inter_enclosure:1;
+
+	u8 fc_tech_electrical_intra_enclosure:1;
+	u8 fc_tech_sn:1;
+	u8 fc_tech_sl:1;
+	u8 fc_tech_ll:1;
+	u8 sfp_ct_active:1;
+	u8 sfp_ct_passive:1;
+	u8 unallocated_8_1:1;
+	u8 unallocated_8_0:1;
+
+	u8 fc_media_tw:1;
+	u8 fc_media_tp:1;
+	u8 fc_media_mi:1;
+	u8 fc_media_tv:1;
+	u8 fc_media_m6:1;
+	u8 fc_media_m5:1;
+	u8 unallocated_9_1:1;
+	u8 fc_media_sm:1;
+
+	u8 fc_speed_1200:1;
+	u8 fc_speed_800:1;
+	u8 fc_speed_1600:1;
+	u8 fc_speed_400:1;
+	u8 fc_speed_3200:1;
+	u8 fc_speed_200:1;
+	u8 unallocated_10_1:1;
+	u8 fc_speed_100:1;
+#elif defined __LITTLE_ENDIAN_BITFIELD
+	u8 if_1x_copper_passive:1;
+	u8 if_1x_copper_active:1;
+	u8 if_1x_lx:1;
+	u8 if_1x_sx:1;
+	u8 e10g_base_sr:1;
+	u8 e10g_base_lr:1;
+	u8 e10g_base_lrm:1;
+	u8 e10g_base_er:1;
+
+	u8 sonet_oc3_short_reach:1;
+	u8 sonet_oc3_smf_intermediate_reach:1;
+	u8 sonet_oc3_smf_long_reach:1;
+	u8 unallocated_5_3:1;
+	u8 sonet_oc12_short_reach:1;
+	u8 sonet_oc12_smf_intermediate_reach:1;
+	u8 sonet_oc12_smf_long_reach:1;
+	u8 unallocated_5_7:1;
+
+	u8 sonet_oc48_short_reach:1;
+	u8 sonet_oc48_intermediate_reach:1;
+	u8 sonet_oc48_long_reach:1;
+	u8 sonet_reach_bit2:1;
+	u8 sonet_reach_bit1:1;
+	u8 sonet_oc192_short_reach:1;
+	u8 escon_smf_1310_laser:1;
+	u8 escon_mmf_1310_led:1;
+
+	u8 e1000_base_sx:1;
+	u8 e1000_base_lx:1;
+	u8 e1000_base_cx:1;
+	u8 e1000_base_t:1;
+	u8 e100_base_lx:1;
+	u8 e100_base_fx:1;
+	u8 e_base_bx10:1;
+	u8 e_base_px:1;
+
+	u8 fc_tech_electrical_inter_enclosure:1;
+	u8 fc_tech_lc:1;
+	u8 fc_tech_sa:1;
+	u8 fc_ll_m:1;
+	u8 fc_ll_l:1;
+	u8 fc_ll_i:1;
+	u8 fc_ll_s:1;
+	u8 fc_ll_v:1;
+
+	u8 unallocated_8_0:1;
+	u8 unallocated_8_1:1;
+	u8 sfp_ct_passive:1;
+	u8 sfp_ct_active:1;
+	u8 fc_tech_ll:1;
+	u8 fc_tech_sl:1;
+	u8 fc_tech_sn:1;
+	u8 fc_tech_electrical_intra_enclosure:1;
+
+	u8 fc_media_sm:1;
+	u8 unallocated_9_1:1;
+	u8 fc_media_m5:1;
+	u8 fc_media_m6:1;
+	u8 fc_media_tv:1;
+	u8 fc_media_mi:1;
+	u8 fc_media_tp:1;
+	u8 fc_media_tw:1;
+
+	u8 fc_speed_100:1;
+	u8 unallocated_10_1:1;
+	u8 fc_speed_200:1;
+	u8 fc_speed_3200:1;
+	u8 fc_speed_400:1;
+	u8 fc_speed_1600:1;
+	u8 fc_speed_800:1;
+	u8 fc_speed_1200:1;
+#else
+#error Unknown Endian
+#endif
+	u8 encoding;
+	u8 br_nominal;
+	u8 rate_id;
+	u8 link_len[6];
+	char vendor_name[16];
+	u8 reserved36;
+	char vendor_oui[3];
+	char vendor_pn[16];
+	char vendor_rev[4];
+	union {
+		__be16 optical_wavelength;
+		u8 cable_spec;
+	};
+	u8 reserved62;
+	u8 cc_base;
+};
+
+struct __packed sfp_eeprom_ext {
+	__be16 options;
+	u8 br_max;
+	u8 br_min;
+	char vendor_sn[16];
+	char datecode[8];
+	u8 diagmon;
+	u8 enhopts;
+	u8 sff8472_compliance;
+	u8 cc_ext;
+};
+
+struct __packed sfp_eeprom_id {
+	struct sfp_eeprom_base base;
+	struct sfp_eeprom_ext ext;
+};
+
+/* SFP EEPROM registers */
+enum {
+	SFP_PHYS_ID			= 0x00,
+	SFP_PHYS_EXT_ID			= 0x01,
+	SFP_CONNECTOR			= 0x02,
+	SFP_COMPLIANCE			= 0x03,
+	SFP_ENCODING			= 0x0b,
+	SFP_BR_NOMINAL			= 0x0c,
+	SFP_RATE_ID			= 0x0d,
+	SFP_LINK_LEN_SM_KM		= 0x0e,
+	SFP_LINK_LEN_SM_100M		= 0x0f,
+	SFP_LINK_LEN_50UM_OM2_10M	= 0x10,
+	SFP_LINK_LEN_62_5UM_OM1_10M	= 0x11,
+	SFP_LINK_LEN_COPPER_1M		= 0x12,
+	SFP_LINK_LEN_50UM_OM4_10M	= 0x12,
+	SFP_LINK_LEN_50UM_OM3_10M	= 0x13,
+	SFP_VENDOR_NAME			= 0x14,
+	SFP_VENDOR_OUI			= 0x25,
+	SFP_VENDOR_PN			= 0x28,
+	SFP_VENDOR_REV			= 0x38,
+	SFP_OPTICAL_WAVELENGTH_MSB	= 0x3c,
+	SFP_OPTICAL_WAVELENGTH_LSB	= 0x3d,
+	SFP_CABLE_SPEC			= 0x3c,
+	SFP_CC_BASE			= 0x3f,
+	SFP_OPTIONS			= 0x40,	/* 2 bytes, MSB, LSB */
+	SFP_BR_MAX			= 0x42,
+	SFP_BR_MIN			= 0x43,
+	SFP_VENDOR_SN			= 0x44,
+	SFP_DATECODE			= 0x54,
+	SFP_DIAGMON			= 0x5c,
+	SFP_ENHOPTS			= 0x5d,
+	SFP_SFF8472_COMPLIANCE		= 0x5e,
+	SFP_CC_EXT			= 0x5f,
+
+	SFP_PHYS_ID_SFP			= 0x03,
+	SFP_PHYS_EXT_ID_SFP		= 0x04,
+	SFP_CONNECTOR_UNSPEC		= 0x00,
+	/* codes 01-05 not supportable on SFP */
+	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
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH RFC 14/26] sfp: display SFP module information
  2015-12-07 17:35 [PATCH RFC 00/26] Phylink & SFP support Russell King - ARM Linux
                   ` (12 preceding siblings ...)
  2015-12-07 17:38 ` [PATCH RFC 13/26] sfp: add phylink based SFP module support Russell King
@ 2015-12-07 17:38 ` Russell King
  2016-01-07 20:23   ` Florian Fainelli
  2015-12-07 17:38 ` [PATCH RFC 15/26] net: mvneta: convert to phylink Russell King
                   ` (13 subsequent siblings)
  27 siblings, 1 reply; 58+ messages in thread
From: Russell King @ 2015-12-07 17:38 UTC (permalink / raw)
  To: Florian Fainelli, Thomas Petazzoni; +Cc: netdev

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
 drivers/net/phy/sfp.c | 245 +++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 244 insertions(+), 1 deletion(-)

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

^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH RFC 15/26] net: mvneta: convert to phylink
  2015-12-07 17:35 [PATCH RFC 00/26] Phylink & SFP support Russell King - ARM Linux
                   ` (13 preceding siblings ...)
  2015-12-07 17:38 ` [PATCH RFC 14/26] sfp: display SFP module information Russell King
@ 2015-12-07 17:38 ` Russell King
  2016-01-07 20:22   ` Florian Fainelli
  2015-12-07 17:38 ` [PATCH RFC 16/26] phy: fixed-phy: remove fixed_phy_update_state() Russell King
                   ` (12 subsequent siblings)
  27 siblings, 1 reply; 58+ messages in thread
From: Russell King @ 2015-12-07 17:38 UTC (permalink / raw)
  To: Florian Fainelli, Thomas Petazzoni; +Cc: netdev

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

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
 drivers/net/ethernet/marvell/Kconfig  |   2 +-
 drivers/net/ethernet/marvell/mvneta.c | 399 +++++++++++++++++++---------------
 2 files changed, 224 insertions(+), 177 deletions(-)

diff --git a/drivers/net/ethernet/marvell/Kconfig b/drivers/net/ethernet/marvell/Kconfig
index a1c862b4664d..d59fb29f28b3 100644
--- a/drivers/net/ethernet/marvell/Kconfig
+++ b/drivers/net/ethernet/marvell/Kconfig
@@ -44,7 +44,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 e84c7f2634d3..f19d9a31dccd 100644
--- a/drivers/net/ethernet/marvell/mvneta.c
+++ b/drivers/net/ethernet/marvell/mvneta.c
@@ -31,6 +31,7 @@
 #include <linux/of_net.h>
 #include <linux/of_address.h>
 #include <linux/phy.h>
+#include <linux/phylink.h>
 #include <linux/clk.h>
 #include <linux/cpu.h>
 
@@ -169,6 +170,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)
@@ -184,13 +186,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)
@@ -361,15 +369,9 @@ struct mvneta_port {
 	u16 tx_ring_size;
 	u16 rx_ring_size;
 
-	struct mii_bus *mii_bus;
-	struct phy_device *phy_dev;
-	phy_interface_t phy_interface;
-	struct device_node *phy_node;
-	unsigned int link;
-	unsigned int duplex;
-	unsigned int speed;
+	struct device_node *dn;
 	unsigned int tx_csum_limit;
-	int use_inband_status:1;
+	struct phylink *phylink;
 
 	u64 ethtool_stats[ARRAY_SIZE(mvneta_statistics)];
 };
@@ -1056,26 +1058,6 @@ static void mvneta_defaults_set(struct mvneta_port *pp)
 	val &= ~MVNETA_PHY_POLLING_ENABLE;
 	mvreg_write(pp, MVNETA_UNIT_CONTROL, val);
 
-	if (pp->use_inband_status) {
-		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);
-	} 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);
-	}
-
 	mvneta_set_ucast_table(pp, -1);
 	mvneta_set_special_mcast_table(pp, -1);
 	mvneta_set_other_mcast_table(pp, -1);
@@ -2115,26 +2097,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
@@ -2162,12 +2129,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 */
@@ -2456,7 +2422,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);
 }
 
@@ -2464,7 +2430,7 @@ static void mvneta_stop_dev(struct mvneta_port *pp)
 {
 	unsigned int cpu;
 
-	phy_stop(pp->phy_dev);
+	phylink_stop(pp->phylink);
 
 	for_each_present_cpu(cpu) {
 		struct mvneta_pcpu_port *port = per_cpu_ptr(pp->ports, cpu);
@@ -2615,99 +2581,218 @@ static int mvneta_set_mac_addr(struct net_device *dev, void *addr)
 	return 0;
 }
 
-static void mvneta_adjust_link(struct net_device *ndev)
+static int mvneta_mac_support(struct net_device *ndev, unsigned int mode,
+			      struct phylink_link_state *state)
+{
+	switch (mode) {
+	case MLO_AN_8023Z:
+		state->supported = SUPPORTED_1000baseT_Full |
+				   SUPPORTED_Autoneg | SUPPORTED_Pause;
+		state->advertising = ADVERTISED_1000baseT_Full |
+				     ADVERTISED_Autoneg | ADVERTISED_Pause;
+		state->an_enabled = 1;
+		break;
+
+	case MLO_AN_FIXED:
+		break;
+
+	default:
+		state->supported = PHY_10BT_FEATURES |
+				   PHY_100BT_FEATURES |
+				   SUPPORTED_1000baseT_Full |
+				   SUPPORTED_Autoneg;
+		state->advertising = ADVERTISED_10baseT_Half |
+				     ADVERTISED_10baseT_Full |
+				     ADVERTISED_100baseT_Half |
+				     ADVERTISED_100baseT_Full |
+				     ADVERTISED_1000baseT_Full |
+				     ADVERTISED_Autoneg;
+		state->an_enabled = 1;
+		break;
+	}
+	return 0;
+}
+
+static int mvneta_mac_link_state(struct net_device *ndev,
+				 struct phylink_link_state *state)
 {
 	struct mvneta_port *pp = netdev_priv(ndev);
-	struct phy_device *phydev = pp->phy_dev;
-	int status_change = 0;
+	u32 gmac_stat;
 
-	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);
+
+		gmac_an |= MVNETA_GMAC_INBAND_RESTART_AN;
+		mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, gmac_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);
+
+	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;
+
+		if (state->advertising & ADVERTISED_Pause)
+			new_an |= MVNETA_GMAC_ADVERT_SYM_FLOW_CTRL;
+
+		if (state->an_enabled)
+			new_an |= MVNETA_GMAC_AN_FLOW_CTRL_EN |
+				  MVNETA_GMAC_AN_DUPLEX_EN;
+		else if (state->duplex)
+			new_an |= MVNETA_GMAC_CONFIG_FULL_DUPLEX;
+		break;
 
-		pp->link = phydev->link;
-		status_change = 1;
+	default:
+		/* Phy or fixed speed */
+		if (state->duplex)
+			new_an |= MVNETA_GMAC_CONFIG_FULL_DUPLEX;
+
+		if (state->speed == SPEED_1000)
+			new_an |= MVNETA_GMAC_CONFIG_GMII_SPEED;
+		else if (state->speed == SPEED_100)
+			new_an |= MVNETA_GMAC_CONFIG_MII_SPEED;
+		break;
 	}
 
-	if (status_change) {
-		if (phydev->link) {
-			if (!pp->use_inband_status) {
-				u32 val = mvreg_read(pp,
-						  MVNETA_GMAC_AUTONEG_CONFIG);
-				val &= ~MVNETA_GMAC_FORCE_LINK_DOWN;
-				val |= MVNETA_GMAC_FORCE_LINK_PASS;
-				mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG,
-					    val);
-			}
-			mvneta_port_up(pp);
-		} else {
-			if (!pp->use_inband_status) {
-				u32 val = mvreg_read(pp,
-						  MVNETA_GMAC_AUTONEG_CONFIG);
-				val &= ~MVNETA_GMAC_FORCE_LINK_PASS;
-				val |= MVNETA_GMAC_FORCE_LINK_DOWN;
-				mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG,
-					    val);
-			}
-			mvneta_port_down(pp);
-		}
-		phy_print_status(phydev);
+	/* Armada 370 documentation says we can only change the port mode
+	 * and in-band enable when the link is down, so force it down
+	 * while making these changes. We also do this for GMAC_CTRL2 */
+	if ((new_ctrl0 ^ gmac_ctrl0) & MVNETA_GMAC0_PORT_1000BASE_X ||
+	    (new_ctrl2 ^ gmac_ctrl2) & MVNETA_GMAC2_INBAND_AN_ENABLE ||
+	    (new_an  ^ gmac_an) & MVNETA_GMAC_INBAND_AN_ENABLE) {
+		mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG,
+			    (gmac_an & ~MVNETA_GMAC_FORCE_LINK_PASS) |
+			    MVNETA_GMAC_FORCE_LINK_DOWN);
 	}
+
+	if (new_ctrl0 != gmac_ctrl0)
+		mvreg_write(pp, MVNETA_GMAC_CTRL_0, new_ctrl0);
+	if (new_ctrl2 != gmac_ctrl2)
+		mvreg_write(pp, MVNETA_GMAC_CTRL_2, new_ctrl2);
+	if (new_clk != gmac_clk)
+		mvreg_write(pp, MVNETA_GMAC_CLOCK_DIVIDER, new_clk);
+	if (new_an != gmac_an)
+		mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, new_an);
 }
 
-static int mvneta_mdio_probe(struct mvneta_port *pp)
+static void mvneta_mac_link_down(struct net_device *ndev, unsigned int mode)
 {
-	struct 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;
+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;
 
-	pp->phy_dev = phy_dev;
-	pp->link    = 0;
-	pp->duplex  = 0;
-	pp->speed   = 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);
+	}
 
-	return 0;
+	mvneta_port_up(pp);
+}
+
+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);
 }
 
 static void mvneta_percpu_enable(void *arg)
@@ -2914,10 +2999,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 */
@@ -2927,10 +3009,7 @@ 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 */
@@ -2938,10 +3017,7 @@ int mvneta_ethtool_set_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_sset(pp->phy_dev, cmd);
+	return phylink_ethtool_set_settings(pp->phylink, cmd);
 }
 
 /* Set interrupt coalescing for ethtools */
@@ -3223,9 +3299,6 @@ static int mvneta_port_power_up(struct mvneta_port *pp, int phy_mode)
 		return -EINVAL;
 	}
 
-	if (pp->use_inband_status)
-		ctrl |= MVNETA_GMAC2_INBAND_AN_ENABLE;
-
 	/* Cancel Port Reset */
 	ctrl &= ~MVNETA_GMAC2_PORT_RESET;
 	mvreg_write(pp, MVNETA_GMAC_CTRL_2, ctrl);
@@ -3243,13 +3316,12 @@ 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 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 phy_mode;
 	int err;
 	int cpu;
@@ -3264,31 +3336,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;
@@ -3298,18 +3350,13 @@ static int mvneta_probe(struct platform_device *pdev)
 	dev->ethtool_ops = &mvneta_eth_tool_ops;
 
 	pp = netdev_priv(dev);
-	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->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);
@@ -3386,6 +3433,14 @@ static int mvneta_probe(struct platform_device *pdev)
 	dev->priv_flags |= IFF_UNICAST_FLT;
 	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");
@@ -3397,24 +3452,16 @@ 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->dev);
-	}
-
 	return 0;
 
 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);
-err_put_phy_node:
-	of_node_put(phy_node);
 err_free_irq:
 	irq_dispose_mapping(dev->irq);
 err_free_netdev:
@@ -3433,7 +3480,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);
 
 	return 0;
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH RFC 16/26] phy: fixed-phy: remove fixed_phy_update_state()
  2015-12-07 17:35 [PATCH RFC 00/26] Phylink & SFP support Russell King - ARM Linux
                   ` (14 preceding siblings ...)
  2015-12-07 17:38 ` [PATCH RFC 15/26] net: mvneta: convert to phylink Russell King
@ 2015-12-07 17:38 ` Russell King
  2016-01-07 20:15   ` Florian Fainelli
  2015-12-07 17:38 ` [PATCH RFC 17/26] phylink: add ethtool nway_reset support Russell King
                   ` (11 subsequent siblings)
  27 siblings, 1 reply; 58+ messages in thread
From: Russell King @ 2015-12-07 17:38 UTC (permalink / raw)
  To: Florian Fainelli, Thomas Petazzoni; +Cc: netdev

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

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
 drivers/net/phy/fixed_phy.c | 31 -------------------------------
 include/linux/phy_fixed.h   |  9 ---------
 2 files changed, 40 deletions(-)

diff --git a/drivers/net/phy/fixed_phy.c b/drivers/net/phy/fixed_phy.c
index 474cc39a5457..19ac47aab0ba 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->bus != fmb->mii_bus)
-		return -EINVAL;
-
-	list_for_each_entry(fp, &fmb->phys, node) {
-		if (fp->addr == phydev->addr) {
-			write_seqcount_begin(&fp->seqcount);
-#define _UPD(x) if (changed->x) \
-	fp->status.x = status->x
-			_UPD(link);
-			_UPD(speed);
-			_UPD(duplex);
-			_UPD(pause);
-			_UPD(asym_pause);
-#undef _UPD
-			fixed_phy_update(fp);
-			write_seqcount_end(&fp->seqcount);
-			return 0;
-		}
-	}
-
-	return -ENOENT;
-}
-EXPORT_SYMBOL(fixed_phy_update_state);
-
 int fixed_phy_add(unsigned int irq, int phy_addr,
 		  struct fixed_phy_status *status,
 		  int link_gpio)
diff --git a/include/linux/phy_fixed.h b/include/linux/phy_fixed.h
index 2400d2ea4f34..cf3f718c62b9 100644
--- a/include/linux/phy_fixed.h
+++ b/include/linux/phy_fixed.h
@@ -23,9 +23,6 @@ extern void fixed_phy_del(int phy_addr);
 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,
@@ -50,12 +47,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 */
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH RFC 17/26] phylink: add ethtool nway_reset support
  2015-12-07 17:35 [PATCH RFC 00/26] Phylink & SFP support Russell King - ARM Linux
                   ` (15 preceding siblings ...)
  2015-12-07 17:38 ` [PATCH RFC 16/26] phy: fixed-phy: remove fixed_phy_update_state() Russell King
@ 2015-12-07 17:38 ` Russell King
  2016-01-07 20:24   ` Florian Fainelli
  2015-12-07 17:38 ` [PATCH RFC 18/26] net: mvneta: add " Russell King
                   ` (10 subsequent siblings)
  27 siblings, 1 reply; 58+ messages in thread
From: Russell King @ 2015-12-07 17:38 UTC (permalink / raw)
  To: Florian Fainelli, Thomas Petazzoni; +Cc: netdev

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

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
 drivers/net/phy/phylink.c | 14 ++++++++++++++
 include/linux/phylink.h   |  1 +
 2 files changed, 15 insertions(+)

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

^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH RFC 18/26] net: mvneta: add nway_reset support
  2015-12-07 17:35 [PATCH RFC 00/26] Phylink & SFP support Russell King - ARM Linux
                   ` (16 preceding siblings ...)
  2015-12-07 17:38 ` [PATCH RFC 17/26] phylink: add ethtool nway_reset support Russell King
@ 2015-12-07 17:38 ` Russell King
  2016-01-07 20:19   ` Florian Fainelli
  2015-12-07 17:38 ` [PATCH RFC 19/26] phylink: add flow control support Russell King
                   ` (9 subsequent siblings)
  27 siblings, 1 reply; 58+ messages in thread
From: Russell King @ 2015-12-07 17:38 UTC (permalink / raw)
  To: Florian Fainelli, Thomas Petazzoni; +Cc: netdev

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 <rmk+kernel@arm.linux.org.uk>
---
 drivers/net/ethernet/marvell/mvneta.c | 8 ++++++++
 1 file changed, 8 insertions(+)

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

^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH RFC 19/26] phylink: add flow control support
  2015-12-07 17:35 [PATCH RFC 00/26] Phylink & SFP support Russell King - ARM Linux
                   ` (17 preceding siblings ...)
  2015-12-07 17:38 ` [PATCH RFC 18/26] net: mvneta: add " Russell King
@ 2015-12-07 17:38 ` Russell King
  2016-01-07 20:25   ` Florian Fainelli
  2015-12-07 17:38 ` [PATCH RFC 20/26] net: mvneta: add flow control support via phylink Russell King
                   ` (8 subsequent siblings)
  27 siblings, 1 reply; 58+ messages in thread
From: Russell King @ 2015-12-07 17:38 UTC (permalink / raw)
  To: Florian Fainelli, Thomas Petazzoni; +Cc: netdev

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 <rmk+kernel@arm.linux.org.uk>
---
 drivers/net/phy/phylink.c | 128 +++++++++++++++++++++++++++++++++++++++++++++-
 include/linux/phylink.h   |   8 +++
 2 files changed, 135 insertions(+), 1 deletion(-)

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

^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH RFC 20/26] net: mvneta: add flow control support via phylink
  2015-12-07 17:35 [PATCH RFC 00/26] Phylink & SFP support Russell King - ARM Linux
                   ` (18 preceding siblings ...)
  2015-12-07 17:38 ` [PATCH RFC 19/26] phylink: add flow control support Russell King
@ 2015-12-07 17:38 ` Russell King
  2016-01-07 20:26   ` Florian Fainelli
  2015-12-07 17:38 ` [PATCH RFC 21/26] net: mvneta: enable flow control for PHY connections Russell King
                   ` (7 subsequent siblings)
  27 siblings, 1 reply; 58+ messages in thread
From: Russell King @ 2015-12-07 17:38 UTC (permalink / raw)
  To: Florian Fainelli, Thomas Petazzoni; +Cc: netdev

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 <rmk+kernel@arm.linux.org.uk>
---
 drivers/net/ethernet/marvell/mvneta.c | 29 +++++++++++++++++++++++++++--
 1 file changed, 27 insertions(+), 2 deletions(-)

diff --git a/drivers/net/ethernet/marvell/mvneta.c b/drivers/net/ethernet/marvell/mvneta.c
index 82aa2b59a249..00cfb120e324 100644
--- a/drivers/net/ethernet/marvell/mvneta.c
+++ b/drivers/net/ethernet/marvell/mvneta.c
@@ -2701,9 +2701,13 @@ static void mvneta_mac_config(struct net_device *ndev, unsigned int mode,
 		if (state->advertising & ADVERTISED_Pause)
 			new_an |= MVNETA_GMAC_ADVERT_SYM_FLOW_CTRL;
 
+		if (state->pause & MLO_PAUSE_AN && state->an_enabled)
+			new_an |= MVNETA_GMAC_AN_FLOW_CTRL_EN;
+		else if (state->pause & MLO_PAUSE_TXRX_MASK)
+			new_an |= MVNETA_GMAC_CONFIG_FLOW_CTRL;
+
 		if (state->an_enabled)
-			new_an |= MVNETA_GMAC_AN_FLOW_CTRL_EN |
-				  MVNETA_GMAC_AN_DUPLEX_EN;
+			new_an |= MVNETA_GMAC_AN_DUPLEX_EN;
 		else if (state->duplex)
 			new_an |= MVNETA_GMAC_CONFIG_FULL_DUPLEX;
 		break;
@@ -2717,6 +2721,9 @@ static void mvneta_mac_config(struct net_device *ndev, unsigned int mode,
 			new_an |= MVNETA_GMAC_CONFIG_GMII_SPEED;
 		else if (state->speed == SPEED_100)
 			new_an |= MVNETA_GMAC_CONFIG_MII_SPEED;
+
+		if (state->pause & MLO_PAUSE_TXRX_MASK)
+			new_an |= MVNETA_GMAC_CONFIG_FLOW_CTRL;
 		break;
 	}
 
@@ -3116,6 +3123,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)
 {
@@ -3197,6 +3220,8 @@ const struct ethtool_ops mvneta_eth_tool_ops = {
 	.get_drvinfo    = mvneta_ethtool_get_drvinfo,
 	.get_ringparam  = mvneta_ethtool_get_ringparam,
 	.set_ringparam	= mvneta_ethtool_set_ringparam,
+	.get_pauseparam	= mvneta_ethtool_get_pauseparam,
+	.set_pauseparam	= mvneta_ethtool_set_pauseparam,
 	.get_strings	= mvneta_ethtool_get_strings,
 	.get_ethtool_stats = mvneta_ethtool_get_stats,
 	.get_sset_count	= mvneta_ethtool_get_sset_count,
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH RFC 21/26] net: mvneta: enable flow control for PHY connections
  2015-12-07 17:35 [PATCH RFC 00/26] Phylink & SFP support Russell King - ARM Linux
                   ` (19 preceding siblings ...)
  2015-12-07 17:38 ` [PATCH RFC 20/26] net: mvneta: add flow control support via phylink Russell King
@ 2015-12-07 17:38 ` Russell King
  2016-01-07 20:31   ` Florian Fainelli
  2015-12-07 17:39 ` [PATCH RFC 22/26] phylink: add EEE support Russell King
                   ` (6 subsequent siblings)
  27 siblings, 1 reply; 58+ messages in thread
From: Russell King @ 2015-12-07 17:38 UTC (permalink / raw)
  To: Florian Fainelli, Thomas Petazzoni; +Cc: netdev

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

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
 drivers/net/ethernet/marvell/mvneta.c | 2 ++
 1 file changed, 2 insertions(+)

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

^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH RFC 22/26] phylink: add EEE support
  2015-12-07 17:35 [PATCH RFC 00/26] Phylink & SFP support Russell King - ARM Linux
                   ` (20 preceding siblings ...)
  2015-12-07 17:38 ` [PATCH RFC 21/26] net: mvneta: enable flow control for PHY connections Russell King
@ 2015-12-07 17:39 ` Russell King
  2016-01-07 20:34   ` Florian Fainelli
  2015-12-07 17:39 ` [PATCH RFC 23/26] net: mvneta: " Russell King
                   ` (5 subsequent siblings)
  27 siblings, 1 reply; 58+ messages in thread
From: Russell King @ 2015-12-07 17:39 UTC (permalink / raw)
  To: Florian Fainelli, Thomas Petazzoni; +Cc: netdev

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

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
 drivers/net/phy/phylink.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++-
 include/linux/phylink.h   |  7 +++++-
 2 files changed, 63 insertions(+), 2 deletions(-)

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

^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH RFC 23/26] net: mvneta: add EEE support
  2015-12-07 17:35 [PATCH RFC 00/26] Phylink & SFP support Russell King - ARM Linux
                   ` (21 preceding siblings ...)
  2015-12-07 17:39 ` [PATCH RFC 22/26] phylink: add EEE support Russell King
@ 2015-12-07 17:39 ` Russell King
  2016-01-07 20:35   ` Florian Fainelli
  2015-12-07 17:39 ` [PATCH RFC 24/26] phylink: add module EEPROM support Russell King
                   ` (4 subsequent siblings)
  27 siblings, 1 reply; 58+ messages in thread
From: Russell King @ 2015-12-07 17:39 UTC (permalink / raw)
  To: Florian Fainelli, Thomas Petazzoni; +Cc: netdev

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 <rmk+kernel@arm.linux.org.uk>
---
 drivers/net/ethernet/marvell/mvneta.c | 87 +++++++++++++++++++++++++++++++++++
 1 file changed, 87 insertions(+)

diff --git a/drivers/net/ethernet/marvell/mvneta.c b/drivers/net/ethernet/marvell/mvneta.c
index 165dfab134b7..3de2aa9335b2 100644
--- a/drivers/net/ethernet/marvell/mvneta.c
+++ b/drivers/net/ethernet/marvell/mvneta.c
@@ -224,6 +224,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 */
@@ -288,6 +294,11 @@
 
 #define MVNETA_RX_BUF_SIZE(pkt_size)   ((pkt_size) + NET_SKB_PAD)
 
+enum {
+	ETHTOOL_STAT_EEE_WAKEUP,
+	ETHTOOL_MAX_STATS,
+};
+
 struct mvneta_statistic {
 	unsigned short offset;
 	unsigned short type;
@@ -296,6 +307,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", },
@@ -330,6 +342,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 {
@@ -373,6 +386,10 @@ struct mvneta_port {
 	unsigned int tx_csum_limit;
 	struct phylink *phylink;
 
+	bool eee_enabled;
+	bool eee_active;
+	bool tx_lpi_enabled;
+
 	u64 ethtool_stats[ARRAY_SIZE(mvneta_statistics)];
 };
 
@@ -2750,6 +2767,18 @@ static void mvneta_mac_config(struct net_device *ndev, unsigned int mode,
 		mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, new_an);
 }
 
+static void mvneta_set_eee(struct mvneta_port *pp, bool enable)
+{
+	u32 lpi_ctl1;
+
+	lpi_ctl1 = mvreg_read(pp, MVNETA_LPI_CTRL_1);
+	if (enable)
+		lpi_ctl1 |= MVNETA_LPI_REQUEST_ENABLE;
+	else
+		lpi_ctl1 &= ~MVNETA_LPI_REQUEST_ENABLE;
+	mvreg_write(pp, MVNETA_LPI_CTRL_1, lpi_ctl1);
+}
+
 static void mvneta_mac_link_down(struct net_device *ndev, unsigned int mode)
 {
 	struct mvneta_port *pp = netdev_priv(ndev);
@@ -2763,6 +2792,9 @@ static void mvneta_mac_link_down(struct net_device *ndev, unsigned int mode)
 		val |= MVNETA_GMAC_FORCE_LINK_DOWN;
 		mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val);
 	}
+
+	pp->eee_active = false;
+	mvneta_set_eee(pp, false);
 }
 
 static void mvneta_mac_link_up(struct net_device *ndev, unsigned int mode,
@@ -2779,6 +2811,11 @@ static void mvneta_mac_link_up(struct net_device *ndev, unsigned int mode,
 	}
 
 	mvneta_port_up(pp);
+
+	if (phy && pp->eee_enabled) {
+		pp->eee_active = phy_init_eee(phy, 0) >= 0;
+		mvneta_set_eee(pp, pp->eee_active && pp->tx_lpi_enabled);
+	}
 }
 
 static const struct phylink_mac_ops mvneta_phylink_ops = {
@@ -3175,6 +3212,13 @@ static void mvneta_ethtool_update_stats(struct mvneta_port *pp)
 			high = readl_relaxed(base + s->offset + 4);
 			val = (u64)high << 32 | low;
 			break;
+		case T_SW:
+			switch (s->offset) {
+			case ETHTOOL_STAT_EEE_WAKEUP:
+				val = phylink_get_eee_err(pp->phylink);
+				break;
+			}
+			break;
 		}
 
 		pp->ethtool_stats[i] += val;
@@ -3200,6 +3244,47 @@ static int mvneta_ethtool_get_sset_count(struct net_device *dev, int sset)
 	return -EOPNOTSUPP;
 }
 
+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,
@@ -3227,6 +3312,8 @@ const struct ethtool_ops mvneta_eth_tool_ops = {
 	.get_strings	= mvneta_ethtool_get_strings,
 	.get_ethtool_stats = mvneta_ethtool_get_stats,
 	.get_sset_count	= mvneta_ethtool_get_sset_count,
+	.get_eee	= mvneta_ethtool_get_eee,
+	.set_eee	= mvneta_ethtool_set_eee,
 };
 
 /* Initialize hw */
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH RFC 24/26] phylink: add module EEPROM support
  2015-12-07 17:35 [PATCH RFC 00/26] Phylink & SFP support Russell King - ARM Linux
                   ` (22 preceding siblings ...)
  2015-12-07 17:39 ` [PATCH RFC 23/26] net: mvneta: " Russell King
@ 2015-12-07 17:39 ` Russell King
  2016-01-07 20:36   ` Florian Fainelli
  2015-12-07 17:39 ` [PATCH RFC 25/26] net: mvneta: add module EEPROM reading support Russell King
                   ` (3 subsequent siblings)
  27 siblings, 1 reply; 58+ messages in thread
From: Russell King @ 2015-12-07 17:39 UTC (permalink / raw)
  To: Florian Fainelli, Thomas Petazzoni; +Cc: netdev

Add support for reading module EEPROMs through phylink.

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
 drivers/net/phy/phylink.c | 66 +++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/phylink.h   | 12 +++++++++
 2 files changed, 78 insertions(+)

diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index 82492db0fd04..cd56d454c587 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -60,6 +60,9 @@ struct phylink {
 	struct work_struct resolve;
 
 	bool mac_link_up;
+
+	const struct phylink_module_ops *module_ops;
+	void *module_data;
 };
 
 static const char *phylink_an_mode_str(unsigned int mode)
@@ -800,6 +803,36 @@ int phylink_ethtool_set_pauseparam(struct phylink *pl,
 }
 EXPORT_SYMBOL_GPL(phylink_ethtool_set_pauseparam);
 
+int phylink_ethtool_get_module_info(struct phylink *pl,
+				    struct ethtool_modinfo *modinfo)
+{
+	int ret = -EOPNOTSUPP;
+
+	mutex_lock(&pl->config_mutex);
+	if (pl->module_ops)
+		ret = pl->module_ops->get_module_info(pl->module_data,
+						      modinfo);
+	mutex_unlock(&pl->config_mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(phylink_ethtool_get_module_info);
+
+int phylink_ethtool_get_module_eeprom(struct phylink *pl,
+				      struct ethtool_eeprom *ee, u8 *buf)
+{
+	int ret = -EOPNOTSUPP;
+
+	mutex_lock(&pl->config_mutex);
+	if (pl->module_ops)
+		ret = pl->module_ops->get_module_eeprom(pl->module_data, ee,
+							buf);
+	mutex_unlock(&pl->config_mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(phylink_ethtool_get_module_eeprom);
+
 int phylink_init_eee(struct phylink *pl, bool clk_stop_enable)
 {
 	int ret = -EPROTONOSUPPORT;
@@ -996,6 +1029,39 @@ EXPORT_SYMBOL_GPL(phylink_mii_ioctl);
 
 
 
+int phylink_register_module(struct phylink *pl, void *data,
+			    const struct phylink_module_ops *ops)
+{
+	int ret = -EBUSY;
+
+	mutex_lock(&pl->config_mutex);
+	if (!pl->module_ops) {
+		pl->module_ops = ops;
+		pl->module_data = data;
+		ret = 0;
+	}
+	mutex_unlock(&pl->config_mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(phylink_register_module);
+
+int phylink_unregister_module(struct phylink *pl, void *data)
+{
+	int ret = -EINVAL;
+
+	mutex_lock(&pl->config_mutex);
+	if (pl->module_data == data) {
+		pl->module_ops = NULL;
+		pl->module_data = NULL;
+		ret = 0;
+	}
+	mutex_unlock(&pl->config_mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(phylink_unregister_module);
+
 void phylink_disable(struct phylink *pl)
 {
 	set_bit(PHYLINK_DISABLE_LINK, &pl->phylink_disable_state);
diff --git a/include/linux/phylink.h b/include/linux/phylink.h
index 361fbe9222b2..01d442b08e62 100644
--- a/include/linux/phylink.h
+++ b/include/linux/phylink.h
@@ -55,6 +55,11 @@ struct phylink_mac_ops {
 			    struct phy_device *);
 };
 
+struct phylink_module_ops {
+	int (*get_module_info)(void *, struct ethtool_modinfo *);
+	int (*get_module_eeprom)(void *, struct ethtool_eeprom *, u8 *);
+};
+
 struct phylink *phylink_create(struct net_device *, struct device_node *,
 	phy_interface_t iface, const struct phylink_mac_ops *ops);
 void phylink_destroy(struct phylink *);
@@ -75,12 +80,19 @@ void phylink_ethtool_get_pauseparam(struct phylink *,
 				    struct ethtool_pauseparam *);
 int phylink_ethtool_set_pauseparam(struct phylink *,
 				   struct ethtool_pauseparam *);
+int phylink_ethtool_get_module_info(struct phylink *, struct ethtool_modinfo *);
+int phylink_ethtool_get_module_eeprom(struct phylink *,
+				      struct ethtool_eeprom *, u8 *);
 int phylink_init_eee(struct phylink *, bool);
 int phylink_get_eee_err(struct phylink *);
 int phylink_ethtool_get_eee(struct phylink *, struct ethtool_eee *);
 int phylink_ethtool_set_eee(struct phylink *, struct ethtool_eee *);
 int phylink_mii_ioctl(struct phylink *, struct ifreq *, int);
 
+int phylink_register_module(struct phylink *, void *,
+			    const struct phylink_module_ops *);
+int phylink_unregister_module(struct phylink *, void *);
+
 void phylink_set_link_port(struct phylink *pl, u32 support, u8 port);
 int phylink_set_link_an_mode(struct phylink *pl, unsigned int mode);
 void phylink_disable(struct phylink *pl);
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH RFC 25/26] net: mvneta: add module EEPROM reading support
  2015-12-07 17:35 [PATCH RFC 00/26] Phylink & SFP support Russell King - ARM Linux
                   ` (23 preceding siblings ...)
  2015-12-07 17:39 ` [PATCH RFC 24/26] phylink: add module EEPROM support Russell King
@ 2015-12-07 17:39 ` Russell King
  2016-01-07 20:36   ` Florian Fainelli
  2015-12-07 17:39 ` [PATCH RFC 26/26] sfp/phylink: hook up eeprom functions Russell King
                   ` (2 subsequent siblings)
  27 siblings, 1 reply; 58+ messages in thread
From: Russell King @ 2015-12-07 17:39 UTC (permalink / raw)
  To: Florian Fainelli, Thomas Petazzoni; +Cc: netdev

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
 drivers/net/ethernet/marvell/mvneta.c | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/drivers/net/ethernet/marvell/mvneta.c b/drivers/net/ethernet/marvell/mvneta.c
index 3de2aa9335b2..bf0b03f58dae 100644
--- a/drivers/net/ethernet/marvell/mvneta.c
+++ b/drivers/net/ethernet/marvell/mvneta.c
@@ -3244,6 +3244,22 @@ static int mvneta_ethtool_get_sset_count(struct net_device *dev, int sset)
 	return -EOPNOTSUPP;
 }
 
+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)
 {
@@ -3312,6 +3328,8 @@ const struct ethtool_ops mvneta_eth_tool_ops = {
 	.get_strings	= mvneta_ethtool_get_strings,
 	.get_ethtool_stats = mvneta_ethtool_get_stats,
 	.get_sset_count	= mvneta_ethtool_get_sset_count,
+	.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,
 };
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 58+ messages in thread

* [PATCH RFC 26/26] sfp/phylink: hook up eeprom functions
  2015-12-07 17:35 [PATCH RFC 00/26] Phylink & SFP support Russell King - ARM Linux
                   ` (24 preceding siblings ...)
  2015-12-07 17:39 ` [PATCH RFC 25/26] net: mvneta: add module EEPROM reading support Russell King
@ 2015-12-07 17:39 ` Russell King
  2015-12-15  7:26 ` [PATCH RFC 00/26] Phylink & SFP support Dustin Byford
  2015-12-28  1:56 ` Florian Fainelli
  27 siblings, 0 replies; 58+ messages in thread
From: Russell King @ 2015-12-07 17:39 UTC (permalink / raw)
  To: Florian Fainelli, Thomas Petazzoni; +Cc: netdev

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
---
 drivers/net/phy/sfp.c | 19 +++++++++++--------
 1 file changed, 11 insertions(+), 8 deletions(-)

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

^ permalink raw reply related	[flat|nested] 58+ messages in thread

* Re: [PATCH RFC 10/26] phy: add I2C mdio bus
  2015-12-07 17:38 ` [PATCH RFC 10/26] phy: add I2C mdio bus Russell King
@ 2015-12-08 18:15   ` Florian Fainelli
  2015-12-11 10:25     ` Russell King - ARM Linux
  0 siblings, 1 reply; 58+ messages in thread
From: Florian Fainelli @ 2015-12-08 18:15 UTC (permalink / raw)
  To: Russell King, Thomas Petazzoni; +Cc: netdev

On 07/12/15 09:38, Russell King wrote:
> 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.
> 
> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
> ---
[snip]
> +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;
> +
> +	bus_addr = 0x40 + phy_id;
> +	if (bus_addr == 0x50 || bus_addr == 0x51)
> +		return 0xffff;

These could deserve a local definition for these specific addresses so
we know why there is such logic here.

> +
> +	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 bus_addr, ret;
> +	u8 data[3];
> +
> +	bus_addr = 0x40 + phy_id;
> +	if (bus_addr == 0x50 || bus_addr == 0x51)
> +		return 0;

Same here.
-- 
Florian

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH RFC 10/26] phy: add I2C mdio bus
  2015-12-08 18:15   ` Florian Fainelli
@ 2015-12-11 10:25     ` Russell King - ARM Linux
  0 siblings, 0 replies; 58+ messages in thread
From: Russell King - ARM Linux @ 2015-12-11 10:25 UTC (permalink / raw)
  To: Florian Fainelli; +Cc: Thomas Petazzoni, netdev

On Tue, Dec 08, 2015 at 10:15:08AM -0800, Florian Fainelli wrote:
> On 07/12/15 09:38, Russell King wrote:
> > 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.
> > 
> > Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
> > ---
> [snip]
> > +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;
> > +
> > +	bus_addr = 0x40 + phy_id;
> > +	if (bus_addr == 0x50 || bus_addr == 0x51)
> > +		return 0xffff;
> 
> These could deserve a local definition for these specific addresses so
> we know why there is such logic here.

This is the only comment I've received on this series so far; I'm
waiting for further comments before addressing it.

-- 
FTTC broadband for 0.8mile line: currently at 9.6Mbps down 400kbps up
according to speedtest.net.

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH RFC 00/26] Phylink & SFP support
  2015-12-07 17:35 [PATCH RFC 00/26] Phylink & SFP support Russell King - ARM Linux
                   ` (25 preceding siblings ...)
  2015-12-07 17:39 ` [PATCH RFC 26/26] sfp/phylink: hook up eeprom functions Russell King
@ 2015-12-15  7:26 ` Dustin Byford
  2015-12-28  2:08   ` Florian Fainelli
  2015-12-28  1:56 ` Florian Fainelli
  27 siblings, 1 reply; 58+ messages in thread
From: Dustin Byford @ 2015-12-15  7:26 UTC (permalink / raw)
  To: Russell King - ARM Linux; +Cc: Florian Fainelli, Thomas Petazzoni, netdev

On Mon Dec 07 17:35, Russell King - ARM Linux wrote:
> Hi,

Hello.

> SFP modules are hot-pluggable ethernet transceivers; they can be
> detected at runtime and accordingly configured.  There are a range of
> modules offering many different features.
> 
> Some SFP modules have PHYs conventional integrated into them, others
> drive a laser diode from the Serdes bus.  Some have monitoring, others
> do not.
> 
> Some SFP modules want to use SGMII over the Serdes link, others want
> to use 1000base-X over the Serdes link.
> 
> This makes it non-trivial to support with the existing code structure.
> Not wanting to write something specific to the mvneta driver, I decided
> to have a go at coming up with something more generic.
> 
> My initial attempts were to provide a PHY driver, but I found that
> phylib's state machine got in the way, and it was hard to support two
> chained PHYs.  Conversely, having a fixed DT specified setup (via
> the fixed phy infrastructure) would allow some SFP modules to work, but
> not others.  The same is true of the "managed" in-band status (which
> is SGMII.)
> 
> The result is that I came up with phylink - an infrastructure layer
> which sits between the network driver and any attached PHY, and a
> SFP module layer detects the SFP module, and configures phylink
> accordingly.
> 
> Overall, this supports:
> 
> * switching the serdes mode at the NIC driver
> * controlling autonegotiation and autoneg results
> * allowing PHYs to be hotplugged
> * allowing SFP modules to be hotplugged with proper link indication
> * fixed-mode links without involving phylib
> * flow control
> * EEE support
> * reading SFP module EEPROMs
> 
> Overall, phylink supports several link modes, with dynamic switching
> possible between these:
> * A true fixed link mode, where the parameters are set by DT.
> * PHY mode, where we read the negotiation results from the PHY registers
>   and pass them to the NIC driver.
> * SGMII mode, where the in-band status indicates the speed, duplex and
>   flow control settings of the link partner.
> * 1000base-X mode, where the in-band status indicates only duplex and
>   flow control settings (different, incompatible bit layout from SGMII.)

I've been working on some similar code to handle interactions with a
wide range of SFF modules, 1G to 100G, on Linux network switches for
some time.  For practical reasons a lot of that was in userspace but
I've been planning and recently working on an SFF kernel driver that
does some of what's done in this series.  I think the model you're
proposing is right on, and since you're further along in implementation
I'd like to help round out support for the other SFF modules if I can.
Then make this work on the network ASICs I have access to.

Any concrete plans for QSFP or the new 25G modules?

> Ethtool support is included, as well as emulation of the MII registers
> for situations where a PHY is not attached, giving compatible emulation
> of existing user interfaces where required.
> 
> The patches here include modification of mvneta (against 4.4-rc1, so
> probably won't apply to current development tips.)  It basically
> hooks into the places where the phylib would hook into.
> 
> DT wise, the changes needed to support SFP look like this (example
> taken from Clearfog):
> 
>  			ethernet@34000 {
> +				managed = "in-band-status";
>  				phy-mode = "sgmii";
>  				status = "okay";
> -
> -				fixed-link {
> -					speed = <1000>;
> -					full-duplex;
> -				};
>  			};
> ...
> +	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>;

Using &eth2 is unambiguous in the this case because there's only one
serdes and one mac involved.  To specify the mac/serdes/cage
associations at the same level of detail as the gpios it might be nice
(at least for some devices) to point to a serdes node (or 4 in the case
of QSFP) instead of &eth2.  Any thoughts on that?

Switch ASICs, and I imagine at least some NICs, are really flexible in
terms of how serdes are wired to a cage.  Both in the sense that the
board designer gets to pick which wires route to the cage based on
physical constraints and the user gets to pick which serdes or group of
serdes compose the ethernet device.  For example, using a breakout cable
to get 4xSFP out of a QSFP or the other way around.

Perhaps the simple case (sfp,ethernet -> &eth2) can remain simple, but
I'd be interested in any thoughts you have on introducing a serdes
layer here.

I think adding such a layer would make it easier to 1) make serdes to
cage mappings part of the platform description (DT or ACPI) and 2) allow
automatic reconfiguration of the mac based on the SFF module.  For
example, if a user plugs in a QSFP->4xSFP breakout cable why not
automatically create four netdevs instead of one?

> +		tx-disable-gpio = <&expander0 14 GPIO_ACTIVE_HIGH>;
> +		tx-fault-gpio = <&expander0 13 GPIO_ACTIVE_HIGH>;
> +	};
> 
> These DT changes are omitted from this patch set as the baseline DT
> file is not in mainline yet (has been submitted.)

Cool.  Do you have a link to the DT patches?


In short, I think this is awesome, and I'd like to help where I can.
I'll start by having a look at the rest of the series.  I'd like to
apply it and see if I can make it work on one of my systems.

Thanks,

		--Dustin

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH RFC 00/26] Phylink & SFP support
  2015-12-07 17:35 [PATCH RFC 00/26] Phylink & SFP support Russell King - ARM Linux
                   ` (26 preceding siblings ...)
  2015-12-15  7:26 ` [PATCH RFC 00/26] Phylink & SFP support Dustin Byford
@ 2015-12-28  1:56 ` Florian Fainelli
  27 siblings, 0 replies; 58+ messages in thread
From: Florian Fainelli @ 2015-12-28  1:56 UTC (permalink / raw)
  To: Russell King - ARM Linux, Thomas Petazzoni; +Cc: netdev

On December 7, 2015 9:35:53 AM PST, Russell King - ARM Linux <linux@arm.linux.org.uk> wrote:
>
>Overall, phylink supports several link modes, with dynamic switching
>possible between these:
>* A true fixed link mode, where the parameters are set by DT.
>* PHY mode, where we read the negotiation results from the PHY
>registers
>  and pass them to the NIC driver.
>* SGMII mode, where the in-band status indicates the speed, duplex and
>  flow control settings of the link partner.
>* 1000base-X mode, where the in-band status indicates only duplex and
> flow control settings (different, incompatible bit layout from SGMII.)
>
>Ethtool support is included, as well as emulation of the MII registers
>for situations where a PHY is not attached, giving compatible emulation
>of existing user interfaces where required.

This is looking really nice at first glance, I will provide feedback on each patches on the next few days, thanks!
Hello,
-- 
Florian

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH RFC 00/26] Phylink & SFP support
  2015-12-15  7:26 ` [PATCH RFC 00/26] Phylink & SFP support Dustin Byford
@ 2015-12-28  2:08   ` Florian Fainelli
  2015-12-28 23:39     ` Dustin Byford
  0 siblings, 1 reply; 58+ messages in thread
From: Florian Fainelli @ 2015-12-28  2:08 UTC (permalink / raw)
  To: Dustin Byford, Russell King - ARM Linux; +Cc: Thomas Petazzoni, netdev

On December 14, 2015 11:26:21 PM PST, Dustin Byford <dustin@cumulusnetworks.com> wrote:
>On Mon Dec 07 17:35, Russell King - ARM Linux wrote:
>> Hi,
>
>Hello.
>
>> SFP modules are hot-pluggable ethernet transceivers; they can be
>> detected at runtime and accordingly configured.  There are a range of
>> modules offering many different features.
>> 
>> Some SFP modules have PHYs conventional integrated into them, others
>> drive a laser diode from the Serdes bus.  Some have monitoring,
>others
>> do not.
>> 
>> Some SFP modules want to use SGMII over the Serdes link, others want
>> to use 1000base-X over the Serdes link.
>> 
>> This makes it non-trivial to support with the existing code
>structure.
>> Not wanting to write something specific to the mvneta driver, I
>decided
>> to have a go at coming up with something more generic.
>> 
>> My initial attempts were to provide a PHY driver, but I found that
>> phylib's state machine got in the way, and it was hard to support two
>> chained PHYs.  Conversely, having a fixed DT specified setup (via
>> the fixed phy infrastructure) would allow some SFP modules to work,
>but
>> not others.  The same is true of the "managed" in-band status (which
>> is SGMII.)
>> 
>> The result is that I came up with phylink - an infrastructure layer
>> which sits between the network driver and any attached PHY, and a
>> SFP module layer detects the SFP module, and configures phylink
>> accordingly.
>> 
>> Overall, this supports:
>> 
>> * switching the serdes mode at the NIC driver
>> * controlling autonegotiation and autoneg results
>> * allowing PHYs to be hotplugged
>> * allowing SFP modules to be hotplugged with proper link indication
>> * fixed-mode links without involving phylib
>> * flow control
>> * EEE support
>> * reading SFP module EEPROMs
>> 
>> Overall, phylink supports several link modes, with dynamic switching
>> possible between these:
>> * A true fixed link mode, where the parameters are set by DT.
>> * PHY mode, where we read the negotiation results from the PHY
>registers
>>   and pass them to the NIC driver.
>> * SGMII mode, where the in-band status indicates the speed, duplex
>and
>>   flow control settings of the link partner.
>> * 1000base-X mode, where the in-band status indicates only duplex and
>>   flow control settings (different, incompatible bit layout from
>SGMII.)
>
>I've been working on some similar code to handle interactions with a
>wide range of SFF modules, 1G to 100G, on Linux network switches for
>some time.  For practical reasons a lot of that was in userspace but
>I've been planning and recently working on an SFF kernel driver that
>does some of what's done in this series.  I think the model you're
>proposing is right on, and since you're further along in implementation
>I'd like to help round out support for the other SFF modules if I can.
>Then make this work on the network ASICs I have access to.
>
>Any concrete plans for QSFP or the new 25G modules?
>
>> Ethtool support is included, as well as emulation of the MII
>registers
>> for situations where a PHY is not attached, giving compatible
>emulation
>> of existing user interfaces where required.
>> 
>> The patches here include modification of mvneta (against 4.4-rc1, so
>> probably won't apply to current development tips.)  It basically
>> hooks into the places where the phylib would hook into.
>> 
>> DT wise, the changes needed to support SFP look like this (example
>> taken from Clearfog):
>> 
>>  			ethernet@34000 {
>> +				managed = "in-band-status";
>>  				phy-mode = "sgmii";
>>  				status = "okay";
>> -
>> -				fixed-link {
>> -					speed = <1000>;
>> -					full-duplex;
>> -				};
>>  			};
>> ...
>> +	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>;
>
>Using &eth2 is unambiguous in the this case because there's only one
>serdes and one mac involved.  To specify the mac/serdes/cage
>associations at the same level of detail as the gpios it might be nice
>(at least for some devices) to point to a serdes node (or 4 in the case
>of QSFP) instead of &eth2.  Any thoughts on that?

Using a phandle here allows for quite a lot of flexibility on how you want to associate a given SFP to its data plane partner. I do not think we need to get more strict than that strictly mandate an actual Ethernet controller node. These Marvell adapters typically have one or more " ports", each of them being backed by a netdev. The same could be true with a switch properly modeled.

>Switch ASICs, and I imagine at least some NICs, are really flexible in
>terms of how serdes are wired to a cage.  Both in the sense that the
>board designer gets to pick which wires route to the cage based on
>physical constraints and the user gets to pick which serdes or group of
>serdes compose the ethernet device.  For example, using a breakout
>cable
>to get 4xSFP out of a QSFP or the other way around.
>
>Perhaps the simple case (sfp,ethernet -> &eth2) can remain simple, but
>I'd be interested in any thoughts you have on introducing a serdes
>layer here.
>
>I think adding such a layer would make it easier to 1) make serdes to
>cage mappings part of the platform description (DT or ACPI) and 2)
>allow
>automatic reconfiguration of the mac based on the SFF module.  For
>example, if a user plugs in a QSFP->4xSFP breakout cable why not
>automatically create four netdevs instead of one?

Would this be something you expect to happen dynamically? Not that this does not seem reasonable but would these netdevs serve a different purpose than being control endpoints, or would they become real logical netdevs with separate data planes at the MAC they would be linked to?

>
>> +		tx-disable-gpio = <&expander0 14 GPIO_ACTIVE_HIGH>;
>> +		tx-fault-gpio = <&expander0 13 GPIO_ACTIVE_HIGH>;
>> +	};
>> 
>> These DT changes are omitted from this patch set as the baseline DT
>> file is not in mainline yet (has been submitted.)
>
>Cool.  Do you have a link to the DT patches?
>
>
>In short, I think this is awesome, and I'd like to help where I can.
>I'll start by having a look at the rest of the series.  I'd like to
>apply it and see if I can make it work on one of my systems.
>
>Thanks,
>
>		--Dustin


-- 
Florian

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH RFC 00/26] Phylink & SFP support
  2015-12-28  2:08   ` Florian Fainelli
@ 2015-12-28 23:39     ` Dustin Byford
  2016-01-07 20:42       ` Florian Fainelli
  0 siblings, 1 reply; 58+ messages in thread
From: Dustin Byford @ 2015-12-28 23:39 UTC (permalink / raw)
  To: Florian Fainelli; +Cc: Russell King - ARM Linux, Thomas Petazzoni, netdev

Hi Florian,

On Sun Dec 27 18:08, Florian Fainelli wrote:
> On December 14, 2015 11:26:21 PM PST, Dustin Byford <dustin@cumulusnetworks.com> wrote:
> >On Mon Dec 07 17:35, Russell King - ARM Linux wrote:
> >> Hi,
> >
> >Hello.
> >
> >> SFP modules are hot-pluggable ethernet transceivers; they can be
> >> detected at runtime and accordingly configured.  There are a range of
> >> modules offering many different features.
> >> 
> >> Some SFP modules have PHYs conventional integrated into them, others
> >> drive a laser diode from the Serdes bus.  Some have monitoring,
> >others
> >> do not.
> >> 
> >> Some SFP modules want to use SGMII over the Serdes link, others want
> >> to use 1000base-X over the Serdes link.
> >> 
> >> This makes it non-trivial to support with the existing code
> >structure.
> >> Not wanting to write something specific to the mvneta driver, I
> >decided
> >> to have a go at coming up with something more generic.
> >> 
> >> My initial attempts were to provide a PHY driver, but I found that
> >> phylib's state machine got in the way, and it was hard to support two
> >> chained PHYs.  Conversely, having a fixed DT specified setup (via
> >> the fixed phy infrastructure) would allow some SFP modules to work,
> >but
> >> not others.  The same is true of the "managed" in-band status (which
> >> is SGMII.)
> >> 
> >> The result is that I came up with phylink - an infrastructure layer
> >> which sits between the network driver and any attached PHY, and a
> >> SFP module layer detects the SFP module, and configures phylink
> >> accordingly.
> >> 
> >> Overall, this supports:
> >> 
> >> * switching the serdes mode at the NIC driver
> >> * controlling autonegotiation and autoneg results
> >> * allowing PHYs to be hotplugged
> >> * allowing SFP modules to be hotplugged with proper link indication
> >> * fixed-mode links without involving phylib
> >> * flow control
> >> * EEE support
> >> * reading SFP module EEPROMs
> >> 
> >> Overall, phylink supports several link modes, with dynamic switching
> >> possible between these:
> >> * A true fixed link mode, where the parameters are set by DT.
> >> * PHY mode, where we read the negotiation results from the PHY
> >registers
> >>   and pass them to the NIC driver.
> >> * SGMII mode, where the in-band status indicates the speed, duplex
> >and
> >>   flow control settings of the link partner.
> >> * 1000base-X mode, where the in-band status indicates only duplex and
> >>   flow control settings (different, incompatible bit layout from
> >SGMII.)
> >
> >I've been working on some similar code to handle interactions with a
> >wide range of SFF modules, 1G to 100G, on Linux network switches for
> >some time.  For practical reasons a lot of that was in userspace but
> >I've been planning and recently working on an SFF kernel driver that
> >does some of what's done in this series.  I think the model you're
> >proposing is right on, and since you're further along in implementation
> >I'd like to help round out support for the other SFF modules if I can.
> >Then make this work on the network ASICs I have access to.
> >
> >Any concrete plans for QSFP or the new 25G modules?
> >
> >> Ethtool support is included, as well as emulation of the MII
> >registers
> >> for situations where a PHY is not attached, giving compatible
> >emulation
> >> of existing user interfaces where required.
> >> 
> >> The patches here include modification of mvneta (against 4.4-rc1, so
> >> probably won't apply to current development tips.)  It basically
> >> hooks into the places where the phylib would hook into.
> >> 
> >> DT wise, the changes needed to support SFP look like this (example
> >> taken from Clearfog):
> >> 
> >>  			ethernet@34000 {
> >> +				managed = "in-band-status";
> >>  				phy-mode = "sgmii";
> >>  				status = "okay";
> >> -
> >> -				fixed-link {
> >> -					speed = <1000>;
> >> -					full-duplex;
> >> -				};
> >>  			};
> >> ...
> >> +	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>;
> >
> >Using &eth2 is unambiguous in the this case because there's only one
> >serdes and one mac involved.  To specify the mac/serdes/cage
> >associations at the same level of detail as the gpios it might be nice
> >(at least for some devices) to point to a serdes node (or 4 in the case
> >of QSFP) instead of &eth2.  Any thoughts on that?
> 
> Using a phandle here allows for quite a lot of flexibility on how you want to associate a given SFP to its data plane partner. I do not think we need to get more strict than that strictly mandate an actual Ethernet controller node. These Marvell adapters typically have one or more " ports", each of them being backed by a netdev. The same could be true with a switch properly modeled.

On a switch though, the number of "ports" is often configurable.
Physically the q/sfp cage to ASIC wiring is fixed, but when you've
plugged in a breakout cable you get four "ports" for a single QSFP cage.
They act like four separate devices in most ways, the notable exception
is that they share a QSFP module eeprom and the discrete IOs to the cage
like "reset" and "interrupt"  At the MAC layer, each port gets an
independent set of resources and they act like separate netdevs.

A concrete proposal might be to add a "channel" or "lane" parameter to
sfp,ethernet with a default of 0.

sfp,ethernet = <&eth2>

is equivalent to:
sfp,ethernet = <&eth2 0>


SFP on a switch0 device with 128 channels:

sfp,ethernet = <&switch0 42>
consumes channel 42

qsfp,ethernet = <&switch0>
consumes channels 0-3

qsfp,ethernet = <&switch0 124 125 126 127>
consumes channels 124-127

alternatives:

(less explicit, assume adjacent channels)
qsfp,ethernet = <&switch0 124> // consumes 124-127

(more explicit, don't assume the same device)
qsfp,ethernet = <&switch0 124 &switch0 125 &switch0 126 &switch0 127>
or:
qsfp,ethernet0 = <&switch0 124>
qsfp,ethernet1 = <&switch0 125>
qsfp,ethernet2 = <&switch0 126>
qsfp,ethernet3 = <&switch0 127>

(move complexity to the NIC/ASIC, ensure one channel per handle on the
NIC/ASIC side)
qsfp,ethernet = <&switch0c124 &switch0c125 &switch0c126 &switch0c127>


> >Switch ASICs, and I imagine at least some NICs, are really flexible in
> >terms of how serdes are wired to a cage.  Both in the sense that the
> >board designer gets to pick which wires route to the cage based on
> >physical constraints and the user gets to pick which serdes or group of
> >serdes compose the ethernet device.  For example, using a breakout
> >cable
> >to get 4xSFP out of a QSFP or the other way around.
> >
> >Perhaps the simple case (sfp,ethernet -> &eth2) can remain simple, but
> >I'd be interested in any thoughts you have on introducing a serdes
> >layer here.
> >
> >I think adding such a layer would make it easier to 1) make serdes to
> >cage mappings part of the platform description (DT or ACPI) and 2)
> >allow
> >automatic reconfiguration of the mac based on the SFF module.  For
> >example, if a user plugs in a QSFP->4xSFP breakout cable why not
> >automatically create four netdevs instead of one?
> 
> Would this be something you expect to happen dynamically? Not that this does not seem reasonable but would these netdevs serve a different purpose than being control endpoints, or would they become real logical netdevs with separate data planes at the MAC they would be linked to?

Real netdevs with separate data planes.  Reconfiguring them dynamically
seems like a good theoretical goal but is probably impractical in most
cases.  Even if it's not dynamic I think it's a good example of why you
might want a QSFP device to have an ethernet handle that points to four
things instead of one.

> >> +		tx-disable-gpio = <&expander0 14 GPIO_ACTIVE_HIGH>;
> >> +		tx-fault-gpio = <&expander0 13 GPIO_ACTIVE_HIGH>;
> >> +	};
> >> 
> >> These DT changes are omitted from this patch set as the baseline DT
> >> file is not in mainline yet (has been submitted.)
> >
> >Cool.  Do you have a link to the DT patches?
> >
> >
> >In short, I think this is awesome, and I'd like to help where I can.
> >I'll start by having a look at the rest of the series.  I'd like to
> >apply it and see if I can make it work on one of my systems.
> >
> >Thanks,
> >
> >		--Dustin
> 
> 
> -- 
> Florian


		--Dustin

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH RFC 01/26] phy: move fixed_phy MII register generation to a library
  2015-12-07 17:37 ` [PATCH RFC 01/26] phy: move fixed_phy MII register generation to a library Russell King
@ 2016-01-07 19:47   ` Florian Fainelli
  0 siblings, 0 replies; 58+ messages in thread
From: Florian Fainelli @ 2016-01-07 19:47 UTC (permalink / raw)
  To: Russell King, Thomas Petazzoni; +Cc: netdev

On 07/12/15 09:37, Russell King wrote:
> Move the fixed_phy MII register generation to a library to allow other
> software phy implementations to use this code.
> 
> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>

Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-- 
Florian

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH RFC 02/26] phy: convert swphy register generation to tabular form
  2015-12-07 17:37 ` [PATCH RFC 02/26] phy: convert swphy register generation to tabular form Russell King
@ 2016-01-07 19:47   ` Florian Fainelli
  0 siblings, 0 replies; 58+ messages in thread
From: Florian Fainelli @ 2016-01-07 19:47 UTC (permalink / raw)
  To: Russell King, Thomas Petazzoni; +Cc: netdev

On 07/12/15 09:37, Russell King wrote:
> Convert the swphy register generation to tabular form which allows us
> to eliminate multiple switch() statements.  This results in a smaller
> object code size, more efficient, and easier to add support for faster
> speeds.
> 
> Before:
> 
> Idx Name          Size      VMA       LMA       File off  Algn
>   0 .text         00000164  00000000  00000000  00000034  2**2
> 
>    text    data     bss     dec     hex filename
>     388       0       0     388     184 swphy.o
> 
> After:
> 
> Idx Name          Size      VMA       LMA       File off  Algn
>   0 .text         000000fc  00000000  00000000  00000034  2**2
>   5 .rodata       00000028  00000000  00000000  00000138  2**2
> 
>    text    data     bss     dec     hex filename
>     324       0       0     324     144 swphy.o
> 
> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>

Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-- 
Florian

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH RFC 03/26] phy: separate swphy state validation from register generation
  2015-12-07 17:37 ` [PATCH RFC 03/26] phy: separate swphy state validation from register generation Russell King
@ 2016-01-07 19:48   ` Florian Fainelli
  0 siblings, 0 replies; 58+ messages in thread
From: Florian Fainelli @ 2016-01-07 19:48 UTC (permalink / raw)
  To: Russell King, Thomas Petazzoni; +Cc: netdev

On 07/12/15 09:37, Russell King wrote:
> Separate out the generation of MII registers from the state validation.
> This allows us to simplify the error handing in fixed_phy() by allowing
> earlier error detection.
> 
> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>

Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-- 
Florian

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH RFC 04/26] phy: generate swphy registers on the fly
  2015-12-07 17:37 ` [PATCH RFC 04/26] phy: generate swphy registers on the fly Russell King
@ 2016-01-07 19:49   ` Florian Fainelli
  0 siblings, 0 replies; 58+ messages in thread
From: Florian Fainelli @ 2016-01-07 19:49 UTC (permalink / raw)
  To: Russell King, Thomas Petazzoni; +Cc: netdev

On 07/12/15 09:37, Russell King wrote:
> Generate software phy registers as and when requested, rather than
> duplicating the state in fixed_phy.  This allows us to eliminate
> the duplicate storage of of the same data, which is only different
> in format.
> 
> As fixed_phy_update_regs() no longer updates register state, rename
> it to fixed_phy_update().
> 
> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>

Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-- 
Florian

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH RFC 05/26] phy: improve safety of fixed-phy MII register reading
  2015-12-07 17:37 ` [PATCH RFC 05/26] phy: improve safety of fixed-phy MII register reading Russell King
@ 2016-01-07 19:50   ` Florian Fainelli
  0 siblings, 0 replies; 58+ messages in thread
From: Florian Fainelli @ 2016-01-07 19:50 UTC (permalink / raw)
  To: Russell King, Thomas Petazzoni; +Cc: netdev

On 07/12/15 09:37, Russell King wrote:
> There is no prevention of a concurrent call to both fixed_mdio_read()
> and fixed_phy_update_state(), which can result in the state being
> modified while it's being inspected.  Fix this by using a seqcount
> to detect modifications, and memcpy()ing the state.
> 
> We remain slightly naughty here, calling link_update() and updating
> the link status within the read-side loop - which would need rework
> of the design to change.
> 
> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>

Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-- 
Florian

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH RFC 06/26] phy: provide a hook for link up/link down events
  2015-12-07 17:37 ` [PATCH RFC 06/26] phy: provide a hook for link up/link down events Russell King
@ 2016-01-07 19:53   ` Florian Fainelli
  0 siblings, 0 replies; 58+ messages in thread
From: Florian Fainelli @ 2016-01-07 19:53 UTC (permalink / raw)
  To: Russell King, Thomas Petazzoni; +Cc: netdev

On 07/12/15 09:37, Russell King wrote:
> 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.
> 
> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>


Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-- 
Florian

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH RFC 07/26] phy: marvell: 88E1512: add flow control support
  2015-12-07 17:37 ` [PATCH RFC 07/26] phy: marvell: 88E1512: add flow control support Russell King
@ 2016-01-07 19:53   ` Florian Fainelli
  0 siblings, 0 replies; 58+ messages in thread
From: Florian Fainelli @ 2016-01-07 19:53 UTC (permalink / raw)
  To: Russell King, Thomas Petazzoni; +Cc: netdev

On 07/12/15 09:37, Russell King wrote:
> 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 <rmk+kernel@arm.linux.org.uk>

Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-- 
Florian

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH RFC 08/26] phy: export phy_start_machine() for phylink
  2015-12-07 17:37 ` [PATCH RFC 08/26] phy: export phy_start_machine() for phylink Russell King
@ 2016-01-07 19:53   ` Florian Fainelli
  0 siblings, 0 replies; 58+ messages in thread
From: Florian Fainelli @ 2016-01-07 19:53 UTC (permalink / raw)
  To: Russell King, Thomas Petazzoni; +Cc: netdev

On 07/12/15 09:37, Russell King wrote:
> 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.
> 
> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>

Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-- 
Florian

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH RFC 09/26] phy: export phy_speed_to_str() for phylink
  2015-12-07 17:37 ` [PATCH RFC 09/26] phy: export phy_speed_to_str() " Russell King
@ 2016-01-07 19:54   ` Florian Fainelli
  0 siblings, 0 replies; 58+ messages in thread
From: Florian Fainelli @ 2016-01-07 19:54 UTC (permalink / raw)
  To: Russell King, Thomas Petazzoni; +Cc: netdev

On 07/12/15 09:37, Russell King wrote:
> phylink would like to reuse phy_speed_to_str() to convert the speed
> to a string.  Add a prototype and export this helper function.
> 
> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>

Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-- 
Florian

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH RFC 12/26] phylink: add hooks for SFP support
  2015-12-07 17:38 ` [PATCH RFC 12/26] phylink: add hooks for SFP support Russell King
@ 2016-01-07 20:05   ` Florian Fainelli
  0 siblings, 0 replies; 58+ messages in thread
From: Florian Fainelli @ 2016-01-07 20:05 UTC (permalink / raw)
  To: Russell King, Thomas Petazzoni; +Cc: netdev

On 07/12/15 09:38, Russell King wrote:
> 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.
> 
> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>

Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-- 
Florian

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH RFC 11/26] phylink: add phylink infrastructure
  2015-12-07 17:38 ` [PATCH RFC 11/26] phylink: add phylink infrastructure Russell King
@ 2016-01-07 20:09   ` Florian Fainelli
  0 siblings, 0 replies; 58+ messages in thread
From: Florian Fainelli @ 2016-01-07 20:09 UTC (permalink / raw)
  To: Russell King, Thomas Petazzoni; +Cc: netdev

On 07/12/15 09:38, Russell King wrote:
[snip]
> +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");
> +}

Should this function be exported?

[snip]

> +
> +	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) {

This could be worth becoming a helper function that drivers could use,
as a subsequent patch for instance.


> +		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);

Overall, this looks good to me, I am not very comfortable with the API
similarities between PHY link and traditional PHY devices, since that
forces PHY link to duplicate a bit of code (ioctl among other things).

Would it be better to have a generic PHY device have the option to be
overloaded with PHY link operations such that drivers do not have to
absorb API changes?
-- 
Florian

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH RFC 16/26] phy: fixed-phy: remove fixed_phy_update_state()
  2015-12-07 17:38 ` [PATCH RFC 16/26] phy: fixed-phy: remove fixed_phy_update_state() Russell King
@ 2016-01-07 20:15   ` Florian Fainelli
  0 siblings, 0 replies; 58+ messages in thread
From: Florian Fainelli @ 2016-01-07 20:15 UTC (permalink / raw)
  To: Russell King, Thomas Petazzoni; +Cc: netdev

On 07/12/15 09:38, Russell King wrote:
> mvneta is the only user of fixed_phy_update_state(), which has been
> converted to use phylink instead.  Remove fixed_phy_update_state().
> 
> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>

Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-- 
Florian

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH RFC 18/26] net: mvneta: add nway_reset support
  2015-12-07 17:38 ` [PATCH RFC 18/26] net: mvneta: add " Russell King
@ 2016-01-07 20:19   ` Florian Fainelli
  0 siblings, 0 replies; 58+ messages in thread
From: Florian Fainelli @ 2016-01-07 20:19 UTC (permalink / raw)
  To: Russell King, Thomas Petazzoni; +Cc: netdev

On 07/12/15 09:38, Russell King wrote:
> 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 <rmk+kernel@arm.linux.org.uk>

Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-- 
Florian

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH RFC 15/26] net: mvneta: convert to phylink
  2015-12-07 17:38 ` [PATCH RFC 15/26] net: mvneta: convert to phylink Russell King
@ 2016-01-07 20:22   ` Florian Fainelli
  0 siblings, 0 replies; 58+ messages in thread
From: Florian Fainelli @ 2016-01-07 20:22 UTC (permalink / raw)
  To: Russell King, Thomas Petazzoni; +Cc: netdev

On 07/12/15 09:38, Russell King wrote:
> Convert mvneta to use phylink, which models the MAC to PHY link in
> a generic, reusable form.

This looks much nicer after your changes, and this also makes it much
clearer what the PHY link changes bring in, and why in patch 11 I was
under the impression this duplicated a similar API from PHYLIB.

Since PHYLINK becomes a much better super set of PHYLIB now, we should
think about what to do with PHYLIB being using in drivers at some point
and see what could be deprecated/moved over to PHYLINK.

> 
> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
> ---
>  drivers/net/ethernet/marvell/Kconfig  |   2 +-
>  drivers/net/ethernet/marvell/mvneta.c | 399 +++++++++++++++++++---------------
>  2 files changed, 224 insertions(+), 177 deletions(-)
> 
> diff --git a/drivers/net/ethernet/marvell/Kconfig b/drivers/net/ethernet/marvell/Kconfig
> index a1c862b4664d..d59fb29f28b3 100644
> --- a/drivers/net/ethernet/marvell/Kconfig
> +++ b/drivers/net/ethernet/marvell/Kconfig
> @@ -44,7 +44,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 e84c7f2634d3..f19d9a31dccd 100644
> --- a/drivers/net/ethernet/marvell/mvneta.c
> +++ b/drivers/net/ethernet/marvell/mvneta.c
> @@ -31,6 +31,7 @@
>  #include <linux/of_net.h>
>  #include <linux/of_address.h>
>  #include <linux/phy.h>
> +#include <linux/phylink.h>
>  #include <linux/clk.h>
>  #include <linux/cpu.h>
>  
> @@ -169,6 +170,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)
> @@ -184,13 +186,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)
> @@ -361,15 +369,9 @@ struct mvneta_port {
>  	u16 tx_ring_size;
>  	u16 rx_ring_size;
>  
> -	struct mii_bus *mii_bus;
> -	struct phy_device *phy_dev;
> -	phy_interface_t phy_interface;
> -	struct device_node *phy_node;
> -	unsigned int link;
> -	unsigned int duplex;
> -	unsigned int speed;
> +	struct device_node *dn;
>  	unsigned int tx_csum_limit;
> -	int use_inband_status:1;
> +	struct phylink *phylink;
>  
>  	u64 ethtool_stats[ARRAY_SIZE(mvneta_statistics)];
>  };
> @@ -1056,26 +1058,6 @@ static void mvneta_defaults_set(struct mvneta_port *pp)
>  	val &= ~MVNETA_PHY_POLLING_ENABLE;
>  	mvreg_write(pp, MVNETA_UNIT_CONTROL, val);
>  
> -	if (pp->use_inband_status) {
> -		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);
> -	} 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);
> -	}
> -
>  	mvneta_set_ucast_table(pp, -1);
>  	mvneta_set_special_mcast_table(pp, -1);
>  	mvneta_set_other_mcast_table(pp, -1);
> @@ -2115,26 +2097,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
> @@ -2162,12 +2129,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 */
> @@ -2456,7 +2422,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);
>  }
>  
> @@ -2464,7 +2430,7 @@ static void mvneta_stop_dev(struct mvneta_port *pp)
>  {
>  	unsigned int cpu;
>  
> -	phy_stop(pp->phy_dev);
> +	phylink_stop(pp->phylink);
>  
>  	for_each_present_cpu(cpu) {
>  		struct mvneta_pcpu_port *port = per_cpu_ptr(pp->ports, cpu);
> @@ -2615,99 +2581,218 @@ static int mvneta_set_mac_addr(struct net_device *dev, void *addr)
>  	return 0;
>  }
>  
> -static void mvneta_adjust_link(struct net_device *ndev)
> +static int mvneta_mac_support(struct net_device *ndev, unsigned int mode,
> +			      struct phylink_link_state *state)
> +{
> +	switch (mode) {
> +	case MLO_AN_8023Z:
> +		state->supported = SUPPORTED_1000baseT_Full |
> +				   SUPPORTED_Autoneg | SUPPORTED_Pause;
> +		state->advertising = ADVERTISED_1000baseT_Full |
> +				     ADVERTISED_Autoneg | ADVERTISED_Pause;
> +		state->an_enabled = 1;
> +		break;
> +
> +	case MLO_AN_FIXED:
> +		break;
> +
> +	default:
> +		state->supported = PHY_10BT_FEATURES |
> +				   PHY_100BT_FEATURES |
> +				   SUPPORTED_1000baseT_Full |
> +				   SUPPORTED_Autoneg;
> +		state->advertising = ADVERTISED_10baseT_Half |
> +				     ADVERTISED_10baseT_Full |
> +				     ADVERTISED_100baseT_Half |
> +				     ADVERTISED_100baseT_Full |
> +				     ADVERTISED_1000baseT_Full |
> +				     ADVERTISED_Autoneg;
> +		state->an_enabled = 1;
> +		break;
> +	}
> +	return 0;
> +}
> +
> +static int mvneta_mac_link_state(struct net_device *ndev,
> +				 struct phylink_link_state *state)
>  {
>  	struct mvneta_port *pp = netdev_priv(ndev);
> -	struct phy_device *phydev = pp->phy_dev;
> -	int status_change = 0;
> +	u32 gmac_stat;
>  
> -	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);
> +
> +		gmac_an |= MVNETA_GMAC_INBAND_RESTART_AN;
> +		mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, gmac_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);
> +
> +	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;
> +
> +		if (state->advertising & ADVERTISED_Pause)
> +			new_an |= MVNETA_GMAC_ADVERT_SYM_FLOW_CTRL;
> +
> +		if (state->an_enabled)
> +			new_an |= MVNETA_GMAC_AN_FLOW_CTRL_EN |
> +				  MVNETA_GMAC_AN_DUPLEX_EN;
> +		else if (state->duplex)
> +			new_an |= MVNETA_GMAC_CONFIG_FULL_DUPLEX;
> +		break;
>  
> -		pp->link = phydev->link;
> -		status_change = 1;
> +	default:
> +		/* Phy or fixed speed */
> +		if (state->duplex)
> +			new_an |= MVNETA_GMAC_CONFIG_FULL_DUPLEX;
> +
> +		if (state->speed == SPEED_1000)
> +			new_an |= MVNETA_GMAC_CONFIG_GMII_SPEED;
> +		else if (state->speed == SPEED_100)
> +			new_an |= MVNETA_GMAC_CONFIG_MII_SPEED;
> +		break;
>  	}
>  
> -	if (status_change) {
> -		if (phydev->link) {
> -			if (!pp->use_inband_status) {
> -				u32 val = mvreg_read(pp,
> -						  MVNETA_GMAC_AUTONEG_CONFIG);
> -				val &= ~MVNETA_GMAC_FORCE_LINK_DOWN;
> -				val |= MVNETA_GMAC_FORCE_LINK_PASS;
> -				mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG,
> -					    val);
> -			}
> -			mvneta_port_up(pp);
> -		} else {
> -			if (!pp->use_inband_status) {
> -				u32 val = mvreg_read(pp,
> -						  MVNETA_GMAC_AUTONEG_CONFIG);
> -				val &= ~MVNETA_GMAC_FORCE_LINK_PASS;
> -				val |= MVNETA_GMAC_FORCE_LINK_DOWN;
> -				mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG,
> -					    val);
> -			}
> -			mvneta_port_down(pp);
> -		}
> -		phy_print_status(phydev);
> +	/* Armada 370 documentation says we can only change the port mode
> +	 * and in-band enable when the link is down, so force it down
> +	 * while making these changes. We also do this for GMAC_CTRL2 */
> +	if ((new_ctrl0 ^ gmac_ctrl0) & MVNETA_GMAC0_PORT_1000BASE_X ||
> +	    (new_ctrl2 ^ gmac_ctrl2) & MVNETA_GMAC2_INBAND_AN_ENABLE ||
> +	    (new_an  ^ gmac_an) & MVNETA_GMAC_INBAND_AN_ENABLE) {
> +		mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG,
> +			    (gmac_an & ~MVNETA_GMAC_FORCE_LINK_PASS) |
> +			    MVNETA_GMAC_FORCE_LINK_DOWN);
>  	}
> +
> +	if (new_ctrl0 != gmac_ctrl0)
> +		mvreg_write(pp, MVNETA_GMAC_CTRL_0, new_ctrl0);
> +	if (new_ctrl2 != gmac_ctrl2)
> +		mvreg_write(pp, MVNETA_GMAC_CTRL_2, new_ctrl2);
> +	if (new_clk != gmac_clk)
> +		mvreg_write(pp, MVNETA_GMAC_CLOCK_DIVIDER, new_clk);
> +	if (new_an != gmac_an)
> +		mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, new_an);
>  }
>  
> -static int mvneta_mdio_probe(struct mvneta_port *pp)
> +static void mvneta_mac_link_down(struct net_device *ndev, unsigned int mode)
>  {
> -	struct 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;
> +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;
>  
> -	pp->phy_dev = phy_dev;
> -	pp->link    = 0;
> -	pp->duplex  = 0;
> -	pp->speed   = 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);
> +	}
>  
> -	return 0;
> +	mvneta_port_up(pp);
> +}
> +
> +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);
>  }
>  
>  static void mvneta_percpu_enable(void *arg)
> @@ -2914,10 +2999,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 */
> @@ -2927,10 +3009,7 @@ 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 */
> @@ -2938,10 +3017,7 @@ int mvneta_ethtool_set_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_sset(pp->phy_dev, cmd);
> +	return phylink_ethtool_set_settings(pp->phylink, cmd);
>  }
>  
>  /* Set interrupt coalescing for ethtools */
> @@ -3223,9 +3299,6 @@ static int mvneta_port_power_up(struct mvneta_port *pp, int phy_mode)
>  		return -EINVAL;
>  	}
>  
> -	if (pp->use_inband_status)
> -		ctrl |= MVNETA_GMAC2_INBAND_AN_ENABLE;
> -
>  	/* Cancel Port Reset */
>  	ctrl &= ~MVNETA_GMAC2_PORT_RESET;
>  	mvreg_write(pp, MVNETA_GMAC_CTRL_2, ctrl);
> @@ -3243,13 +3316,12 @@ 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 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 phy_mode;
>  	int err;
>  	int cpu;
> @@ -3264,31 +3336,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;
> @@ -3298,18 +3350,13 @@ static int mvneta_probe(struct platform_device *pdev)
>  	dev->ethtool_ops = &mvneta_eth_tool_ops;
>  
>  	pp = netdev_priv(dev);
> -	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->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);
> @@ -3386,6 +3433,14 @@ static int mvneta_probe(struct platform_device *pdev)
>  	dev->priv_flags |= IFF_UNICAST_FLT;
>  	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");
> @@ -3397,24 +3452,16 @@ 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->dev);
> -	}
> -
>  	return 0;
>  
>  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);
> -err_put_phy_node:
> -	of_node_put(phy_node);
>  err_free_irq:
>  	irq_dispose_mapping(dev->irq);
>  err_free_netdev:
> @@ -3433,7 +3480,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);
>  
>  	return 0;
> 


-- 
Florian

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH RFC 14/26] sfp: display SFP module information
  2015-12-07 17:38 ` [PATCH RFC 14/26] sfp: display SFP module information Russell King
@ 2016-01-07 20:23   ` Florian Fainelli
  0 siblings, 0 replies; 58+ messages in thread
From: Florian Fainelli @ 2016-01-07 20:23 UTC (permalink / raw)
  To: Russell King, Thomas Petazzoni; +Cc: netdev

On 07/12/15 09:38, Russell King wrote:
> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>

Looks useful, but also duplicates a bit what ethtool already does,
unless this is really useful to make decisions, I would probably drop
this entirely...

-- 
Florian

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH RFC 13/26] sfp: add phylink based SFP module support
  2015-12-07 17:38 ` [PATCH RFC 13/26] sfp: add phylink based SFP module support Russell King
@ 2016-01-07 20:23   ` Florian Fainelli
  0 siblings, 0 replies; 58+ messages in thread
From: Florian Fainelli @ 2016-01-07 20:23 UTC (permalink / raw)
  To: Russell King, Thomas Petazzoni; +Cc: netdev

On 07/12/15 09:38, Russell King wrote:
> 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.

This goes pretty deep into the SFP specs, which I don't have a fresh
memory of, few nits here and there:

include/linux/sfp.h contains things that could be useful for user-space
since these are standard definitions, should this be moved to uapi such
that ethtool could use that header too?
-- 
Florian

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH RFC 17/26] phylink: add ethtool nway_reset support
  2015-12-07 17:38 ` [PATCH RFC 17/26] phylink: add ethtool nway_reset support Russell King
@ 2016-01-07 20:24   ` Florian Fainelli
  0 siblings, 0 replies; 58+ messages in thread
From: Florian Fainelli @ 2016-01-07 20:24 UTC (permalink / raw)
  To: Russell King, Thomas Petazzoni; +Cc: netdev

On 07/12/15 09:38, Russell King wrote:
> Add ethtool nway_reset support to phylink, to allow userspace to
> request a re-negotiation of the link.
> 
> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>

Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-- 
Florian

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH RFC 19/26] phylink: add flow control support
  2015-12-07 17:38 ` [PATCH RFC 19/26] phylink: add flow control support Russell King
@ 2016-01-07 20:25   ` Florian Fainelli
  0 siblings, 0 replies; 58+ messages in thread
From: Florian Fainelli @ 2016-01-07 20:25 UTC (permalink / raw)
  To: Russell King, Thomas Petazzoni; +Cc: netdev

On 07/12/15 09:38, Russell King wrote:
> 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 <rmk+kernel@arm.linux.org.uk>

Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-- 
Florian

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH RFC 20/26] net: mvneta: add flow control support via phylink
  2015-12-07 17:38 ` [PATCH RFC 20/26] net: mvneta: add flow control support via phylink Russell King
@ 2016-01-07 20:26   ` Florian Fainelli
  0 siblings, 0 replies; 58+ messages in thread
From: Florian Fainelli @ 2016-01-07 20:26 UTC (permalink / raw)
  To: Russell King, Thomas Petazzoni; +Cc: netdev

On 07/12/15 09:38, Russell King wrote:
> 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 <rmk+kernel@arm.linux.org.uk>

Looks obviously correct from an API usage perpsective.

Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>

-- 
Florian

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH RFC 21/26] net: mvneta: enable flow control for PHY connections
  2015-12-07 17:38 ` [PATCH RFC 21/26] net: mvneta: enable flow control for PHY connections Russell King
@ 2016-01-07 20:31   ` Florian Fainelli
  0 siblings, 0 replies; 58+ messages in thread
From: Florian Fainelli @ 2016-01-07 20:31 UTC (permalink / raw)
  To: Russell King, Thomas Petazzoni; +Cc: netdev

On 07/12/15 09:38, Russell King wrote:
> Enable flow control support for PHY connections by indicating our
> support via the ethtool capabilities.  phylink takes care of the
> appropriate handling.
> 
> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>

Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-- 
Florian

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH RFC 22/26] phylink: add EEE support
  2015-12-07 17:39 ` [PATCH RFC 22/26] phylink: add EEE support Russell King
@ 2016-01-07 20:34   ` Florian Fainelli
  0 siblings, 0 replies; 58+ messages in thread
From: Florian Fainelli @ 2016-01-07 20:34 UTC (permalink / raw)
  To: Russell King, Thomas Petazzoni; +Cc: netdev

On 07/12/15 09:39, Russell King wrote:
> Add EEE hooks to phylink to allow the phylib EEE functions for the
> connected phy to be safely accessed.

Looks good, a comment about why mac_link_up() now needs to have access
to phydev would be nice to have, just so it is clear, this is meant to
be used to call phy_init_eee() against.
-- 
Florian

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH RFC 23/26] net: mvneta: add EEE support
  2015-12-07 17:39 ` [PATCH RFC 23/26] net: mvneta: " Russell King
@ 2016-01-07 20:35   ` Florian Fainelli
  0 siblings, 0 replies; 58+ messages in thread
From: Florian Fainelli @ 2016-01-07 20:35 UTC (permalink / raw)
  To: Russell King, Thomas Petazzoni; +Cc: netdev

On 07/12/15 09:39, Russell King wrote:
> 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 <rmk+kernel@arm.linux.org.uk>

Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-- 
Florian

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH RFC 24/26] phylink: add module EEPROM support
  2015-12-07 17:39 ` [PATCH RFC 24/26] phylink: add module EEPROM support Russell King
@ 2016-01-07 20:36   ` Florian Fainelli
  0 siblings, 0 replies; 58+ messages in thread
From: Florian Fainelli @ 2016-01-07 20:36 UTC (permalink / raw)
  To: Russell King, Thomas Petazzoni; +Cc: netdev

On 07/12/15 09:39, Russell King wrote:
> Add support for reading module EEPROMs through phylink.
> 
> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>

Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-- 
Florian

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH RFC 25/26] net: mvneta: add module EEPROM reading support
  2015-12-07 17:39 ` [PATCH RFC 25/26] net: mvneta: add module EEPROM reading support Russell King
@ 2016-01-07 20:36   ` Florian Fainelli
  0 siblings, 0 replies; 58+ messages in thread
From: Florian Fainelli @ 2016-01-07 20:36 UTC (permalink / raw)
  To: Russell King, Thomas Petazzoni; +Cc: netdev

On 07/12/15 09:39, Russell King wrote:
> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>

Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
-- 
Florian

^ permalink raw reply	[flat|nested] 58+ messages in thread

* Re: [PATCH RFC 00/26] Phylink & SFP support
  2015-12-28 23:39     ` Dustin Byford
@ 2016-01-07 20:42       ` Florian Fainelli
  0 siblings, 0 replies; 58+ messages in thread
From: Florian Fainelli @ 2016-01-07 20:42 UTC (permalink / raw)
  To: Dustin Byford; +Cc: Russell King - ARM Linux, Thomas Petazzoni, netdev

On 28/12/15 15:39, Dustin Byford wrote:
>> Using a phandle here allows for quite a lot of flexibility on how you want to associate a given SFP to its data plane partner. I do not think we need to get more strict than that strictly mandate an actual Ethernet controller node. These Marvell adapters typically have one or more " ports", each of them being backed by a netdev. The same could be true with a switch properly modeled.
> 
> On a switch though, the number of "ports" is often configurable.
> Physically the q/sfp cage to ASIC wiring is fixed, but when you've
> plugged in a breakout cable you get four "ports" for a single QSFP cage.
> They act like four separate devices in most ways, the notable exception
> is that they share a QSFP module eeprom and the discrete IOs to the cage
> like "reset" and "interrupt"  At the MAC layer, each port gets an
> independent set of resources and they act like separate netdevs.
> 
> A concrete proposal might be to add a "channel" or "lane" parameter to
> sfp,ethernet with a default of 0.
> 
> sfp,ethernet = <&eth2>
> 
> is equivalent to:
> sfp,ethernet = <&eth2 0>
> 
> 
> SFP on a switch0 device with 128 channels:
> 
> sfp,ethernet = <&switch0 42>
> consumes channel 42
> 
> qsfp,ethernet = <&switch0>
> consumes channels 0-3
> 
> qsfp,ethernet = <&switch0 124 125 126 127>
> consumes channels 124-127
> 
> alternatives:
> 
> (less explicit, assume adjacent channels)
> qsfp,ethernet = <&switch0 124> // consumes 124-127

Right, that does not sound specific enough, because the name is qsfp,
that means quad, and then you do 124 + 4 - 1 to know the last number,
what could possibly go wrong ;)?

> 
> (more explicit, don't assume the same device)
> qsfp,ethernet = <&switch0 124 &switch0 125 &switch0 126 &switch0 127>
> or:
> qsfp,ethernet0 = <&switch0 124>
> qsfp,ethernet1 = <&switch0 125>
> qsfp,ethernet2 = <&switch0 126>
> qsfp,ethernet3 = <&switch0 127>

That might be the best representation actually.

> 
> (move complexity to the NIC/ASIC, ensure one channel per handle on the
> NIC/ASIC side)
> qsfp,ethernet = <&switch0c124 &switch0c125 &switch0c126 &switch0c127>

So counting the number of cells tells you how the QSFP got broken out
into individual SFPs? That could work too, humm.

[snip]

>> Would this be something you expect to happen dynamically? Not that this does not seem reasonable but would these netdevs serve a different purpose than being control endpoints, or would they become real logical netdevs with separate data planes at the MAC they would be linked to?
> 
> Real netdevs with separate data planes.  Reconfiguring them dynamically
> seems like a good theoretical goal but is probably impractical in most
> cases.  Even if it's not dynamic I think it's a good example of why you
> might want a QSFP device to have an ethernet handle that points to four
> things instead of one.

Agreed.
-- 
Florian

^ permalink raw reply	[flat|nested] 58+ messages in thread

end of thread, other threads:[~2016-01-07 20:43 UTC | newest]

Thread overview: 58+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-12-07 17:35 [PATCH RFC 00/26] Phylink & SFP support Russell King - ARM Linux
2015-12-07 17:37 ` [PATCH RFC 01/26] phy: move fixed_phy MII register generation to a library Russell King
2016-01-07 19:47   ` Florian Fainelli
2015-12-07 17:37 ` [PATCH RFC 02/26] phy: convert swphy register generation to tabular form Russell King
2016-01-07 19:47   ` Florian Fainelli
2015-12-07 17:37 ` [PATCH RFC 03/26] phy: separate swphy state validation from register generation Russell King
2016-01-07 19:48   ` Florian Fainelli
2015-12-07 17:37 ` [PATCH RFC 04/26] phy: generate swphy registers on the fly Russell King
2016-01-07 19:49   ` Florian Fainelli
2015-12-07 17:37 ` [PATCH RFC 05/26] phy: improve safety of fixed-phy MII register reading Russell King
2016-01-07 19:50   ` Florian Fainelli
2015-12-07 17:37 ` [PATCH RFC 06/26] phy: provide a hook for link up/link down events Russell King
2016-01-07 19:53   ` Florian Fainelli
2015-12-07 17:37 ` [PATCH RFC 07/26] phy: marvell: 88E1512: add flow control support Russell King
2016-01-07 19:53   ` Florian Fainelli
2015-12-07 17:37 ` [PATCH RFC 08/26] phy: export phy_start_machine() for phylink Russell King
2016-01-07 19:53   ` Florian Fainelli
2015-12-07 17:37 ` [PATCH RFC 09/26] phy: export phy_speed_to_str() " Russell King
2016-01-07 19:54   ` Florian Fainelli
2015-12-07 17:38 ` [PATCH RFC 10/26] phy: add I2C mdio bus Russell King
2015-12-08 18:15   ` Florian Fainelli
2015-12-11 10:25     ` Russell King - ARM Linux
2015-12-07 17:38 ` [PATCH RFC 11/26] phylink: add phylink infrastructure Russell King
2016-01-07 20:09   ` Florian Fainelli
2015-12-07 17:38 ` [PATCH RFC 12/26] phylink: add hooks for SFP support Russell King
2016-01-07 20:05   ` Florian Fainelli
2015-12-07 17:38 ` [PATCH RFC 13/26] sfp: add phylink based SFP module support Russell King
2016-01-07 20:23   ` Florian Fainelli
2015-12-07 17:38 ` [PATCH RFC 14/26] sfp: display SFP module information Russell King
2016-01-07 20:23   ` Florian Fainelli
2015-12-07 17:38 ` [PATCH RFC 15/26] net: mvneta: convert to phylink Russell King
2016-01-07 20:22   ` Florian Fainelli
2015-12-07 17:38 ` [PATCH RFC 16/26] phy: fixed-phy: remove fixed_phy_update_state() Russell King
2016-01-07 20:15   ` Florian Fainelli
2015-12-07 17:38 ` [PATCH RFC 17/26] phylink: add ethtool nway_reset support Russell King
2016-01-07 20:24   ` Florian Fainelli
2015-12-07 17:38 ` [PATCH RFC 18/26] net: mvneta: add " Russell King
2016-01-07 20:19   ` Florian Fainelli
2015-12-07 17:38 ` [PATCH RFC 19/26] phylink: add flow control support Russell King
2016-01-07 20:25   ` Florian Fainelli
2015-12-07 17:38 ` [PATCH RFC 20/26] net: mvneta: add flow control support via phylink Russell King
2016-01-07 20:26   ` Florian Fainelli
2015-12-07 17:38 ` [PATCH RFC 21/26] net: mvneta: enable flow control for PHY connections Russell King
2016-01-07 20:31   ` Florian Fainelli
2015-12-07 17:39 ` [PATCH RFC 22/26] phylink: add EEE support Russell King
2016-01-07 20:34   ` Florian Fainelli
2015-12-07 17:39 ` [PATCH RFC 23/26] net: mvneta: " Russell King
2016-01-07 20:35   ` Florian Fainelli
2015-12-07 17:39 ` [PATCH RFC 24/26] phylink: add module EEPROM support Russell King
2016-01-07 20:36   ` Florian Fainelli
2015-12-07 17:39 ` [PATCH RFC 25/26] net: mvneta: add module EEPROM reading support Russell King
2016-01-07 20:36   ` Florian Fainelli
2015-12-07 17:39 ` [PATCH RFC 26/26] sfp/phylink: hook up eeprom functions Russell King
2015-12-15  7:26 ` [PATCH RFC 00/26] Phylink & SFP support Dustin Byford
2015-12-28  2:08   ` Florian Fainelli
2015-12-28 23:39     ` Dustin Byford
2016-01-07 20:42       ` Florian Fainelli
2015-12-28  1:56 ` Florian Fainelli

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.