mvebu: add support for SFP
authorJonas Gorski <jonas.gorski@gmail.com>
Thu, 29 Sep 2016 10:16:51 +0000 (12:16 +0200)
committerJonas Gorski <jonas.gorski@gmail.com>
Thu, 29 Sep 2016 11:47:18 +0000 (13:47 +0200)
Add patches for SFP support and package it for ClearFog. Tested with a
Juniper SFP module.

Signed-off-by: Jonas Gorski <jonas.gorski@gmail.com>
Acked-by: Felix Fietkau <nbd@nbd.name>
30 files changed:
target/linux/mvebu/config-4.4
target/linux/mvebu/patches-4.4/120-phy-move-fixed_phy-MII-register-generation-to-a-libr.patch [new file with mode: 0644]
target/linux/mvebu/patches-4.4/121-phy-convert-swphy-register-generation-to-tabular-for.patch [new file with mode: 0644]
target/linux/mvebu/patches-4.4/122-phy-separate-swphy-state-validation-from-register-ge.patch [new file with mode: 0644]
target/linux/mvebu/patches-4.4/123-phy-generate-swphy-registers-on-the-fly.patch [new file with mode: 0644]
target/linux/mvebu/patches-4.4/124-phy-improve-safety-of-fixed-phy-MII-register-reading.patch [new file with mode: 0644]
target/linux/mvebu/patches-4.4/125-phy-provide-a-hook-for-link-up-link-down-events.patch [new file with mode: 0644]
target/linux/mvebu/patches-4.4/126-phy-marvell-88E1512-add-flow-control-support.patch [new file with mode: 0644]
target/linux/mvebu/patches-4.4/127-phy-export-phy_start_machine-for-phylink.patch [new file with mode: 0644]
target/linux/mvebu/patches-4.4/128-phy-export-phy_speed_to_str-for-phylink.patch [new file with mode: 0644]
target/linux/mvebu/patches-4.4/129-phy-add-I2C-mdio-bus.patch [new file with mode: 0644]
target/linux/mvebu/patches-4.4/130-phylink-add-phylink-infrastructure.patch [new file with mode: 0644]
target/linux/mvebu/patches-4.4/131-phylink-add-hooks-for-SFP-support.patch [new file with mode: 0644]
target/linux/mvebu/patches-4.4/132-sfp-add-phylink-based-SFP-module-support.patch [new file with mode: 0644]
target/linux/mvebu/patches-4.4/133-sfp-display-SFP-module-information.patch [new file with mode: 0644]
target/linux/mvebu/patches-4.4/134-net-mvneta-convert-to-phylink.patch [new file with mode: 0644]
target/linux/mvebu/patches-4.4/135-phy-fixed-phy-remove-fixed_phy_update_state.patch [new file with mode: 0644]
target/linux/mvebu/patches-4.4/136-phylink-add-ethtool-nway_reset-support.patch [new file with mode: 0644]
target/linux/mvebu/patches-4.4/137-net-mvneta-add-nway_reset-support.patch [new file with mode: 0644]
target/linux/mvebu/patches-4.4/138-phylink-add-flow-control-support.patch [new file with mode: 0644]
target/linux/mvebu/patches-4.4/139-net-mvneta-add-flow-control-support-via-phylink.patch [new file with mode: 0644]
target/linux/mvebu/patches-4.4/140-net-mvneta-enable-flow-control-for-PHY-connections.patch [new file with mode: 0644]
target/linux/mvebu/patches-4.4/141-net-mvneta-enable-flow-control-for-fixed-connections.patch [new file with mode: 0644]
target/linux/mvebu/patches-4.4/142-phylink-add-EEE-support.patch [new file with mode: 0644]
target/linux/mvebu/patches-4.4/143-net-mvneta-add-EEE-support.patch [new file with mode: 0644]
target/linux/mvebu/patches-4.4/144-phylink-add-module-EEPROM-support.patch [new file with mode: 0644]
target/linux/mvebu/patches-4.4/145-net-mvneta-add-module-EEPROM-reading-support.patch [new file with mode: 0644]
target/linux/mvebu/patches-4.4/146-sfp-phylink-hook-up-eeprom-functions.patch [new file with mode: 0644]
target/linux/mvebu/patches-4.4/210-ARM-dts-armada388-clearfog-add-SFP-module-support.patch [new file with mode: 0644]
target/linux/mvebu/patches-4.4/300-reprobe_sfp_phy.patch [new file with mode: 0644]

index c2a81d3..5c1206b 100644 (file)
@@ -272,6 +272,7 @@ CONFIG_MAGIC_SYSRQ=y
 CONFIG_MANGLE_BOOTARGS=y
 CONFIG_MARVELL_PHY=y
 CONFIG_MDIO_BOARDINFO=y
+CONFIG_MDIO_I2C=y
 CONFIG_MEMORY=y
 CONFIG_MIGHT_HAVE_CACHE_L2X0=y
 CONFIG_MIGHT_HAVE_PCI=y
@@ -341,6 +342,7 @@ CONFIG_PCI_MVEBU=y
 CONFIG_PERF_USE_VMALLOC=y
 CONFIG_PGTABLE_LEVELS=2
 CONFIG_PHYLIB=y
+CONFIG_PHYLINK=y
 CONFIG_PINCTRL=y
 CONFIG_PINCTRL_ARMADA_370=y
 CONFIG_PINCTRL_ARMADA_38X=y
@@ -378,6 +380,7 @@ CONFIG_SENSORS_PWM_FAN=y
 CONFIG_SENSORS_TMP421=y
 CONFIG_SERIAL_8250_DW=y
 CONFIG_SERIAL_8250_FSL=y
+CONFIG_SFP=y
 CONFIG_SMP=y
 CONFIG_SMP_ON_UP=y
 CONFIG_SOC_BUS=y
@@ -389,6 +392,7 @@ CONFIG_SRAM=y
 CONFIG_SRCU=y
 CONFIG_SWCONFIG=y
 CONFIG_SWIOTLB=y
+CONFIG_SWPHY=y
 CONFIG_SWP_EMULATE=y
 CONFIG_SYS_SUPPORTS_APM_EMULATION=y
 CONFIG_THERMAL=y
diff --git a/target/linux/mvebu/patches-4.4/120-phy-move-fixed_phy-MII-register-generation-to-a-libr.patch b/target/linux/mvebu/patches-4.4/120-phy-move-fixed_phy-MII-register-generation-to-a-libr.patch
new file mode 100644 (file)
index 0000000..00745f1
--- /dev/null
@@ -0,0 +1,306 @@
+From 4d5621372f6e7ddbfd5879602f82073987bcc722 Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Sun, 20 Sep 2015 09:57:10 +0100
+Subject: [PATCH 709/744] phy: move fixed_phy MII register generation to a
+ library
+
+Move the fixed_phy MII register generation to a library to allow other
+software phy implementations to use this code.
+
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+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
+
+--- a/drivers/net/phy/Kconfig
++++ b/drivers/net/phy/Kconfig
+@@ -26,6 +26,9 @@ config SWCONFIG_LEDS
+       bool "Switch LED trigger support"
+       depends on (SWCONFIG && LEDS_TRIGGERS)
++config SWPHY
++      bool
++
+ comment "MII PHY device drivers"
+ config AQUANTIA_PHY
+@@ -205,6 +208,7 @@ config RTL8306_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.
+--- 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_MDIO_BOARDINFO)  += mdio-boardinfo.o
+--- 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_fm
+ 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)
+--- /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);
+--- /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
diff --git a/target/linux/mvebu/patches-4.4/121-phy-convert-swphy-register-generation-to-tabular-for.patch b/target/linux/mvebu/patches-4.4/121-phy-convert-swphy-register-generation-to-tabular-for.patch
new file mode 100644 (file)
index 0000000..0d689f3
--- /dev/null
@@ -0,0 +1,203 @@
+From cd834fe430f030a63bfa9277bba194e8eef4dbd0 Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Sun, 20 Sep 2015 10:18:59 +0100
+Subject: [PATCH 710/744] phy: convert swphy register generation to tabular
+ form
+
+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
+
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ drivers/net/phy/swphy.c | 143 ++++++++++++++++++++++++++----------------------
+ 1 file changed, 78 insertions(+), 65 deletions(-)
+
+--- 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;
diff --git a/target/linux/mvebu/patches-4.4/122-phy-separate-swphy-state-validation-from-register-ge.patch b/target/linux/mvebu/patches-4.4/122-phy-separate-swphy-state-validation-from-register-ge.patch
new file mode 100644 (file)
index 0000000..4b332cc
--- /dev/null
@@ -0,0 +1,138 @@
+From e07630ad84c7dc145863f079f108154fb7c975e7 Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Sun, 20 Sep 2015 11:12:15 +0100
+Subject: [PATCH 711/744] phy: separate swphy state validation from register
+ generation
+
+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.
+
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+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(-)
+
+--- a/drivers/net/phy/fixed_phy.c
++++ b/drivers/net/phy/fixed_phy.c
+@@ -49,12 +49,12 @@ static struct fixed_mdio_bus platform_fm
+       .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
+       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
+                       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;
+--- 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 s
+       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 s
+       regs[MII_BMCR] = bmcr;
+       regs[MII_LPA] = lpa;
+       regs[MII_STAT1000] = lpagb;
+-
+-      return 0;
+ }
+ EXPORT_SYMBOL_GPL(swphy_update_regs);
+--- 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
diff --git a/target/linux/mvebu/patches-4.4/123-phy-generate-swphy-registers-on-the-fly.patch b/target/linux/mvebu/patches-4.4/123-phy-generate-swphy-registers-on-the-fly.patch
new file mode 100644 (file)
index 0000000..218b902
--- /dev/null
@@ -0,0 +1,204 @@
+From e0f33a88243329da1aa5a90fe10ab25c9fb0a091 Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Sun, 20 Sep 2015 11:28:39 +0100
+Subject: [PATCH 712/744] phy: generate swphy registers on the fly
+
+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().
+
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+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(-)
+
+--- 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_fm
+       .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_bu
+       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_de
+                       _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
+       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
+                       goto err_regs;
+       }
+-      fixed_phy_update_regs(fp);
++      fixed_phy_update(fp);
+       list_add_tail(&fp->node, &fmb->phys);
+--- 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 fi
+ 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
+       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
+                       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);
+--- 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
diff --git a/target/linux/mvebu/patches-4.4/124-phy-improve-safety-of-fixed-phy-MII-register-reading.patch b/target/linux/mvebu/patches-4.4/124-phy-improve-safety-of-fixed-phy-MII-register-reading.patch
new file mode 100644 (file)
index 0000000..5167b07
--- /dev/null
@@ -0,0 +1,92 @@
+From c36739c3cfd277a4cc9820a29dd0f4b7fbac795b Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Sun, 20 Sep 2015 18:31:36 +0100
+Subject: [PATCH 713/744] phy: improve safety of fixed-phy MII register reading
+
+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.
+
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+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(-)
+
+--- 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_bu
+       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_de
+       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_de
+                       _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
+       if (!fp)
+               return -ENOMEM;
++      seqcount_init(&fp->seqcount);
++
+       fmb->irqs[phy_addr] = irq;
+       fp->addr = phy_addr;
diff --git a/target/linux/mvebu/patches-4.4/125-phy-provide-a-hook-for-link-up-link-down-events.patch b/target/linux/mvebu/patches-4.4/125-phy-provide-a-hook-for-link-up-link-down-events.patch
new file mode 100644 (file)
index 0000000..17c1ead
--- /dev/null
@@ -0,0 +1,183 @@
+From d8b4e728f598d3c8a9b219d4679d5de350caa082 Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Fri, 18 Sep 2015 14:42:16 +0100
+Subject: [PATCH 714/744] phy: provide a hook for link up/link down events
+
+Sometimes, we need to do additional work between the PHY coming up and
+marking the carrier present - for example, we may need to wait for the
+PHY to MAC link to finish negotiation.  This changes phylib to provide
+a notification function pointer which avoids the built-in
+netif_carrier_on() and netif_carrier_off() functions.
+
+Standard ->adjust_link functionality is provided by hooking a helper
+into the new ->phy_link_change method.
+
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+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(-)
+
+--- a/drivers/net/phy/phy.c
++++ b/drivers/net/phy/phy.c
+@@ -847,6 +847,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
+@@ -888,8 +898,7 @@ void phy_state_machine(struct work_struc
+               /* 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;
+               }
+@@ -901,9 +910,7 @@ void phy_state_machine(struct work_struc
+               /* 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;
+@@ -928,8 +935,7 @@ void phy_state_machine(struct work_struc
+                               }
+                       }
+                       phydev->state = PHY_RUNNING;
+-                      netif_carrier_on(phydev->attached_dev);
+-                      phydev->adjust_link(phydev->attached_dev);
++                      phy_link_up(phydev);
+               }
+               break;
+       case PHY_FORCING:
+@@ -939,13 +945,12 @@ void phy_state_machine(struct work_struc
+               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
+@@ -968,14 +973,12 @@ void phy_state_machine(struct work_struc
+               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);
+@@ -983,8 +986,7 @@ void phy_state_machine(struct work_struc
+       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;
+@@ -1004,11 +1006,11 @@ void phy_state_machine(struct work_struc
+                               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;
+@@ -1020,11 +1022,11 @@ void phy_state_machine(struct work_struc
+                       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;
+       }
+--- 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
+ }
+ 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
+               goto error;
+       }
++      phydev->phy_link_change = phy_link_change;
+       phydev->attached_dev = dev;
+       dev->phydev = phydev;
+--- 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)
diff --git a/target/linux/mvebu/patches-4.4/126-phy-marvell-88E1512-add-flow-control-support.patch b/target/linux/mvebu/patches-4.4/126-phy-marvell-88E1512-add-flow-control-support.patch
new file mode 100644 (file)
index 0000000..e731c4d
--- /dev/null
@@ -0,0 +1,26 @@
+From 5eed0bf3bc3e69b20a13d8ffcdf97cb720391637 Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Thu, 1 Oct 2015 00:34:08 +0100
+Subject: [PATCH 735/744] phy: marvell: 88E1512: add flow control support
+
+The Marvell PHYs support pause frame advertisments, so we should not be
+masking their support off.  Add the necessary flag to the Marvell PHY
+to allow any MAC level pause frame support to be advertised.
+
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ drivers/net/phy/marvell.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- 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,
diff --git a/target/linux/mvebu/patches-4.4/127-phy-export-phy_start_machine-for-phylink.patch b/target/linux/mvebu/patches-4.4/127-phy-export-phy_start_machine-for-phylink.patch
new file mode 100644 (file)
index 0000000..eb73933
--- /dev/null
@@ -0,0 +1,25 @@
+From f2a9687b39cda3fb67ecd5eaa88e3545e78c982c Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Fri, 16 Oct 2015 12:18:41 +0100
+Subject: [PATCH 715/744] phy: export phy_start_machine() for phylink
+
+phylink will need phy_start_machine exported, so lets export it as a
+GPL symbol.  Documentation/networking/phy.txt indicates that this
+should be a PHY API function.
+
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ drivers/net/phy/phy.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/drivers/net/phy/phy.c
++++ b/drivers/net/phy/phy.c
+@@ -568,6 +568,7 @@ void phy_start_machine(struct phy_device
+ {
+       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
diff --git a/target/linux/mvebu/patches-4.4/128-phy-export-phy_speed_to_str-for-phylink.patch b/target/linux/mvebu/patches-4.4/128-phy-export-phy_speed_to_str-for-phylink.patch
new file mode 100644 (file)
index 0000000..c436070
--- /dev/null
@@ -0,0 +1,44 @@
+From 5c77cc2ffd5deb4762d9551409472f2441297fe7 Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Sun, 18 Oct 2015 19:51:10 +0100
+Subject: [PATCH 716/744] phy: export phy_speed_to_str() for phylink
+
+phylink would like to reuse phy_speed_to_str() to convert the speed
+to a string.  Add a prototype and export this helper function.
+
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+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(-)
+
+--- 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
+               return "Unsupported (update phy.c)";
+       }
+ }
++EXPORT_SYMBOL_GPL(phy_speed_to_str);
+ #define PHY_STATE_STR(_state)                 \
+       case PHY_##_state:                      \
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -812,6 +812,7 @@ int phy_ethtool_gset(struct phy_device *
+ int phy_ethtool_ioctl(struct phy_device *phydev, void *useraddr);
+ int phy_mii_ioctl(struct phy_device *phydev, struct ifreq *ifr, int cmd);
+ int phy_start_interrupts(struct phy_device *phydev);
++const char *phy_speed_to_str(int speed);
+ void phy_print_status(struct phy_device *phydev);
+ void phy_device_free(struct phy_device *phydev);
+ int phy_set_max_speed(struct phy_device *phydev, u32 max_speed);
diff --git a/target/linux/mvebu/patches-4.4/129-phy-add-I2C-mdio-bus.patch b/target/linux/mvebu/patches-4.4/129-phy-add-I2C-mdio-bus.patch
new file mode 100644 (file)
index 0000000..13f8ecf
--- /dev/null
@@ -0,0 +1,163 @@
+From 7f36ac946bfbd4090b8b94be3661b41ac73e21f4 Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Fri, 25 Sep 2015 17:43:52 +0100
+Subject: [PATCH 717/744] phy: add I2C mdio bus
+
+Add an I2C MDIO bus bridge library, to allow phylib to access PHYs which
+are connected to an I2C bus instead of the more conventional MDIO bus.
+Such PHYs can be found in SFP adapters and SFF modules.
+
+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
+
+--- a/drivers/net/phy/Kconfig
++++ b/drivers/net/phy/Kconfig
+@@ -233,6 +233,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
+--- a/drivers/net/phy/Makefile
++++ b/drivers/net/phy/Makefile
+@@ -41,6 +41,7 @@ obj-$(CONFIG_SWCONFIG_B53)   += b53/
+ 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
+--- /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");
+--- /dev/null
++++ b/drivers/net/phy/mdio-i2c.h
+@@ -0,0 +1,19 @@
++/*
++ * MDIO I2C bridge
++ *
++ * Copyright (C) 2015 Russell King
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2 as
++ * published by the Free Software Foundation.
++ */
++#ifndef MDIO_I2C_H
++#define MDIO_I2C_H
++
++struct device;
++struct i2c_adapter;
++struct mii_bus;
++
++struct mii_bus *mdio_i2c_alloc(struct device *parent, struct i2c_adapter *i2c);
++
++#endif
diff --git a/target/linux/mvebu/patches-4.4/130-phylink-add-phylink-infrastructure.patch b/target/linux/mvebu/patches-4.4/130-phylink-add-phylink-infrastructure.patch
new file mode 100644 (file)
index 0000000..0d340c8
--- /dev/null
@@ -0,0 +1,1005 @@
+From c6de6de7d3df13822872ac756eebe868d236297a Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Tue, 22 Sep 2015 20:52:18 +0100
+Subject: [PATCH 718/744] phylink: add phylink infrastructure
+
+The link between the ethernet MAC and its PHY has become more complex
+as the interface evolves.  This is especially true with serdes links,
+where the part of the PHY is effectively integrated into the MAC.
+
+Serdes links can be connected to a variety of devices, including SFF
+modules soldered down onto the board with the MAC, a SFP cage with
+a hotpluggable SFP module which may contain a PHY or directly modulate
+the serdes signals onto optical media with or without a PHY, or even
+a classical PHY connection.
+
+Moreover, the negotiation information on serdes links comes in two
+varieties - SGMII mode, where the PHY provides its speed/duplex/flow
+control information to the MAC, and 1000base-X mode where both ends
+exchange their abilities and each resolve the link capabilities.
+
+This means we need a more flexible means to support these arrangements,
+particularly with the hotpluggable nature of SFP, where the PHY can
+be attached or detached after the network device has been brought up.
+
+Ethtool information can come from multiple sources:
+- we may have a PHY operating in either SGMII or 1000base-X mode, in
+  which case we take ethtool/mii data directly from the PHY.
+- we may have a optical SFP module without a PHY, with the MAC
+  operating in 1000base-X mode - the ethtool/mii data needs to come
+  from the MAC.
+- we may have a copper SFP module with a PHY whic can't be accessed,
+  which means we need to take ethtool/mii data from the MAC.
+
+Phylink aims to solve this by providing an intermediary between the
+MAC and PHY, providing a safe way for PHYs to be hotplugged, and
+allowing a SFP driver to reconfigure the serdes connection.
+
+Phylink also takes over support of fixed link connections, where
+the speed/duplex/flow control are fixed, but link status may be
+controlled by a GPIO signal.  By avoiding the fixed-phy implementation,
+phylink can provide a faster response to link events: fixed-phy has
+to wait for phylib to operate its state machine, which can take
+several seconds.  In comparison, phylink takes milliseconds.
+
+Signed-off-by: Russell King <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    | 816 +++++++++++++++++++++++++++++++++++++++++++
+ include/linux/phy.h          |   2 +
+ include/linux/phylink.h      |  70 ++++
+ 6 files changed, 900 insertions(+)
+ create mode 100644 drivers/net/phy/phylink.c
+ create mode 100644 include/linux/phylink.h
+
+--- 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 MDIO_BOARDINFO
+--- a/drivers/net/phy/Makefile
++++ b/drivers/net/phy/Makefile
+@@ -5,6 +5,7 @@ libphy-$(CONFIG_SWPHY)         += swphy.o
+ obj-$(CONFIG_MDIO_BOARDINFO)  += mdio-boardinfo.o
++obj-$(CONFIG_PHYLINK)         += phylink.o
+ obj-$(CONFIG_PHYLIB)          += libphy.o
+ obj-$(CONFIG_SWCONFIG)                += swconfig.o
+ obj-$(CONFIG_AQUANTIA_PHY)    += aquantia.o
+--- a/drivers/net/phy/phy_device.c
++++ b/drivers/net/phy/phy_device.c
+@@ -754,6 +754,7 @@ void phy_detach(struct phy_device *phyde
+       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
+--- /dev/null
++++ b/drivers/net/phy/phylink.c
+@@ -0,0 +1,816 @@
++/*
++ * 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 &&
++          strcmp(managed, "in-band-status") == 0) {
++              if (pl->link_an_mode == MLO_AN_FIXED) {
++                      netdev_err(pl->netdev,
++                                 "can't use both fixed-link and in-band-status\n");
++                      return -EINVAL;
++              }
++              pl->link_an_mode = MLO_AN_SGMII;
++              pl->link_config.an_enabled = true;
++      }
++
++      return 0;
++}
++
++
++static int phylink_get_support(struct phylink *pl, unsigned int mode)
++{
++      struct phylink_link_state state = pl->link_config;
++      int ret;
++
++      ret = pl->ops->mac_get_support(pl->netdev, mode, &state);
++      if (ret == 0) {
++              pl->link_an_mode = mode;
++              pl->link_config = state;
++      }
++
++      return ret;
++}
++
++static void phylink_mac_config(struct phylink *pl,
++                             const struct phylink_link_state *state)
++{
++      pl->ops->mac_config(pl->netdev, pl->link_an_mode, state);
++}
++
++static void phylink_mac_an_restart(struct phylink *pl)
++{
++      if (pl->link_config.an_enabled)
++              pl->ops->mac_an_restart(pl->netdev, pl->link_an_mode);
++}
++
++static int phylink_get_mac_state(struct phylink *pl, struct phylink_link_state *state)
++{
++      struct net_device *ndev = pl->netdev;
++
++      state->supported = pl->link_config.supported;
++      state->advertising = pl->link_config.advertising;
++      state->an_enabled = pl->link_config.an_enabled;
++      state->link = 1;
++      state->sync = 1;
++
++      return pl->ops->mac_link_state(ndev, state);
++}
++
++/* The fixed state is... fixed except for the link state,
++ * which may be determined by a GPIO.
++ */
++static void phylink_get_fixed_state(struct phylink *pl, struct phylink_link_state *state)
++{
++      *state = pl->link_config;
++      if (pl->link_gpio)
++              state->link = !!gpiod_get_value(pl->link_gpio);
++}
++
++extern const char *phy_speed_to_str(int speed);
++
++static void phylink_resolve(struct work_struct *w)
++{
++      struct phylink *pl = container_of(w, struct phylink, resolve);
++      struct phylink_link_state link_state;
++      struct net_device *ndev = pl->netdev;
++
++      mutex_lock(&pl->state_mutex);
++      if (pl->phylink_disable_state) {
++              link_state.link = false;
++      } else {
++              switch (pl->link_an_mode) {
++              case MLO_AN_PHY:
++                      link_state = pl->phy_state;
++                      break;
++
++              case MLO_AN_FIXED:
++                      phylink_get_fixed_state(pl, &link_state);
++                      break;
++
++              case MLO_AN_SGMII:
++                      phylink_get_mac_state(pl, &link_state);
++                      if (pl->phydev)
++                              link_state.link = link_state.link &&
++                                                pl->phy_state.link;
++                      break;
++
++              case MLO_AN_8023Z:
++                      phylink_get_mac_state(pl, &link_state);
++                      break;
++              }
++      }
++
++      if (link_state.link != netif_carrier_ok(ndev)) {
++              if (!link_state.link) {
++                      netif_carrier_off(ndev);
++                      pl->ops->mac_link_down(ndev, pl->link_an_mode);
++                      netdev_info(ndev, "Link is Down\n");
++              } else {
++                      /* If we have a PHY, we need the MAC updated with
++                       * the current link parameters (eg, in SGMII mode,
++                       * with flow control status.)
++                       */
++                      if (pl->phydev)
++                              phylink_mac_config(pl, &link_state);
++
++                      pl->ops->mac_link_up(ndev, pl->link_an_mode);
++
++                      netif_carrier_on(ndev);
++
++                      netdev_info(ndev,
++                                  "Link is Up - %s/%s - flow control %s\n",
++                                  phy_speed_to_str(link_state.speed),
++                                  link_state.duplex ? "Full" : "Half",
++                                  link_state.pause ? "rx/tx" : "off");
++              }
++      }
++      mutex_unlock(&pl->state_mutex);
++}
++
++static void phylink_run_resolve(struct phylink *pl)
++{
++      if (!pl->phylink_disable_state)
++              queue_work(system_power_efficient_wq, &pl->resolve);
++}
++
++struct phylink *phylink_create(struct net_device *ndev, struct device_node *np,
++      phy_interface_t iface, const struct phylink_mac_ops *ops)
++{
++      struct phylink *pl;
++      int ret;
++
++      pl = kzalloc(sizeof(*pl), GFP_KERNEL);
++      if (!pl)
++              return ERR_PTR(-ENOMEM);
++
++      mutex_init(&pl->state_mutex);
++      mutex_init(&pl->config_mutex);
++      INIT_WORK(&pl->resolve, phylink_resolve);
++      pl->netdev = ndev;
++      pl->link_interface = iface;
++      pl->link_port_support = SUPPORTED_MII;
++      pl->link_port = PORT_MII;
++      pl->ops = ops;
++      __set_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state);
++
++      ret = phylink_parse_fixedlink(pl, np);
++      if (ret < 0) {
++              kfree(pl);
++              return ERR_PTR(ret);
++      }
++
++      ret = phylink_parse_managed(pl, np);
++      if (ret < 0) {
++              kfree(pl);
++              return ERR_PTR(ret);
++      }
++
++      ret = phylink_get_support(pl, pl->link_an_mode);
++      if (ret) {
++              kfree(pl);
++              return ERR_PTR(ret);
++      }
++
++      return pl;
++}
++EXPORT_SYMBOL_GPL(phylink_create);
++
++void phylink_destroy(struct phylink *pl)
++{
++      cancel_work_sync(&pl->resolve);
++      kfree(pl);
++}
++EXPORT_SYMBOL_GPL(phylink_destroy);
++
++void phylink_phy_change(struct phy_device *phy, bool up, bool do_carrier)
++{
++      struct phylink *pl = phy->phylink;
++
++      mutex_lock(&pl->state_mutex);
++      pl->phy_state.speed = phy->speed;
++      pl->phy_state.duplex = phy->duplex;
++      pl->phy_state.pause = MLO_PAUSE_NONE;
++      if (phy->pause)
++              pl->phy_state.pause |= MLO_PAUSE_SYM;
++      if (phy->asym_pause)
++              pl->phy_state.pause |= MLO_PAUSE_ASYM;
++      pl->phy_state.link = up;
++      mutex_unlock(&pl->state_mutex);
++
++      phylink_run_resolve(pl);
++
++      netdev_dbg(pl->netdev, "phy link %s\n", up ? "up" : "down");
++}
++
++static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy)
++{
++      mutex_lock(&pl->config_mutex);
++      phy->phylink = pl;
++      phy->phy_link_change = phylink_phy_change;
++
++      netdev_info(pl->netdev,
++                  "PHY [%s] driver [%s]\n", dev_name(&phy->dev),
++                  phy->drv->name);
++
++      mutex_lock(&pl->state_mutex);
++      pl->phydev = phy;
++
++      /* Restrict the phy advertisment to the union of the PHY and
++       * MAC-level advert.
++       */
++      phy->advertising &= ADVERTISED_INTERFACES |
++                          pl->link_config.advertising;
++      mutex_unlock(&pl->state_mutex);
++
++      phy_start_machine(phy);
++      if (phy->irq > 0)
++              phy_start_interrupts(phy);
++
++      mutex_unlock(&pl->config_mutex);
++
++      return 0;
++}
++
++int phylink_connect_phy(struct phylink *pl, struct phy_device *phy)
++{
++      int ret;
++
++      ret = phy_attach_direct(pl->netdev, phy, 0, pl->link_interface);
++      if (ret)
++              return ret;
++
++      ret = phylink_bringup_phy(pl, phy);
++      if (ret)
++              phy_detach(phy);
++
++      return ret;
++}
++EXPORT_SYMBOL_GPL(phylink_connect_phy);
++
++int phylink_of_phy_connect(struct phylink *pl, struct device_node *dn)
++{
++      struct device_node *phy_node;
++      struct phy_device *phy_dev;
++      int ret;
++
++      /* Fixed links are handled without needing a PHY */
++      if (pl->link_an_mode == MLO_AN_FIXED)
++              return 0;
++
++      phy_node = of_parse_phandle(dn, "phy-handle", 0);
++      if (!phy_node)
++              phy_node = of_parse_phandle(dn, "phy", 0);
++      if (!phy_node)
++              phy_node = of_parse_phandle(dn, "phy-device", 0);
++
++      if (!phy_node) {
++              if (pl->link_an_mode == MLO_AN_PHY) {
++                      netdev_err(pl->netdev, "unable to find PHY node\n");
++                      return -ENODEV;
++              }
++              return 0;
++      }
++
++      phy_dev = of_phy_attach(pl->netdev, phy_node, 0, pl->link_interface);
++      /* We're done with the phy_node handle */
++      of_node_put(phy_node);
++
++      if (!phy_dev)
++              return -ENODEV;
++
++      ret = phylink_bringup_phy(pl, phy_dev);
++      if (ret)
++              phy_detach(phy_dev);
++
++      return ret;
++}
++EXPORT_SYMBOL_GPL(phylink_of_phy_connect);
++
++void phylink_disconnect_phy(struct phylink *pl)
++{
++      struct phy_device *phy;
++
++      mutex_lock(&pl->config_mutex);
++      phy = pl->phydev;
++
++      mutex_lock(&pl->state_mutex);
++      pl->phydev = NULL;
++      mutex_unlock(&pl->state_mutex);
++      flush_work(&pl->resolve);
++
++      if (phy)
++              phy_disconnect(phy);
++
++      mutex_unlock(&pl->config_mutex);
++}
++EXPORT_SYMBOL_GPL(phylink_disconnect_phy);
++
++void phylink_mac_change(struct phylink *pl, bool up)
++{
++      phylink_run_resolve(pl);
++      netdev_dbg(pl->netdev, "mac link %s\n", up ? "up" : "down");
++}
++EXPORT_SYMBOL_GPL(phylink_mac_change);
++
++void phylink_start(struct phylink *pl)
++{
++      mutex_lock(&pl->config_mutex);
++
++      netdev_info(pl->netdev, "configuring for %s link mode\n",
++                  phylink_an_mode_str(pl->link_an_mode));
++
++      /* Apply the link configuration to the MAC when starting. This allows
++       * a fixed-link to start with the correct parameters, and also
++       * ensures that we set the appropriate advertisment for Serdes links.
++       */
++      phylink_mac_config(pl, &pl->link_config);
++
++      clear_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state);
++      phylink_run_resolve(pl);
++
++      if (pl->phydev)
++              phy_start(pl->phydev);
++
++      mutex_unlock(&pl->config_mutex);
++}
++EXPORT_SYMBOL_GPL(phylink_start);
++
++void phylink_stop(struct phylink *pl)
++{
++      mutex_lock(&pl->config_mutex);
++
++      if (pl->phydev)
++              phy_stop(pl->phydev);
++
++      set_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state);
++      flush_work(&pl->resolve);
++
++      pl->mac_link_up = false;
++
++      mutex_unlock(&pl->config_mutex);
++}
++EXPORT_SYMBOL_GPL(phylink_stop);
++
++static void phylink_get_ethtool(const struct phylink_link_state *state,
++                              struct ethtool_cmd *cmd)
++{
++      cmd->supported &= SUPPORTED_INTERFACES;
++      cmd->supported |= state->supported;
++      cmd->advertising &= ADVERTISED_INTERFACES;
++      cmd->advertising |= state->advertising;
++      ethtool_cmd_speed_set(cmd, state->speed);
++      cmd->duplex = state->duplex;
++
++      cmd->autoneg = state->an_enabled ? AUTONEG_ENABLE : AUTONEG_DISABLE;
++}
++
++static int phylink_ethtool_gset(struct phylink *pl, struct ethtool_cmd *cmd)
++{
++      struct phylink_link_state link_state;
++      int ret;
++
++      if (pl->phydev) {
++              ret = phy_ethtool_gset(pl->phydev, cmd);
++              if (ret)
++                      return ret;
++
++              cmd->supported &= SUPPORTED_INTERFACES |
++                                pl->link_config.supported;
++      } else {
++              cmd->supported = pl->link_port_support;
++              cmd->transceiver = XCVR_EXTERNAL;
++              cmd->port = pl->link_port;
++      }
++
++      switch (pl->link_an_mode) {
++      case MLO_AN_FIXED:
++              /* We are using fixed settings. Report these as the
++               * current link settings - and note that these also
++               * represent the supported speeds/duplex/pause modes.
++               */
++              phylink_get_fixed_state(pl, &link_state);
++              phylink_get_ethtool(&link_state, cmd);
++              break;
++
++      case MLO_AN_SGMII:
++              /* If there is a phy attached, then use the reported
++               * settings from the phy with no modification.
++               */
++              if (pl->phydev)
++                      break;
++
++      case MLO_AN_8023Z:
++              phylink_get_mac_state(pl, &link_state);
++
++              /* The MAC is reporting the link results from its own PCS
++               * layer via in-band status. Report these as the current
++               * link settings.
++               */
++              phylink_get_ethtool(&link_state, cmd);
++              break;
++      }
++
++      return 0;
++}
++
++int phylink_ethtool_get_settings(struct phylink *pl, struct ethtool_cmd *cmd)
++{
++      int ret;
++
++      mutex_lock(&pl->config_mutex);
++      ret = phylink_ethtool_gset(pl, cmd);
++      mutex_unlock(&pl->config_mutex);
++
++      return ret;
++}
++EXPORT_SYMBOL_GPL(phylink_ethtool_get_settings);
++
++static int phylink_ethtool_sset(struct phylink *pl, struct ethtool_cmd *cmd)
++{
++      u32 supported;
++      int ret;
++
++      /* Calculate the union of the MAC support and attached phy support */
++      supported = pl->link_config.supported;
++      if (pl->phydev)
++              supported &= pl->phydev->supported;
++
++      /* Mask out unsupported advertisments */
++      cmd->advertising &= supported;
++
++      /* FIXME: should we reject autoneg if phy/mac does not support it? */
++
++      if (cmd->autoneg == AUTONEG_DISABLE) {
++              /* Autonegotiation disabled, validate speed and duplex */
++              if (cmd->duplex != DUPLEX_HALF && cmd->duplex != DUPLEX_FULL)
++                      return -EINVAL;
++
++              /* FIXME: validate speed/duplex against supported */
++
++              cmd->advertising &= ~ADVERTISED_Autoneg;
++      } else {
++              /* Autonegotiation enabled, validate advertisment */
++              /* FIXME: shouldn't we ensure there's some duplex/speeds set */
++              if (cmd->advertising == 0)
++                      return -EINVAL;
++
++              cmd->advertising |= ADVERTISED_Autoneg;
++      }
++
++      /* If we have a fixed link (as specified by firmware), refuse
++       * to enable autonegotiation, or change link parameters.
++       */
++      if (pl->link_an_mode == MLO_AN_FIXED) {
++              if (cmd->autoneg != AUTONEG_DISABLE ||
++                  ethtool_cmd_speed(cmd) != pl->link_config.speed ||
++                  cmd->duplex != pl->link_config.duplex)
++                      return -EINVAL;
++      }
++
++      /* If we have a PHY, configure the phy */
++      if (pl->phydev) {
++              ret = phy_ethtool_sset(pl->phydev, cmd);
++              if (ret)
++                      return ret;
++      }
++
++      mutex_lock(&pl->state_mutex);
++      /* Configure the MAC to match the new settings */
++      pl->link_config.advertising = cmd->advertising;
++      pl->link_config.speed = cmd->speed;
++      pl->link_config.duplex = cmd->duplex;
++      pl->link_config.an_enabled = cmd->autoneg != AUTONEG_DISABLE;
++
++      phylink_mac_config(pl, &pl->link_config);
++      phylink_mac_an_restart(pl);
++      mutex_unlock(&pl->state_mutex);
++
++      return ret;
++}
++
++int phylink_ethtool_set_settings(struct phylink *pl, struct ethtool_cmd *cmd)
++{
++      int ret;
++
++      if (cmd->autoneg != AUTONEG_DISABLE && cmd->autoneg != AUTONEG_ENABLE)
++              return -EINVAL;
++
++      mutex_lock(&pl->config_mutex);
++      ret = phylink_ethtool_sset(pl, cmd);
++      mutex_unlock(&pl->config_mutex);
++
++      return ret;
++}
++EXPORT_SYMBOL_GPL(phylink_ethtool_set_settings);
++
++/* This emulates MII registers for a fixed-mode phy operating as per the
++ * passed in state. "aneg" defines if we report negotiation is possible.
++ *
++ * FIXME: should deal with negotiation state too.
++ */
++static int phylink_mii_emul_read(struct net_device *ndev, unsigned int reg,
++                               struct phylink_link_state *state, bool aneg)
++{
++      struct fixed_phy_status fs;
++      int val;
++
++      fs.link = state->link;
++      fs.speed = state->speed;
++      fs.duplex = state->duplex;
++      fs.pause = state->pause & MLO_PAUSE_SYM;
++      fs.asym_pause = state->pause & MLO_PAUSE_ASYM;
++
++      val = swphy_read_reg(reg, &fs);
++      if (reg == MII_BMSR) {
++              if (!state->an_complete)
++                      val &= ~BMSR_ANEGCOMPLETE;
++              if (!aneg)
++                      val &= ~BMSR_ANEGCAPABLE;
++      }
++      return val;
++}
++
++static int phylink_mii_read(struct phylink *pl, unsigned int phy_id,
++                          unsigned int reg)
++{
++      struct phylink_link_state state;
++      int val = 0xffff;
++
++      if (pl->phydev && pl->phydev->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");
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -147,6 +147,7 @@ static inline const char *phy_modes(phy_
+ #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;
+--- /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
diff --git a/target/linux/mvebu/patches-4.4/131-phylink-add-hooks-for-SFP-support.patch b/target/linux/mvebu/patches-4.4/131-phylink-add-hooks-for-SFP-support.patch
new file mode 100644 (file)
index 0000000..64a1c1f
--- /dev/null
@@ -0,0 +1,156 @@
+From 0a0c4b3dd4f34df4532f254a5940b520015d766f Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Thu, 24 Sep 2015 11:01:13 +0100
+Subject: [PATCH 719/744] phylink: add hooks for SFP support
+
+Add support to phylink for SFP, which needs to control and configure
+the ethernet MAC link state.  Specifically, SFP needs to:
+
+1. set the negotiation mode between SGMII and 1000base-X
+2. attach and detach the module PHY
+3. prevent the link coming up when errors are reported
+
+In the absence of a PHY, we also need to set the ethtool port type
+according to the module plugged in.
+
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ drivers/net/phy/phylink.c | 82 +++++++++++++++++++++++++++++++++++++++++++++++
+ include/linux/phylink.h   |  6 ++++
+ 2 files changed, 88 insertions(+)
+
+--- 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;
+@@ -341,12 +347,20 @@ struct phylink *phylink_create(struct ne
+               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);
+ }
+@@ -813,4 +827,72 @@ int phylink_mii_ioctl(struct phylink *pl
+ }
+ EXPORT_SYMBOL_GPL(phylink_mii_ioctl);
++
++
++void phylink_disable(struct phylink *pl)
++{
++      set_bit(PHYLINK_DISABLE_LINK, &pl->phylink_disable_state);
++      flush_work(&pl->resolve);
++
++      netif_carrier_off(pl->netdev);
++}
++EXPORT_SYMBOL_GPL(phylink_disable);
++
++void phylink_enable(struct phylink *pl)
++{
++      clear_bit(PHYLINK_DISABLE_LINK, &pl->phylink_disable_state);
++      phylink_run_resolve(pl);
++}
++EXPORT_SYMBOL_GPL(phylink_enable);
++
++void phylink_set_link_port(struct phylink *pl, u32 support, u8 port)
++{
++      WARN_ON(support & ~SUPPORTED_INTERFACES);
++
++      mutex_lock(&pl->config_mutex);
++      pl->link_port_support = support;
++      pl->link_port = port;
++      mutex_unlock(&pl->config_mutex);
++}
++EXPORT_SYMBOL_GPL(phylink_set_link_port);
++
++int phylink_set_link_an_mode(struct phylink *pl, unsigned int mode)
++{
++      int ret = 0;
++
++      mutex_lock(&pl->config_mutex);
++      if (pl->link_an_mode != mode) {
++              ret = phylink_get_support(pl, mode);
++              if (ret == 0) {
++                      if (!test_bit(PHYLINK_DISABLE_STOPPED,
++                                    &pl->phylink_disable_state))
++                              phylink_mac_config(pl, &pl->link_config);
++
++                      netdev_info(pl->netdev, "switched to %s link mode\n",
++                                  phylink_an_mode_str(mode));
++              }
++      }
++      mutex_unlock(&pl->config_mutex);
++
++      return ret;
++}
++EXPORT_SYMBOL_GPL(phylink_set_link_an_mode);
++
++struct phylink *phylink_lookup_by_netdev(struct net_device *ndev)
++{
++      struct phylink *pl, *found = NULL;
++
++      mutex_lock(&phylink_mutex);
++      list_for_each_entry(pl, &phylinks, node)
++              if (pl->netdev == ndev) {
++                      found = pl;
++                      break;
++              }
++
++      mutex_unlock(&phylink_mutex);
++
++      return found;
++}
++EXPORT_SYMBOL_GPL(phylink_lookup_by_netdev);
++
+ MODULE_LICENSE("GPL");
+--- a/include/linux/phylink.h
++++ b/include/linux/phylink.h
+@@ -67,4 +67,10 @@ int phylink_ethtool_get_settings(struct
+ 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
diff --git a/target/linux/mvebu/patches-4.4/132-sfp-add-phylink-based-SFP-module-support.patch b/target/linux/mvebu/patches-4.4/132-sfp-add-phylink-based-SFP-module-support.patch
new file mode 100644 (file)
index 0000000..07b1766
--- /dev/null
@@ -0,0 +1,1382 @@
+From bf0a000960234c0e773fadea47240c3cda0cab02 Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Sat, 12 Sep 2015 18:43:39 +0100
+Subject: [PATCH 720/744] sfp: add phylink based SFP module support
+
+Add support for SFP hotpluggable modules via phylink.  This supports
+both copper and optical SFP modules, which require different Serdes
+modes in order to properly negotiate the link.
+
+Optical SFP modules typically require the Serdes link to be talking
+1000base-X mode - this is the gigabit ethernet mode defined by the
+802.3 standard.
+
+Copper SFP modules typically integrate a PHY in the module to convert
+from Serdes to copper, and the PHY will be configured by the vendor
+to either present a 1000base-X Serdes link (for fixed 1000base-T) or
+a SGMII Serdes link.  However, this is vendor defined, so we instead
+detect the PHY, switch the link to SGMII mode, and use traditional
+PHY based negotiation.
+
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ drivers/net/phy/Kconfig  |   5 +
+ drivers/net/phy/Makefile |   1 +
+ drivers/net/phy/sfp.c    | 986 +++++++++++++++++++++++++++++++++++++++++++++++
+ include/linux/sfp.h      | 339 ++++++++++++++++
+ 4 files changed, 1331 insertions(+)
+ create mode 100644 drivers/net/phy/sfp.c
+ create mode 100644 include/linux/sfp.h
+
+--- a/drivers/net/phy/Kconfig
++++ b/drivers/net/phy/Kconfig
+@@ -225,6 +225,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
+--- a/drivers/net/phy/Makefile
++++ b/drivers/net/phy/Makefile
+@@ -61,3 +61,4 @@ obj-$(CONFIG_MDIO_MOXART)    += mdio-moxart
+ 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
+--- /dev/null
++++ b/drivers/net/phy/sfp.c
+@@ -0,0 +1,986 @@
++#include <linux/delay.h>
++#include <linux/gpio.h>
++#include <linux/i2c.h>
++#include <linux/interrupt.h>
++#include <linux/jiffies.h>
++#include <linux/module.h>
++#include <linux/mutex.h>
++#include <linux/netdevice.h>
++#include <linux/of.h>
++#include <linux/of_net.h>
++#include <linux/phylink.h>
++#include <linux/platform_device.h>
++#include <linux/sfp.h>
++#include <linux/slab.h>
++#include <linux/workqueue.h>
++
++#include "mdio-i2c.h"
++#include "swphy.h"
++
++enum {
++      GPIO_MODDEF0,
++      GPIO_LOS,
++      GPIO_TX_FAULT,
++      GPIO_TX_DISABLE,
++      GPIO_RATE_SELECT,
++      GPIO_MAX,
++
++      SFP_F_PRESENT = BIT(GPIO_MODDEF0),
++      SFP_F_LOS = BIT(GPIO_LOS),
++      SFP_F_TX_FAULT = BIT(GPIO_TX_FAULT),
++      SFP_F_TX_DISABLE = BIT(GPIO_TX_DISABLE),
++      SFP_F_RATE_SELECT = BIT(GPIO_RATE_SELECT),
++
++      SFP_E_INSERT = 0,
++      SFP_E_REMOVE,
++      SFP_E_DEV_DOWN,
++      SFP_E_DEV_UP,
++      SFP_E_TX_FAULT,
++      SFP_E_TX_CLEAR,
++      SFP_E_LOS_HIGH,
++      SFP_E_LOS_LOW,
++      SFP_E_TIMEOUT,
++
++      SFP_MOD_EMPTY = 0,
++      SFP_MOD_PROBE,
++      SFP_MOD_PRESENT,
++      SFP_MOD_ERROR,
++
++      SFP_DEV_DOWN = 0,
++      SFP_DEV_UP,
++
++      SFP_S_DOWN = 0,
++      SFP_S_INIT,
++      SFP_S_WAIT_LOS,
++      SFP_S_LINK_UP,
++      SFP_S_TX_FAULT,
++      SFP_S_REINIT,
++      SFP_S_TX_DISABLE,
++};
++
++static const char *gpio_of_names[] = {
++      "moddef0",
++      "los",
++      "tx-fault",
++      "tx-disable",
++      "rate-select",
++};
++
++static const enum gpiod_flags gpio_flags[] = {
++      GPIOD_IN,
++      GPIOD_IN,
++      GPIOD_IN,
++      GPIOD_ASIS,
++      GPIOD_ASIS,
++};
++
++#define T_INIT_JIFFIES        msecs_to_jiffies(300)
++#define T_RESET_US    10
++#define T_FAULT_RECOVER       msecs_to_jiffies(1000)
++
++/* SFP module presence detection is poor: the three MOD DEF signals are
++ * the same length on the PCB, which means it's possible for MOD DEF 0 to
++ * connect before the I2C bus on MOD DEF 1/2.  Try to work around this
++ * design bug by waiting 50ms before probing, and then retry every 250ms.
++ */
++#define T_PROBE_INIT  msecs_to_jiffies(50)
++#define T_PROBE_RETRY msecs_to_jiffies(250)
++
++/*
++ * SFP modules appear to always have their PHY configured for bus address
++ * 0x56 (which with mdio-i2c, translates to a PHY address of 22).
++ */
++#define SFP_PHY_ADDR  22
++
++/*
++ * Give this long for the PHY to reset.
++ */
++#define T_PHY_RESET_MS        50
++
++static DEFINE_MUTEX(sfp_mutex);
++
++struct sfp {
++      struct device *dev;
++      struct i2c_adapter *i2c;
++      struct mii_bus *i2c_mii;
++      struct net_device *ndev;
++      struct phylink *phylink;
++      struct phy_device *mod_phy;
++
++      unsigned int (*get_state)(struct sfp *);
++      void (*set_state)(struct sfp *, unsigned int);
++      int (*read)(struct sfp *, bool, u8, void *, size_t);
++
++      struct gpio_desc *gpio[GPIO_MAX];
++
++      unsigned int state;
++      struct delayed_work poll;
++      struct delayed_work timeout;
++      struct mutex sm_mutex;
++      unsigned char sm_mod_state;
++      unsigned char sm_dev_state;
++      unsigned short sm_state;
++      unsigned int sm_retries;
++
++      struct sfp_eeprom_id id;
++
++      struct notifier_block netdev_nb;
++};
++
++static unsigned long poll_jiffies;
++
++static unsigned int sfp_gpio_get_state(struct sfp *sfp)
++{
++      unsigned int i, state, v;
++
++      for (i = state = 0; i < GPIO_MAX; i++) {
++              if (gpio_flags[i] != GPIOD_IN || !sfp->gpio[i])
++                      continue;
++
++              v = gpiod_get_value_cansleep(sfp->gpio[i]);
++              if (v)
++                      state |= BIT(i);
++      }
++
++      return state;
++}
++
++static void sfp_gpio_set_state(struct sfp *sfp, unsigned int state)
++{
++      if (state & SFP_F_PRESENT) {
++              /* If the module is present, drive the signals */
++              if (sfp->gpio[GPIO_TX_DISABLE])
++                      gpiod_direction_output(sfp->gpio[GPIO_TX_DISABLE],
++                                              state & SFP_F_TX_DISABLE);
++              if (state & SFP_F_RATE_SELECT)
++                      gpiod_direction_output(sfp->gpio[GPIO_RATE_SELECT],
++                                              state & SFP_F_RATE_SELECT);
++      } else {
++              /* Otherwise, let them float to the pull-ups */
++              if (sfp->gpio[GPIO_TX_DISABLE])
++                      gpiod_direction_input(sfp->gpio[GPIO_TX_DISABLE]);
++              if (state & SFP_F_RATE_SELECT)
++                      gpiod_direction_input(sfp->gpio[GPIO_RATE_SELECT]);
++      }
++}
++
++static int sfp__i2c_read(struct i2c_adapter *i2c, u8 bus_addr, u8 dev_addr,
++      void *buf, size_t len)
++{
++      struct i2c_msg msgs[2];
++      int ret;
++
++      msgs[0].addr = bus_addr;
++      msgs[0].flags = 0;
++      msgs[0].len = 1;
++      msgs[0].buf = &dev_addr;
++      msgs[1].addr = bus_addr;
++      msgs[1].flags = I2C_M_RD;
++      msgs[1].len = len;
++      msgs[1].buf = buf;
++
++      ret = i2c_transfer(i2c, msgs, ARRAY_SIZE(msgs));
++      if (ret < 0)
++              return ret;
++
++      return ret == ARRAY_SIZE(msgs) ? len : 0;
++}
++
++static int sfp_i2c_read(struct sfp *sfp, bool a2, u8 addr, void *buf,
++      size_t len)
++{
++      return sfp__i2c_read(sfp->i2c, a2 ? 0x51 : 0x50, addr, buf, len);
++}
++
++static int sfp_i2c_configure(struct sfp *sfp, struct i2c_adapter *i2c)
++{
++      struct mii_bus *i2c_mii;
++      int ret;
++
++      if (!i2c_check_functionality(i2c, I2C_FUNC_I2C))
++              return -EINVAL;
++
++      sfp->i2c = i2c;
++      sfp->read = sfp_i2c_read;
++
++      i2c_mii = mdio_i2c_alloc(sfp->dev, i2c);
++      if (IS_ERR(i2c_mii))
++              return PTR_ERR(i2c_mii);
++
++      i2c_mii->name = "SFP I2C Bus";
++      i2c_mii->phy_mask = ~0;
++
++      ret = mdiobus_register(i2c_mii);
++      if (ret < 0) {
++              mdiobus_free(i2c_mii);
++              return ret;
++      }
++
++      sfp->i2c_mii = i2c_mii;
++
++      return 0;
++}
++
++
++/* Interface */
++static unsigned int sfp_get_state(struct sfp *sfp)
++{
++      return sfp->get_state(sfp);
++}
++
++static void sfp_set_state(struct sfp *sfp, unsigned int state)
++{
++      sfp->set_state(sfp, state);
++}
++
++static int sfp_read(struct sfp *sfp, bool a2, u8 addr, void *buf, size_t len)
++{
++      return sfp->read(sfp, a2, addr, buf, len);
++}
++
++static unsigned int sfp_check(void *buf, size_t len)
++{
++      u8 *p, check;
++
++      for (p = buf, check = 0; len; p++, len--)
++              check += *p;
++
++      return check;
++}
++
++/* Helpers */
++static void sfp_module_tx_disable(struct sfp *sfp)
++{
++      dev_dbg(sfp->dev, "tx disable %u -> %u\n",
++              sfp->state & SFP_F_TX_DISABLE ? 1 : 0, 1);
++      sfp->state |= SFP_F_TX_DISABLE;
++      sfp_set_state(sfp, sfp->state);
++}
++
++static void sfp_module_tx_enable(struct sfp *sfp)
++{
++      dev_dbg(sfp->dev, "tx disable %u -> %u\n",
++              sfp->state & SFP_F_TX_DISABLE ? 1 : 0, 0);
++      sfp->state &= ~SFP_F_TX_DISABLE;
++      sfp_set_state(sfp, sfp->state);
++}
++
++static void sfp_module_tx_fault_reset(struct sfp *sfp)
++{
++      unsigned int state = sfp->state;
++
++      if (state & SFP_F_TX_DISABLE)
++              return;
++
++      sfp_set_state(sfp, state | SFP_F_TX_DISABLE);
++
++      udelay(T_RESET_US);
++
++      sfp_set_state(sfp, state);
++}
++
++/* SFP state machine */
++static void sfp_sm_set_timer(struct sfp *sfp, unsigned int timeout)
++{
++      if (timeout)
++              mod_delayed_work(system_power_efficient_wq, &sfp->timeout,
++                               timeout);
++      else
++              cancel_delayed_work(&sfp->timeout);
++}
++
++static void sfp_sm_next(struct sfp *sfp, unsigned int state,
++                      unsigned int timeout)
++{
++      sfp->sm_state = state;
++      sfp_sm_set_timer(sfp, timeout);
++}
++
++static void sfp_sm_ins_next(struct sfp *sfp, unsigned int state, unsigned int timeout)
++{
++      sfp->sm_mod_state = state;
++      sfp_sm_set_timer(sfp, timeout);
++}
++
++static void sfp_sm_phy_detach(struct sfp *sfp)
++{
++      phy_stop(sfp->mod_phy);
++      if (sfp->phylink)
++              phylink_disconnect_phy(sfp->phylink);
++      phy_device_remove(sfp->mod_phy);
++      phy_device_free(sfp->mod_phy);
++      sfp->mod_phy = NULL;
++}
++
++static void sfp_sm_probe_phy(struct sfp *sfp)
++{
++      struct phy_device *phy;
++      int err;
++
++      msleep(T_PHY_RESET_MS);
++
++      phy = mdiobus_scan(sfp->i2c_mii, SFP_PHY_ADDR);
++      if (IS_ERR(phy)) {
++              dev_err(sfp->dev, "mdiobus scan returned %ld\n", PTR_ERR(phy));
++              return;
++      }
++      if (!phy) {
++              dev_info(sfp->dev, "no PHY detected\n");
++              return;
++      }
++
++      err = phylink_connect_phy(sfp->phylink, phy);
++      if (err) {
++              phy_device_remove(phy);
++              phy_device_free(phy);
++              dev_err(sfp->dev, "phylink_connect_phy failed: %d\n", err);
++              return;
++      }
++
++      sfp->mod_phy = phy;
++      phy_start(phy);
++}
++
++static void sfp_sm_link_up(struct sfp *sfp)
++{
++      if (sfp->phylink)
++              phylink_enable(sfp->phylink);
++
++      sfp_sm_next(sfp, SFP_S_LINK_UP, 0);
++}
++
++static void sfp_sm_link_down(struct sfp *sfp)
++{
++      if (sfp->phylink)
++              phylink_disable(sfp->phylink);
++}
++
++static void sfp_sm_link_check_los(struct sfp *sfp)
++{
++      unsigned int los = sfp->state & SFP_F_LOS;
++
++      /* FIXME: what if neither SFP_OPTIONS_LOS_INVERTED nor
++       * SFP_OPTIONS_LOS_NORMAL are set?  For now, we assume
++       * the same as SFP_OPTIONS_LOS_NORMAL set.
++       */
++      if (sfp->id.ext.options & SFP_OPTIONS_LOS_INVERTED)
++              los ^= SFP_F_LOS;
++
++      if (los)
++              sfp_sm_next(sfp, SFP_S_WAIT_LOS, 0);
++      else
++              sfp_sm_link_up(sfp);
++}
++
++static void sfp_sm_fault(struct sfp *sfp, bool warn)
++{
++      if (sfp->sm_retries && !--sfp->sm_retries) {
++              dev_err(sfp->dev, "module persistently indicates fault, disabling\n");
++              sfp_sm_next(sfp, SFP_S_TX_DISABLE, 0);
++      } else {
++              if (warn)
++                      dev_err(sfp->dev, "module transmit fault indicated\n");
++
++              sfp_sm_next(sfp, SFP_S_TX_FAULT, T_FAULT_RECOVER);
++      }
++}
++
++static void sfp_sm_mod_init(struct sfp *sfp)
++{
++      sfp_module_tx_enable(sfp);
++
++      /* Wait t_init before indicating that the link is up, provided the
++       * current state indicates no TX_FAULT.  If TX_FAULT clears before
++       * this time, that's fine too.
++       */
++      sfp_sm_next(sfp, SFP_S_INIT, T_INIT_JIFFIES);
++      sfp->sm_retries = 5;
++
++      if (sfp->phylink) {
++              /* Setting the serdes link mode is guesswork: there's no
++               * field in the EEPROM which indicates what mode should
++               * be used.
++               *
++               * If it's a gigabit-only fiber module, it probably does
++               * not have a PHY, so switch to 802.3z negotiation mode.
++               * Otherwise, switch to SGMII mode (which is required to
++               * support non-gigabit speeds) and probe for a PHY.
++               */
++              if (!sfp->id.base.e1000_base_t &&
++                  !sfp->id.base.e100_base_lx &&
++                  !sfp->id.base.e100_base_fx) {
++                      phylink_set_link_an_mode(sfp->phylink, MLO_AN_8023Z);
++              } else {
++                      phylink_set_link_an_mode(sfp->phylink, MLO_AN_SGMII);
++                      sfp_sm_probe_phy(sfp);
++              }
++      }
++}
++
++static int sfp_sm_mod_probe(struct sfp *sfp)
++{
++      /* SFP module inserted - read I2C data */
++      struct sfp_eeprom_id id;
++      char vendor[17];
++      char part[17];
++      char sn[17];
++      char date[9];
++      char rev[5];
++      u8 check;
++      int err;
++
++      err = sfp_read(sfp, false, 0, &id, sizeof(id));
++      if (err < 0) {
++              dev_err(sfp->dev, "failed to read EEPROM: %d\n", err);
++              return -EAGAIN;
++      }
++
++      /* Validate the checksum over the base structure */
++      check = sfp_check(&id.base, sizeof(id.base) - 1);
++      if (check != id.base.cc_base) {
++              dev_err(sfp->dev,
++                      "EEPROM base structure checksum failure: 0x%02x\n",
++                      check);
++              return -EINVAL;
++      }
++
++      check = sfp_check(&id.ext, sizeof(id.ext) - 1);
++      if (check != id.ext.cc_ext) {
++              dev_err(sfp->dev,
++                      "EEPROM extended structure checksum failure: 0x%02x\n",
++                      check);
++              memset(&id.ext, 0, sizeof(id.ext));
++      }
++
++      sfp->id = id;
++
++      memcpy(vendor, sfp->id.base.vendor_name, 16);
++      vendor[16] = '\0';
++      memcpy(part, sfp->id.base.vendor_pn, 16);
++      part[16] = '\0';
++      memcpy(rev, sfp->id.base.vendor_rev, 4);
++      rev[4] = '\0';
++      memcpy(sn, sfp->id.ext.vendor_sn, 16);
++      sn[16] = '\0';
++      memcpy(date, sfp->id.ext.datecode, 8);
++      date[8] = '\0';
++
++      dev_info(sfp->dev, "module %s %s rev %s sn %s dc %s\n", vendor, part, rev, sn, date);
++
++      /* We only support SFP modules, not the legacy GBIC modules. */
++      if (sfp->id.base.phys_id != SFP_PHYS_ID_SFP ||
++          sfp->id.base.phys_ext_id != SFP_PHYS_EXT_ID_SFP) {
++              dev_err(sfp->dev, "module is not SFP - phys id 0x%02x 0x%02x\n",
++                      sfp->id.base.phys_id, sfp->id.base.phys_ext_id);
++              return -EINVAL;
++      }
++
++      /*
++       * What isn't clear from the SFP documentation is whether this
++       * specifies the encoding expected on the TD/RD lines, or whether
++       * the TD/RD lines are always 8b10b encoded, but the transceiver
++       * converts.  Eg, think of a copper SFP supporting 1G/100M/10M
++       * ethernet: this requires 8b10b encoding for 1G, 4b5b for 100M,
++       * and manchester for 10M.
++       */
++      /* 1Gbit ethernet requires 8b10b encoding */
++      if (sfp->id.base.encoding != SFP_ENCODING_8B10B) {
++              dev_err(sfp->dev, "module does not support 8B10B encoding\n");
++              return -EINVAL;
++      }
++
++      if (sfp->phylink) {
++              u32 support;
++              u8 port;
++
++              if (sfp->id.base.e1000_base_t) {
++                      support = SUPPORTED_TP;
++                      port = PORT_TP;
++              } else {
++                      support = SUPPORTED_FIBRE;
++                      port = PORT_FIBRE;
++              }
++              phylink_set_link_port(sfp->phylink, support, port);
++      }
++
++      return 0;
++}
++
++static void sfp_sm_mod_remove(struct sfp *sfp)
++{
++      if (sfp->mod_phy)
++              sfp_sm_phy_detach(sfp);
++
++      sfp_module_tx_disable(sfp);
++
++      memset(&sfp->id, 0, sizeof(sfp->id));
++
++      dev_info(sfp->dev, "module removed\n");
++}
++
++static void sfp_sm_event(struct sfp *sfp, unsigned int event)
++{
++      mutex_lock(&sfp->sm_mutex);
++
++      dev_dbg(sfp->dev, "SM: enter %u:%u:%u event %u\n",
++              sfp->sm_mod_state, sfp->sm_dev_state, sfp->sm_state, event);
++
++      /* This state machine tracks the insert/remove state of
++       * the module, and handles probing the on-board EEPROM.
++       */
++      switch (sfp->sm_mod_state) {
++      default:
++              if (event == SFP_E_INSERT) {
++                      sfp_module_tx_disable(sfp);
++                      sfp_sm_ins_next(sfp, SFP_MOD_PROBE, T_PROBE_INIT);
++              }
++              break;
++
++      case SFP_MOD_PROBE:
++              if (event == SFP_E_REMOVE) {
++                      sfp_sm_ins_next(sfp, SFP_MOD_EMPTY, 0);
++              } else if (event == SFP_E_TIMEOUT) {
++                      int err = sfp_sm_mod_probe(sfp);
++
++                      if (err == 0)
++                              sfp_sm_ins_next(sfp, SFP_MOD_PRESENT, 0);
++                      else if (err == -EAGAIN)
++                              sfp_sm_set_timer(sfp, T_PROBE_RETRY);
++                      else
++                              sfp_sm_ins_next(sfp, SFP_MOD_ERROR, 0);
++              }
++              break;
++
++      case SFP_MOD_PRESENT:
++      case SFP_MOD_ERROR:
++              if (event == SFP_E_REMOVE) {
++                      sfp_sm_mod_remove(sfp);
++                      sfp_sm_ins_next(sfp, SFP_MOD_EMPTY, 0);
++              }
++              break;
++      }
++
++      /* This state machine tracks the netdev up/down state */
++      switch (sfp->sm_dev_state) {
++      default:
++              if (event == SFP_E_DEV_UP)
++                      sfp->sm_dev_state = SFP_DEV_UP;
++              break;
++
++      case SFP_DEV_UP:
++              if (event == SFP_E_DEV_DOWN) {
++                      /* If the module has a PHY, avoid raising TX disable
++                       * as this resets the PHY. Otherwise, raise it to
++                       * turn the laser off.
++                       */
++                      if (!sfp->mod_phy)
++                              sfp_module_tx_disable(sfp);
++                      sfp->sm_dev_state = SFP_DEV_DOWN;
++              }
++              break;
++      }
++
++      /* Some events are global */
++      if (sfp->sm_state != SFP_S_DOWN &&
++          (sfp->sm_mod_state != SFP_MOD_PRESENT ||
++           sfp->sm_dev_state != SFP_DEV_UP)) {
++              if (sfp->sm_state == SFP_S_LINK_UP &&
++                  sfp->sm_dev_state == SFP_DEV_UP)
++                      sfp_sm_link_down(sfp);
++              if (sfp->mod_phy)
++                      sfp_sm_phy_detach(sfp);
++              sfp_sm_next(sfp, SFP_S_DOWN, 0);
++              mutex_unlock(&sfp->sm_mutex);
++              return;
++      }
++
++      /* The main state machine */
++      switch (sfp->sm_state) {
++      case SFP_S_DOWN:
++              if (sfp->sm_mod_state == SFP_MOD_PRESENT &&
++                  sfp->sm_dev_state == SFP_DEV_UP)
++                      sfp_sm_mod_init(sfp);
++              break;
++
++      case SFP_S_INIT:
++              if (event == SFP_E_TIMEOUT && sfp->state & SFP_F_TX_FAULT)
++                      sfp_sm_fault(sfp, true);
++              else if (event == SFP_E_TIMEOUT || event == SFP_E_TX_CLEAR)
++                      sfp_sm_link_check_los(sfp);
++              break;
++
++      case SFP_S_WAIT_LOS:
++              if (event == SFP_E_TX_FAULT)
++                      sfp_sm_fault(sfp, true);
++              else if (event ==
++                       (sfp->id.ext.options & SFP_OPTIONS_LOS_INVERTED ?
++                        SFP_E_LOS_HIGH : SFP_E_LOS_LOW))
++                      sfp_sm_link_up(sfp);
++              break;
++
++      case SFP_S_LINK_UP:
++              if (event == SFP_E_TX_FAULT) {
++                      sfp_sm_link_down(sfp);
++                      sfp_sm_fault(sfp, true);
++              } else if (event ==
++                         (sfp->id.ext.options & SFP_OPTIONS_LOS_INVERTED ?
++                          SFP_E_LOS_LOW : SFP_E_LOS_HIGH)) {
++                      sfp_sm_link_down(sfp);
++                      sfp_sm_next(sfp, SFP_S_WAIT_LOS, 0);
++              }
++              break;
++
++      case SFP_S_TX_FAULT:
++              if (event == SFP_E_TIMEOUT) {
++                      sfp_module_tx_fault_reset(sfp);
++                      sfp_sm_next(sfp, SFP_S_REINIT, T_INIT_JIFFIES);
++              }
++              break;
++
++      case SFP_S_REINIT:
++              if (event == SFP_E_TIMEOUT && sfp->state & SFP_F_TX_FAULT) {
++                      sfp_sm_fault(sfp, false);
++              } else if (event == SFP_E_TIMEOUT || event == SFP_E_TX_CLEAR) {
++                      dev_info(sfp->dev, "module transmit fault recovered\n");
++                      sfp_sm_link_check_los(sfp);
++              }
++              break;
++
++      case SFP_S_TX_DISABLE:
++              break;
++      }
++
++      dev_dbg(sfp->dev, "SM: exit %u:%u:%u\n",
++              sfp->sm_mod_state, sfp->sm_dev_state, sfp->sm_state);
++
++      mutex_unlock(&sfp->sm_mutex);
++}
++
++#if 0
++static int sfp_phy_module_info(struct phy_device *phy,
++                             struct ethtool_modinfo *modinfo)
++{
++      struct sfp *sfp = phy->priv;
++
++      /* locking... and check module is present */
++
++      if (sfp->id.ext.sff8472_compliance) {
++              modinfo->type = ETH_MODULE_SFF_8472;
++              modinfo->eeprom_len = ETH_MODULE_SFF_8472_LEN;
++      } else {
++              modinfo->type = ETH_MODULE_SFF_8079;
++              modinfo->eeprom_len = ETH_MODULE_SFF_8079_LEN;
++      }
++      return 0;
++}
++
++static int sfp_phy_module_eeprom(struct phy_device *phy,
++      struct ethtool_eeprom *ee, u8 *data)
++{
++      struct sfp *sfp = phy->priv;
++      unsigned int first, last, len;
++      int ret;
++
++      if (ee->len == 0)
++              return -EINVAL;
++
++      first = ee->offset;
++      last = ee->offset + ee->len;
++      if (first < ETH_MODULE_SFF_8079_LEN) {
++              len = last;
++              if (len > ETH_MODULE_SFF_8079_LEN)
++                      len = ETH_MODULE_SFF_8079_LEN;
++              len -= first;
++
++              ret = sfp->read(sfp, false, first, data, len);
++              if (ret < 0)
++                      return ret;
++
++              first += len;
++              data += len;
++      }
++      if (first >= ETH_MODULE_SFF_8079_LEN && last > first) {
++              len = last - first;
++
++              ret = sfp->read(sfp, true, first, data, len);
++              if (ret < 0)
++                      return ret;
++      }
++      return 0;
++}
++#endif
++
++static void sfp_timeout(struct work_struct *work)
++{
++      struct sfp *sfp = container_of(work, struct sfp, timeout.work);
++
++      sfp_sm_event(sfp, SFP_E_TIMEOUT);
++}
++
++static void sfp_check_state(struct sfp *sfp)
++{
++      unsigned int state, i, changed;
++
++      state = sfp_get_state(sfp);
++      changed = state ^ sfp->state;
++      changed &= SFP_F_PRESENT | SFP_F_LOS | SFP_F_TX_FAULT;
++
++      for (i = 0; i < GPIO_MAX; i++)
++              if (changed & BIT(i))
++                      dev_dbg(sfp->dev, "%s %u -> %u\n", gpio_of_names[i],
++                              !!(sfp->state & BIT(i)), !!(state & BIT(i)));
++
++      state |= sfp->state & (SFP_F_TX_DISABLE | SFP_F_RATE_SELECT);
++      sfp->state = state;
++
++      if (changed & SFP_F_PRESENT)
++              sfp_sm_event(sfp, state & SFP_F_PRESENT ?
++                              SFP_E_INSERT : SFP_E_REMOVE);
++
++      if (changed & SFP_F_TX_FAULT)
++              sfp_sm_event(sfp, state & SFP_F_TX_FAULT ?
++                              SFP_E_TX_FAULT : SFP_E_TX_CLEAR);
++
++      if (changed & SFP_F_LOS)
++              sfp_sm_event(sfp, state & SFP_F_LOS ?
++                              SFP_E_LOS_HIGH : SFP_E_LOS_LOW);
++}
++
++static irqreturn_t sfp_irq(int irq, void *data)
++{
++      struct sfp *sfp = data;
++
++      sfp_check_state(sfp);
++
++      return IRQ_HANDLED;
++}
++
++static void sfp_poll(struct work_struct *work)
++{
++      struct sfp *sfp = container_of(work, struct sfp, poll.work);
++
++      sfp_check_state(sfp);
++      mod_delayed_work(system_wq, &sfp->poll, poll_jiffies);
++}
++
++static int sfp_netdev_notify(struct notifier_block *nb, unsigned long act, void *data)
++{
++      struct sfp *sfp = container_of(nb, struct sfp, netdev_nb);
++      struct netdev_notifier_info *info = data;
++      struct net_device *ndev = info->dev;
++
++      if (!sfp->ndev || ndev != sfp->ndev)
++              return NOTIFY_DONE;
++
++      switch (act) {
++      case NETDEV_UP:
++              sfp_sm_event(sfp, SFP_E_DEV_UP);
++              break;
++
++      case NETDEV_GOING_DOWN:
++              sfp_sm_event(sfp, SFP_E_DEV_DOWN);
++              break;
++
++      case NETDEV_UNREGISTER:
++              if (sfp->mod_phy && sfp->phylink)
++                      phylink_disconnect_phy(sfp->phylink);
++              sfp->phylink = NULL;
++              dev_put(sfp->ndev);
++              sfp->ndev = NULL;
++              break;
++      }
++      return NOTIFY_OK;
++}
++
++static struct sfp *sfp_alloc(struct device *dev)
++{
++      struct sfp *sfp;
++
++      sfp = kzalloc(sizeof(*sfp), GFP_KERNEL);
++      if (!sfp)
++              return ERR_PTR(-ENOMEM);
++
++      sfp->dev = dev;
++
++      mutex_init(&sfp->sm_mutex);
++      INIT_DELAYED_WORK(&sfp->poll, sfp_poll);
++      INIT_DELAYED_WORK(&sfp->timeout, sfp_timeout);
++
++      sfp->netdev_nb.notifier_call = sfp_netdev_notify;
++
++      return sfp;
++}
++
++static void sfp_destroy(struct sfp *sfp)
++{
++      cancel_delayed_work_sync(&sfp->poll);
++      cancel_delayed_work_sync(&sfp->timeout);
++      if (sfp->i2c_mii) {
++              mdiobus_unregister(sfp->i2c_mii);
++              mdiobus_free(sfp->i2c_mii);
++      }
++      if (sfp->i2c)
++              i2c_put_adapter(sfp->i2c);
++      of_node_put(sfp->dev->of_node);
++      kfree(sfp);
++}
++
++static void sfp_cleanup(void *data)
++{
++      struct sfp *sfp = data;
++
++      sfp_destroy(sfp);
++}
++
++static int sfp_probe(struct platform_device *pdev)
++{
++      struct sfp *sfp;
++      bool poll = false;
++      int irq, err, i;
++
++      sfp = sfp_alloc(&pdev->dev);
++      if (IS_ERR(sfp))
++              return PTR_ERR(sfp);
++
++      platform_set_drvdata(pdev, sfp);
++
++      err = devm_add_action(sfp->dev, sfp_cleanup, sfp);
++      if (err < 0)
++              return err;
++
++      if (pdev->dev.of_node) {
++              struct device_node *node = pdev->dev.of_node;
++              struct device_node *np;
++
++              np = of_parse_phandle(node, "i2c-bus", 0);
++              if (np) {
++                      struct i2c_adapter *i2c;
++
++                      i2c = of_find_i2c_adapter_by_node(np);
++                      of_node_put(np);
++                      if (!i2c)
++                              return -EPROBE_DEFER;
++
++                      err = sfp_i2c_configure(sfp, i2c);
++                      if (err < 0) {
++                              i2c_put_adapter(i2c);
++                              return err;
++                      }
++              }
++
++              for (i = 0; i < GPIO_MAX; i++) {
++                      sfp->gpio[i] = devm_gpiod_get_optional(sfp->dev,
++                                         gpio_of_names[i], gpio_flags[i]);
++                      if (IS_ERR(sfp->gpio[i]))
++                              return PTR_ERR(sfp->gpio[i]);
++              }
++
++              sfp->get_state = sfp_gpio_get_state;
++              sfp->set_state = sfp_gpio_set_state;
++
++              np = of_parse_phandle(node, "sfp,ethernet", 0);
++              if (!np) {
++                      dev_err(sfp->dev, "missing sfp,ethernet property\n");
++                      return -EINVAL;
++              }
++
++              sfp->ndev = of_find_net_device_by_node(np);
++              if (!sfp->ndev) {
++                      dev_err(sfp->dev, "ethernet device not found\n");
++                      return -EPROBE_DEFER;
++              }
++
++              dev_hold(sfp->ndev);
++              put_device(&sfp->ndev->dev);
++
++              sfp->phylink = phylink_lookup_by_netdev(sfp->ndev);
++              if (!sfp->phylink) {
++                      dev_err(sfp->dev, "ethernet device not found\n");
++                      return -EPROBE_DEFER;
++              }
++
++              phylink_disable(sfp->phylink);
++      }
++
++      sfp->state = sfp_get_state(sfp);
++      if (sfp->gpio[GPIO_TX_DISABLE] &&
++          gpiod_get_value_cansleep(sfp->gpio[GPIO_TX_DISABLE]))
++              sfp->state |= SFP_F_TX_DISABLE;
++      if (sfp->gpio[GPIO_RATE_SELECT] &&
++          gpiod_get_value_cansleep(sfp->gpio[GPIO_RATE_SELECT]))
++              sfp->state |= SFP_F_RATE_SELECT;
++      sfp_set_state(sfp, sfp->state);
++      sfp_module_tx_disable(sfp);
++      if (sfp->state & SFP_F_PRESENT)
++              sfp_sm_event(sfp, SFP_E_INSERT);
++
++      for (i = 0; i < GPIO_MAX; i++) {
++              if (gpio_flags[i] != GPIOD_IN || !sfp->gpio[i])
++                      continue;
++
++              irq = gpiod_to_irq(sfp->gpio[i]);
++              if (!irq) {
++                      poll = true;
++                      continue;
++              }
++
++              err = devm_request_threaded_irq(sfp->dev, irq, NULL, sfp_irq,
++                                              IRQF_ONESHOT |
++                                              IRQF_TRIGGER_RISING |
++                                              IRQF_TRIGGER_FALLING,
++                                              dev_name(sfp->dev), sfp);
++              if (err)
++                      poll = true;
++      }
++
++      if (poll)
++              mod_delayed_work(system_wq, &sfp->poll, poll_jiffies);
++
++      register_netdevice_notifier(&sfp->netdev_nb);
++
++      return 0;
++}
++
++static int sfp_remove(struct platform_device *pdev)
++{
++      struct sfp *sfp = platform_get_drvdata(pdev);
++
++      unregister_netdevice_notifier(&sfp->netdev_nb);
++      if (sfp->ndev)
++              dev_put(sfp->ndev);
++
++      return 0;
++}
++
++static const struct of_device_id sfp_of_match[] = {
++      { .compatible = "sff,sfp", },
++      { },
++};
++MODULE_DEVICE_TABLE(of, sfp_of_match);
++
++static struct platform_driver sfp_driver = {
++      .probe = sfp_probe,
++      .remove = sfp_remove,
++      .driver = {
++              .name = "sfp",
++              .of_match_table = sfp_of_match,
++      },
++};
++
++static int sfp_init(void)
++{
++      poll_jiffies = msecs_to_jiffies(100);
++
++      return platform_driver_register(&sfp_driver);
++}
++module_init(sfp_init);
++
++static void sfp_exit(void)
++{
++      platform_driver_unregister(&sfp_driver);
++}
++module_exit(sfp_exit);
++
++MODULE_ALIAS("platform:sfp");
++MODULE_AUTHOR("Russell King");
++MODULE_LICENSE("GPL v2");
+--- /dev/null
++++ b/include/linux/sfp.h
+@@ -0,0 +1,339 @@
++#ifndef LINUX_SFP_H
++#define LINUX_SFP_H
++
++struct __packed sfp_eeprom_base {
++      u8 phys_id;
++      u8 phys_ext_id;
++      u8 connector;
++#if defined __BIG_ENDIAN_BITFIELD
++      u8 e10g_base_er:1;
++      u8 e10g_base_lrm:1;
++      u8 e10g_base_lr:1;
++      u8 e10g_base_sr:1;
++      u8 if_1x_sx:1;
++      u8 if_1x_lx:1;
++      u8 if_1x_copper_active:1;
++      u8 if_1x_copper_passive:1;
++
++      u8 escon_mmf_1310_led:1;
++      u8 escon_smf_1310_laser:1;
++      u8 sonet_oc192_short_reach:1;
++      u8 sonet_reach_bit1:1;
++      u8 sonet_reach_bit2:1;
++      u8 sonet_oc48_long_reach:1;
++      u8 sonet_oc48_intermediate_reach:1;
++      u8 sonet_oc48_short_reach:1;
++
++      u8 unallocated_5_7:1;
++      u8 sonet_oc12_smf_long_reach:1;
++      u8 sonet_oc12_smf_intermediate_reach:1;
++      u8 sonet_oc12_short_reach:1;
++      u8 unallocated_5_3:1;
++      u8 sonet_oc3_smf_long_reach:1;
++      u8 sonet_oc3_smf_intermediate_reach:1;
++      u8 sonet_oc3_short_reach:1;
++
++      u8 e_base_px:1;
++      u8 e_base_bx10:1;
++      u8 e100_base_fx:1;
++      u8 e100_base_lx:1;
++      u8 e1000_base_t:1;
++      u8 e1000_base_cx:1;
++      u8 e1000_base_lx:1;
++      u8 e1000_base_sx:1;
++
++      u8 fc_ll_v:1;
++      u8 fc_ll_s:1;
++      u8 fc_ll_i:1;
++      u8 fc_ll_l:1;
++      u8 fc_ll_m:1;
++      u8 fc_tech_sa:1;
++      u8 fc_tech_lc:1;
++      u8 fc_tech_electrical_inter_enclosure:1;
++
++      u8 fc_tech_electrical_intra_enclosure:1;
++      u8 fc_tech_sn:1;
++      u8 fc_tech_sl:1;
++      u8 fc_tech_ll:1;
++      u8 sfp_ct_active:1;
++      u8 sfp_ct_passive:1;
++      u8 unallocated_8_1:1;
++      u8 unallocated_8_0:1;
++
++      u8 fc_media_tw:1;
++      u8 fc_media_tp:1;
++      u8 fc_media_mi:1;
++      u8 fc_media_tv:1;
++      u8 fc_media_m6:1;
++      u8 fc_media_m5:1;
++      u8 unallocated_9_1:1;
++      u8 fc_media_sm:1;
++
++      u8 fc_speed_1200:1;
++      u8 fc_speed_800:1;
++      u8 fc_speed_1600:1;
++      u8 fc_speed_400:1;
++      u8 fc_speed_3200:1;
++      u8 fc_speed_200:1;
++      u8 unallocated_10_1:1;
++      u8 fc_speed_100:1;
++#elif defined __LITTLE_ENDIAN_BITFIELD
++      u8 if_1x_copper_passive:1;
++      u8 if_1x_copper_active:1;
++      u8 if_1x_lx:1;
++      u8 if_1x_sx:1;
++      u8 e10g_base_sr:1;
++      u8 e10g_base_lr:1;
++      u8 e10g_base_lrm:1;
++      u8 e10g_base_er:1;
++
++      u8 sonet_oc3_short_reach:1;
++      u8 sonet_oc3_smf_intermediate_reach:1;
++      u8 sonet_oc3_smf_long_reach:1;
++      u8 unallocated_5_3:1;
++      u8 sonet_oc12_short_reach:1;
++      u8 sonet_oc12_smf_intermediate_reach:1;
++      u8 sonet_oc12_smf_long_reach:1;
++      u8 unallocated_5_7:1;
++
++      u8 sonet_oc48_short_reach:1;
++      u8 sonet_oc48_intermediate_reach:1;
++      u8 sonet_oc48_long_reach:1;
++      u8 sonet_reach_bit2:1;
++      u8 sonet_reach_bit1:1;
++      u8 sonet_oc192_short_reach:1;
++      u8 escon_smf_1310_laser:1;
++      u8 escon_mmf_1310_led:1;
++
++      u8 e1000_base_sx:1;
++      u8 e1000_base_lx:1;
++      u8 e1000_base_cx:1;
++      u8 e1000_base_t:1;
++      u8 e100_base_lx:1;
++      u8 e100_base_fx:1;
++      u8 e_base_bx10:1;
++      u8 e_base_px:1;
++
++      u8 fc_tech_electrical_inter_enclosure:1;
++      u8 fc_tech_lc:1;
++      u8 fc_tech_sa:1;
++      u8 fc_ll_m:1;
++      u8 fc_ll_l:1;
++      u8 fc_ll_i:1;
++      u8 fc_ll_s:1;
++      u8 fc_ll_v:1;
++
++      u8 unallocated_8_0:1;
++      u8 unallocated_8_1:1;
++      u8 sfp_ct_passive:1;
++      u8 sfp_ct_active:1;
++      u8 fc_tech_ll:1;
++      u8 fc_tech_sl:1;
++      u8 fc_tech_sn:1;
++      u8 fc_tech_electrical_intra_enclosure:1;
++
++      u8 fc_media_sm:1;
++      u8 unallocated_9_1:1;
++      u8 fc_media_m5:1;
++      u8 fc_media_m6:1;
++      u8 fc_media_tv:1;
++      u8 fc_media_mi:1;
++      u8 fc_media_tp:1;
++      u8 fc_media_tw:1;
++
++      u8 fc_speed_100:1;
++      u8 unallocated_10_1:1;
++      u8 fc_speed_200:1;
++      u8 fc_speed_3200:1;
++      u8 fc_speed_400:1;
++      u8 fc_speed_1600:1;
++      u8 fc_speed_800:1;
++      u8 fc_speed_1200:1;
++#else
++#error Unknown Endian
++#endif
++      u8 encoding;
++      u8 br_nominal;
++      u8 rate_id;
++      u8 link_len[6];
++      char vendor_name[16];
++      u8 reserved36;
++      char vendor_oui[3];
++      char vendor_pn[16];
++      char vendor_rev[4];
++      union {
++              __be16 optical_wavelength;
++              u8 cable_spec;
++      };
++      u8 reserved62;
++      u8 cc_base;
++};
++
++struct __packed sfp_eeprom_ext {
++      __be16 options;
++      u8 br_max;
++      u8 br_min;
++      char vendor_sn[16];
++      char datecode[8];
++      u8 diagmon;
++      u8 enhopts;
++      u8 sff8472_compliance;
++      u8 cc_ext;
++};
++
++struct __packed sfp_eeprom_id {
++      struct sfp_eeprom_base base;
++      struct sfp_eeprom_ext ext;
++};
++
++/* SFP EEPROM registers */
++enum {
++      SFP_PHYS_ID                     = 0x00,
++      SFP_PHYS_EXT_ID                 = 0x01,
++      SFP_CONNECTOR                   = 0x02,
++      SFP_COMPLIANCE                  = 0x03,
++      SFP_ENCODING                    = 0x0b,
++      SFP_BR_NOMINAL                  = 0x0c,
++      SFP_RATE_ID                     = 0x0d,
++      SFP_LINK_LEN_SM_KM              = 0x0e,
++      SFP_LINK_LEN_SM_100M            = 0x0f,
++      SFP_LINK_LEN_50UM_OM2_10M       = 0x10,
++      SFP_LINK_LEN_62_5UM_OM1_10M     = 0x11,
++      SFP_LINK_LEN_COPPER_1M          = 0x12,
++      SFP_LINK_LEN_50UM_OM4_10M       = 0x12,
++      SFP_LINK_LEN_50UM_OM3_10M       = 0x13,
++      SFP_VENDOR_NAME                 = 0x14,
++      SFP_VENDOR_OUI                  = 0x25,
++      SFP_VENDOR_PN                   = 0x28,
++      SFP_VENDOR_REV                  = 0x38,
++      SFP_OPTICAL_WAVELENGTH_MSB      = 0x3c,
++      SFP_OPTICAL_WAVELENGTH_LSB      = 0x3d,
++      SFP_CABLE_SPEC                  = 0x3c,
++      SFP_CC_BASE                     = 0x3f,
++      SFP_OPTIONS                     = 0x40, /* 2 bytes, MSB, LSB */
++      SFP_BR_MAX                      = 0x42,
++      SFP_BR_MIN                      = 0x43,
++      SFP_VENDOR_SN                   = 0x44,
++      SFP_DATECODE                    = 0x54,
++      SFP_DIAGMON                     = 0x5c,
++      SFP_ENHOPTS                     = 0x5d,
++      SFP_SFF8472_COMPLIANCE          = 0x5e,
++      SFP_CC_EXT                      = 0x5f,
++
++      SFP_PHYS_ID_SFP                 = 0x03,
++      SFP_PHYS_EXT_ID_SFP             = 0x04,
++      SFP_CONNECTOR_UNSPEC            = 0x00,
++      /* codes 01-05 not supportable on SFP, but some modules have single SC */
++      SFP_CONNECTOR_SC                = 0x01,
++      SFP_CONNECTOR_FIBERJACK         = 0x06,
++      SFP_CONNECTOR_LC                = 0x07,
++      SFP_CONNECTOR_MT_RJ             = 0x08,
++      SFP_CONNECTOR_MU                = 0x09,
++      SFP_CONNECTOR_SG                = 0x0a,
++      SFP_CONNECTOR_OPTICAL_PIGTAIL   = 0x0b,
++      SFP_CONNECTOR_HSSDC_II          = 0x20,
++      SFP_CONNECTOR_COPPER_PIGTAIL    = 0x21,
++      SFP_ENCODING_UNSPEC             = 0x00,
++      SFP_ENCODING_8B10B              = 0x01,
++      SFP_ENCODING_4B5B               = 0x02,
++      SFP_ENCODING_NRZ                = 0x03,
++      SFP_ENCODING_MANCHESTER         = 0x04,
++      SFP_OPTIONS_HIGH_POWER_LEVEL    = BIT(13),
++      SFP_OPTIONS_PAGING_A2           = BIT(12),
++      SFP_OPTIONS_RETIMER             = BIT(11),
++      SFP_OPTIONS_COOLED_XCVR         = BIT(10),
++      SFP_OPTIONS_POWER_DECL          = BIT(9),
++      SFP_OPTIONS_RX_LINEAR_OUT       = BIT(8),
++      SFP_OPTIONS_RX_DECISION_THRESH  = BIT(7),
++      SFP_OPTIONS_TUNABLE_TX          = BIT(6),
++      SFP_OPTIONS_RATE_SELECT         = BIT(5),
++      SFP_OPTIONS_TX_DISABLE          = BIT(4),
++      SFP_OPTIONS_TX_FAULT            = BIT(3),
++      SFP_OPTIONS_LOS_INVERTED        = BIT(2),
++      SFP_OPTIONS_LOS_NORMAL          = BIT(1),
++      SFP_DIAGMON_DDM                 = BIT(6),
++      SFP_DIAGMON_INT_CAL             = BIT(5),
++      SFP_DIAGMON_EXT_CAL             = BIT(4),
++      SFP_DIAGMON_RXPWR_AVG           = BIT(3),
++      SFP_DIAGMON_ADDRMODE            = BIT(2),
++      SFP_ENHOPTS_ALARMWARN           = BIT(7),
++      SFP_ENHOPTS_SOFT_TX_DISABLE     = BIT(6),
++      SFP_ENHOPTS_SOFT_TX_FAULT       = BIT(5),
++      SFP_ENHOPTS_SOFT_RX_LOS         = BIT(4),
++      SFP_ENHOPTS_SOFT_RATE_SELECT    = BIT(3),
++      SFP_ENHOPTS_APP_SELECT_SFF8079  = BIT(2),
++      SFP_ENHOPTS_SOFT_RATE_SFF8431   = BIT(1),
++      SFP_SFF8472_COMPLIANCE_NONE     = 0x00,
++      SFP_SFF8472_COMPLIANCE_REV9_3   = 0x01,
++      SFP_SFF8472_COMPLIANCE_REV9_5   = 0x02,
++      SFP_SFF8472_COMPLIANCE_REV10_2  = 0x03,
++      SFP_SFF8472_COMPLIANCE_REV10_4  = 0x04,
++      SFP_SFF8472_COMPLIANCE_REV11_0  = 0x05,
++      SFP_SFF8472_COMPLIANCE_REV11_3  = 0x06,
++      SFP_SFF8472_COMPLIANCE_REV11_4  = 0x07,
++      SFP_SFF8472_COMPLIANCE_REV12_0  = 0x08,
++};
++
++/* SFP Diagnostics */
++enum {
++      /* Alarm and warnings stored MSB at lower address then LSB */
++      SFP_TEMP_HIGH_ALARM             = 0x00,
++      SFP_TEMP_LOW_ALARM              = 0x02,
++      SFP_TEMP_HIGH_WARN              = 0x04,
++      SFP_TEMP_LOW_WARN               = 0x06,
++      SFP_VOLT_HIGH_ALARM             = 0x08,
++      SFP_VOLT_LOW_ALARM              = 0x0a,
++      SFP_VOLT_HIGH_WARN              = 0x0c,
++      SFP_VOLT_LOW_WARN               = 0x0e,
++      SFP_BIAS_HIGH_ALARM             = 0x10,
++      SFP_BIAS_LOW_ALARM              = 0x12,
++      SFP_BIAS_HIGH_WARN              = 0x14,
++      SFP_BIAS_LOW_WARN               = 0x16,
++      SFP_TXPWR_HIGH_ALARM            = 0x18,
++      SFP_TXPWR_LOW_ALARM             = 0x1a,
++      SFP_TXPWR_HIGH_WARN             = 0x1c,
++      SFP_TXPWR_LOW_WARN              = 0x1e,
++      SFP_RXPWR_HIGH_ALARM            = 0x20,
++      SFP_RXPWR_LOW_ALARM             = 0x22,
++      SFP_RXPWR_HIGH_WARN             = 0x24,
++      SFP_RXPWR_LOW_WARN              = 0x26,
++      SFP_LASER_TEMP_HIGH_ALARM       = 0x28,
++      SFP_LASER_TEMP_LOW_ALARM        = 0x2a,
++      SFP_LASER_TEMP_HIGH_WARN        = 0x2c,
++      SFP_LASER_TEMP_LOW_WARN         = 0x2e,
++      SFP_TEC_CUR_HIGH_ALARM          = 0x30,
++      SFP_TEC_CUR_LOW_ALARM           = 0x32,
++      SFP_TEC_CUR_HIGH_WARN           = 0x34,
++      SFP_TEC_CUR_LOW_WARN            = 0x36,
++      SFP_CAL_RXPWR4                  = 0x38,
++      SFP_CAL_RXPWR3                  = 0x3c,
++      SFP_CAL_RXPWR2                  = 0x40,
++      SFP_CAL_RXPWR1                  = 0x44,
++      SFP_CAL_RXPWR0                  = 0x48,
++      SFP_CAL_TXI_SLOPE               = 0x4c,
++      SFP_CAL_TXI_OFFSET              = 0x4e,
++      SFP_CAL_TXPWR_SLOPE             = 0x50,
++      SFP_CAL_TXPWR_OFFSET            = 0x52,
++      SFP_CAL_T_SLOPE                 = 0x54,
++      SFP_CAL_T_OFFSET                = 0x56,
++      SFP_CAL_V_SLOPE                 = 0x58,
++      SFP_CAL_V_OFFSET                = 0x5a,
++      SFP_CHKSUM                      = 0x5f,
++
++      SFP_TEMP                        = 0x60,
++      SFP_VCC                         = 0x62,
++      SFP_TX_BIAS                     = 0x64,
++      SFP_TX_POWER                    = 0x66,
++      SFP_RX_POWER                    = 0x68,
++      SFP_LASER_TEMP                  = 0x6a,
++      SFP_TEC_CUR                     = 0x6c,
++
++      SFP_STATUS                      = 0x6e,
++      SFP_ALARM                       = 0x70,
++
++      SFP_EXT_STATUS                  = 0x76,
++      SFP_VSL                         = 0x78,
++      SFP_PAGE                        = 0x7f,
++};
++
++#endif
diff --git a/target/linux/mvebu/patches-4.4/133-sfp-display-SFP-module-information.patch b/target/linux/mvebu/patches-4.4/133-sfp-display-SFP-module-information.patch
new file mode 100644 (file)
index 0000000..bc039ee
--- /dev/null
@@ -0,0 +1,283 @@
+From 66c248886538d7ee97ef2fe498061f857d4c906a Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Sun, 13 Sep 2015 01:06:31 +0100
+Subject: [PATCH 721/744] sfp: display SFP module information
+
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ drivers/net/phy/sfp.c | 247 +++++++++++++++++++++++++++++++++++++++++++++++++-
+ 1 file changed, 246 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/phy/sfp.c
++++ b/drivers/net/phy/sfp.c
+@@ -248,6 +248,182 @@ static unsigned int sfp_check(void *buf,
+       return check;
+ }
++static const char *sfp_link_len(char *buf, size_t size, unsigned int length,
++      unsigned int multiplier)
++{
++      if (length == 0)
++              return "unsupported/unspecified";
++
++      if (length == 255) {
++              *buf++ = '>';
++              size -= 1;
++              length -= 1;
++      }
++
++      length *= multiplier;
++
++      if (length >= 1000)
++              snprintf(buf, size, "%u.%0*ukm",
++                      length / 1000,
++                      multiplier > 100 ? 1 :
++                      multiplier > 10 ? 2 : 3,
++                      length % 1000);
++      else
++              snprintf(buf, size, "%um", length);
++
++      return buf;
++}
++
++struct bitfield {
++      unsigned int mask;
++      unsigned int val;
++      const char *str;
++};
++
++static const struct bitfield sfp_options[] = {
++      {
++              .mask = SFP_OPTIONS_HIGH_POWER_LEVEL,
++              .val = SFP_OPTIONS_HIGH_POWER_LEVEL,
++              .str = "hpl",
++      }, {
++              .mask = SFP_OPTIONS_PAGING_A2,
++              .val = SFP_OPTIONS_PAGING_A2,
++              .str = "paginga2",
++      }, {
++              .mask = SFP_OPTIONS_RETIMER,
++              .val = SFP_OPTIONS_RETIMER,
++              .str = "retimer",
++      }, {
++              .mask = SFP_OPTIONS_COOLED_XCVR,
++              .val = SFP_OPTIONS_COOLED_XCVR,
++              .str = "cooled",
++      }, {
++              .mask = SFP_OPTIONS_POWER_DECL,
++              .val = SFP_OPTIONS_POWER_DECL,
++              .str = "powerdecl",
++      }, {
++              .mask = SFP_OPTIONS_RX_LINEAR_OUT,
++              .val = SFP_OPTIONS_RX_LINEAR_OUT,
++              .str = "rxlinear",
++      }, {
++              .mask = SFP_OPTIONS_RX_DECISION_THRESH,
++              .val = SFP_OPTIONS_RX_DECISION_THRESH,
++              .str = "rxthresh",
++      }, {
++              .mask = SFP_OPTIONS_TUNABLE_TX,
++              .val = SFP_OPTIONS_TUNABLE_TX,
++              .str = "tunabletx",
++      }, {
++              .mask = SFP_OPTIONS_RATE_SELECT,
++              .val = SFP_OPTIONS_RATE_SELECT,
++              .str = "ratesel",
++      }, {
++              .mask = SFP_OPTIONS_TX_DISABLE,
++              .val = SFP_OPTIONS_TX_DISABLE,
++              .str = "txdisable",
++      }, {
++              .mask = SFP_OPTIONS_TX_FAULT,
++              .val = SFP_OPTIONS_TX_FAULT,
++              .str = "txfault",
++      }, {
++              .mask = SFP_OPTIONS_LOS_INVERTED,
++              .val = SFP_OPTIONS_LOS_INVERTED,
++              .str = "los-",
++      }, {
++              .mask = SFP_OPTIONS_LOS_NORMAL,
++              .val = SFP_OPTIONS_LOS_NORMAL,
++              .str = "los+",
++      }, { }
++};
++
++static const struct bitfield diagmon[] = {
++      {
++              .mask = SFP_DIAGMON_DDM,
++              .val = SFP_DIAGMON_DDM,
++              .str = "ddm",
++      }, {
++              .mask = SFP_DIAGMON_INT_CAL,
++              .val = SFP_DIAGMON_INT_CAL,
++              .str = "intcal",
++      }, {
++              .mask = SFP_DIAGMON_EXT_CAL,
++              .val = SFP_DIAGMON_EXT_CAL,
++              .str = "extcal",
++      }, {
++              .mask = SFP_DIAGMON_RXPWR_AVG,
++              .val = SFP_DIAGMON_RXPWR_AVG,
++              .str = "rxpwravg",
++      }, { }
++};
++
++static const char *sfp_bitfield(char *out, size_t outsz, const struct bitfield *bits, unsigned int val)
++{
++      char *p = out;
++      int n;
++
++      *p = '\0';
++      while (bits->mask) {
++              if ((val & bits->mask) == bits->val) {
++                      n = snprintf(p, outsz, "%s%s",
++                                   out != p ? ", " : "",
++                                   bits->str);
++                      if (n == outsz)
++                              break;
++                      p += n;
++                      outsz -= n;
++              }
++              bits++;
++      }
++
++      return out;
++}
++
++static const char *sfp_connector(unsigned int connector)
++{
++      switch (connector) {
++      case SFP_CONNECTOR_UNSPEC:
++              return "unknown/unspecified";
++      case SFP_CONNECTOR_SC:
++              return "SC";
++      case SFP_CONNECTOR_FIBERJACK:
++              return "Fiberjack";
++      case SFP_CONNECTOR_LC:
++              return "LC";
++      case SFP_CONNECTOR_MT_RJ:
++              return "MT-RJ";
++      case SFP_CONNECTOR_MU:
++              return "MU";
++      case SFP_CONNECTOR_SG:
++              return "SG";
++      case SFP_CONNECTOR_OPTICAL_PIGTAIL:
++              return "Optical pigtail";
++      case SFP_CONNECTOR_HSSDC_II:
++              return "HSSDC II";
++      case SFP_CONNECTOR_COPPER_PIGTAIL:
++              return "Copper pigtail";
++      default:
++              return "unknown";
++      }
++}
++
++static const char *sfp_encoding(unsigned int encoding)
++{
++      switch (encoding) {
++      case SFP_ENCODING_UNSPEC:
++              return "unspecified";
++      case SFP_ENCODING_8B10B:
++              return "8b10b";
++      case SFP_ENCODING_4B5B:
++              return "4b5b";
++      case SFP_ENCODING_NRZ:
++              return "NRZ";
++      case SFP_ENCODING_MANCHESTER:
++              return "MANCHESTER";
++      default:
++              return "unknown";
++      }
++}
++
+ /* Helpers */
+ static void sfp_module_tx_disable(struct sfp *sfp)
+ {
+@@ -426,6 +602,7 @@ static int sfp_sm_mod_probe(struct sfp *
+       char sn[17];
+       char date[9];
+       char rev[5];
++      char options[80];
+       u8 check;
+       int err;
+@@ -462,10 +639,78 @@ static int sfp_sm_mod_probe(struct sfp *
+       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 ||
diff --git a/target/linux/mvebu/patches-4.4/134-net-mvneta-convert-to-phylink.patch b/target/linux/mvebu/patches-4.4/134-net-mvneta-convert-to-phylink.patch
new file mode 100644 (file)
index 0000000..fac283f
--- /dev/null
@@ -0,0 +1,697 @@
+From e268be0ddc666f4a98db462cbed2a97637e82b5c Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Wed, 16 Sep 2015 21:27:10 +0100
+Subject: [PATCH 722/744] net: mvneta: convert to phylink
+
+Convert mvneta to use phylink, which models the MAC to PHY link in
+a generic, reusable form.
+
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ drivers/net/ethernet/marvell/Kconfig  |   2 +-
+ drivers/net/ethernet/marvell/mvneta.c | 451 +++++++++++++++++-----------------
+ 2 files changed, 227 insertions(+), 226 deletions(-)
+
+--- a/drivers/net/ethernet/marvell/Kconfig
++++ b/drivers/net/ethernet/marvell/Kconfig
+@@ -58,7 +58,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.
+--- a/drivers/net/ethernet/marvell/mvneta.c
++++ b/drivers/net/ethernet/marvell/mvneta.c
+@@ -28,6 +28,7 @@
+ #include <linux/of_mdio.h>
+ #include <linux/of_net.h>
+ #include <linux/phy.h>
++#include <linux/phylink.h>
+ #include <linux/platform_device.h>
+ #include <linux/skbuff.h>
+ #include <net/hwbm.h>
+@@ -188,6 +189,7 @@
+ #define MVNETA_GMAC_CTRL_0                       0x2c00
+ #define      MVNETA_GMAC_MAX_RX_SIZE_SHIFT       2
+ #define      MVNETA_GMAC_MAX_RX_SIZE_MASK        0x7ffc
++#define      MVNETA_GMAC0_PORT_1000BASE_X        BIT(1)
+ #define      MVNETA_GMAC0_PORT_ENABLE            BIT(0)
+ #define MVNETA_GMAC_CTRL_2                       0x2c08
+ #define      MVNETA_GMAC2_INBAND_AN_ENABLE       BIT(0)
+@@ -203,13 +205,19 @@
+ #define      MVNETA_GMAC_TX_FLOW_CTRL_ENABLE     BIT(5)
+ #define      MVNETA_GMAC_RX_FLOW_CTRL_ACTIVE     BIT(6)
+ #define      MVNETA_GMAC_TX_FLOW_CTRL_ACTIVE     BIT(7)
++#define      MVNETA_GMAC_AN_COMPLETE             BIT(11)
++#define      MVNETA_GMAC_SYNC_OK                 BIT(14)
+ #define MVNETA_GMAC_AUTONEG_CONFIG               0x2c0c
+ #define      MVNETA_GMAC_FORCE_LINK_DOWN         BIT(0)
+ #define      MVNETA_GMAC_FORCE_LINK_PASS         BIT(1)
+ #define      MVNETA_GMAC_INBAND_AN_ENABLE        BIT(2)
++#define      MVNETA_GMAC_AN_BYPASS_ENABLE        BIT(3)
++#define      MVNETA_GMAC_INBAND_RESTART_AN       BIT(4)
+ #define      MVNETA_GMAC_CONFIG_MII_SPEED        BIT(5)
+ #define      MVNETA_GMAC_CONFIG_GMII_SPEED       BIT(6)
+ #define      MVNETA_GMAC_AN_SPEED_EN             BIT(7)
++#define      MVNETA_GMAC_CONFIG_FLOW_CTRL        BIT(8)
++#define      MVNETA_GMAC_ADVERT_SYM_FLOW_CTRL    BIT(9)
+ #define      MVNETA_GMAC_AN_FLOW_CTRL_EN         BIT(11)
+ #define      MVNETA_GMAC_CONFIG_FULL_DUPLEX      BIT(12)
+ #define      MVNETA_GMAC_AN_DUPLEX_EN            BIT(13)
+@@ -396,15 +404,9 @@ struct mvneta_port {
+       u16 tx_ring_size;
+       u16 rx_ring_size;
+-      struct mii_bus *mii_bus;
+-      struct phy_device *phy_dev;
+-      phy_interface_t phy_interface;
+-      struct device_node *phy_node;
+-      unsigned int link;
+-      unsigned int duplex;
+-      unsigned int speed;
++      struct device_node *dn;
+       unsigned int tx_csum_limit;
+-      unsigned int use_inband_status:1;
++      struct phylink *phylink;
+       struct mvneta_bm *bm_priv;
+       struct mvneta_bm_pool *pool_long;
+@@ -1236,44 +1238,6 @@ static void mvneta_set_other_mcast_table
+               mvreg_write(pp, MVNETA_DA_FILT_OTH_MCAST + offset, val);
+ }
+-static void mvneta_set_autoneg(struct mvneta_port *pp, int enable)
+-{
+-      u32 val;
+-
+-      if (enable) {
+-              val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG);
+-              val &= ~(MVNETA_GMAC_FORCE_LINK_PASS |
+-                       MVNETA_GMAC_FORCE_LINK_DOWN |
+-                       MVNETA_GMAC_AN_FLOW_CTRL_EN);
+-              val |= MVNETA_GMAC_INBAND_AN_ENABLE |
+-                     MVNETA_GMAC_AN_SPEED_EN |
+-                     MVNETA_GMAC_AN_DUPLEX_EN;
+-              mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val);
+-
+-              val = mvreg_read(pp, MVNETA_GMAC_CLOCK_DIVIDER);
+-              val |= MVNETA_GMAC_1MS_CLOCK_ENABLE;
+-              mvreg_write(pp, MVNETA_GMAC_CLOCK_DIVIDER, val);
+-
+-              val = mvreg_read(pp, MVNETA_GMAC_CTRL_2);
+-              val |= MVNETA_GMAC2_INBAND_AN_ENABLE;
+-              mvreg_write(pp, MVNETA_GMAC_CTRL_2, val);
+-      } else {
+-              val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG);
+-              val &= ~(MVNETA_GMAC_INBAND_AN_ENABLE |
+-                     MVNETA_GMAC_AN_SPEED_EN |
+-                     MVNETA_GMAC_AN_DUPLEX_EN);
+-              mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val);
+-
+-              val = mvreg_read(pp, MVNETA_GMAC_CLOCK_DIVIDER);
+-              val &= ~MVNETA_GMAC_1MS_CLOCK_ENABLE;
+-              mvreg_write(pp, MVNETA_GMAC_CLOCK_DIVIDER, val);
+-
+-              val = mvreg_read(pp, MVNETA_GMAC_CTRL_2);
+-              val &= ~MVNETA_GMAC2_INBAND_AN_ENABLE;
+-              mvreg_write(pp, MVNETA_GMAC_CTRL_2, val);
+-      }
+-}
+-
+ static void mvneta_percpu_unmask_interrupt(void *arg)
+ {
+       struct mvneta_port *pp = arg;
+@@ -1421,7 +1385,6 @@ static void mvneta_defaults_set(struct m
+       val &= ~MVNETA_PHY_POLLING_ENABLE;
+       mvreg_write(pp, MVNETA_UNIT_CONTROL, val);
+-      mvneta_set_autoneg(pp, pp->use_inband_status);
+       mvneta_set_ucast_table(pp, -1);
+       mvneta_set_special_mcast_table(pp, -1);
+       mvneta_set_other_mcast_table(pp, -1);
+@@ -2614,26 +2577,11 @@ static irqreturn_t mvneta_isr(int irq, v
+       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
+@@ -2662,12 +2610,11 @@ static int mvneta_poll(struct napi_struc
+               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 */
+@@ -2983,7 +2930,7 @@ static void mvneta_start_dev(struct mvne
+                   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);
+ }
+@@ -2991,7 +2938,7 @@ static void mvneta_stop_dev(struct mvnet
+ {
+       unsigned int cpu;
+-      phy_stop(pp->phy_dev);
++      phylink_stop(pp->phylink);
+       for_each_online_cpu(cpu) {
+               struct mvneta_pcpu_port *port = per_cpu_ptr(pp->ports, cpu);
+@@ -3161,99 +3108,219 @@ static int mvneta_set_mac_addr(struct ne
+       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;
+-
+-                      val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG);
+-                      val &= ~(MVNETA_GMAC_CONFIG_MII_SPEED |
+-                               MVNETA_GMAC_CONFIG_GMII_SPEED |
+-                               MVNETA_GMAC_CONFIG_FULL_DUPLEX);
+-
+-                      if (phydev->duplex)
+-                              val |= MVNETA_GMAC_CONFIG_FULL_DUPLEX;
+-
+-                      if (phydev->speed == SPEED_1000)
+-                              val |= MVNETA_GMAC_CONFIG_GMII_SPEED;
+-                      else if (phydev->speed == SPEED_100)
+-                              val |= MVNETA_GMAC_CONFIG_MII_SPEED;
++      gmac_stat = mvreg_read(pp, MVNETA_GMAC_STATUS);
+-                      mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val);
++      if (gmac_stat & MVNETA_GMAC_SPEED_1000)
++              state->speed = SPEED_1000;
++      else if (gmac_stat & MVNETA_GMAC_SPEED_100)
++              state->speed = SPEED_100;
++      else
++              state->speed = SPEED_10;
+-                      pp->duplex = phydev->duplex;
+-                      pp->speed  = phydev->speed;
+-              }
++      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);
++
++      state->pause = 0;
++      if (gmac_stat & MVNETA_GMAC_RX_FLOW_CTRL_ENABLE)
++              state->pause |= MLO_PAUSE_RX;
++      if (gmac_stat & MVNETA_GMAC_TX_FLOW_CTRL_ENABLE)
++              state->pause |= MLO_PAUSE_TX;
++
++      return 1;
++}
++
++static void mvneta_mac_an_restart(struct net_device *ndev, unsigned int mode)
++{
++      struct mvneta_port *pp = netdev_priv(ndev);
++
++      if (mode == MLO_AN_8023Z) {
++              u32 gmac_an = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG);
++
++              mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG,
++                          gmac_an | MVNETA_GMAC_INBAND_RESTART_AN);
++              mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG,
++                          gmac_an & ~MVNETA_GMAC_INBAND_RESTART_AN);
+       }
++}
+-      if (phydev->link != pp->link) {
+-              if (!phydev->link) {
+-                      pp->duplex = -1;
+-                      pp->speed = 0;
+-              }
++static void mvneta_mac_config(struct net_device *ndev, unsigned int mode,
++      const struct phylink_link_state *state)
++{
++      struct mvneta_port *pp = netdev_priv(ndev);
++      u32 new_ctrl0, gmac_ctrl0 = mvreg_read(pp, MVNETA_GMAC_CTRL_0);
++      u32 new_ctrl2, gmac_ctrl2 = mvreg_read(pp, MVNETA_GMAC_CTRL_2);
++      u32 new_clk, gmac_clk = mvreg_read(pp, MVNETA_GMAC_CLOCK_DIVIDER);
++      u32 new_an, gmac_an = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG);
++
++      new_ctrl0 = gmac_ctrl0 & ~MVNETA_GMAC0_PORT_1000BASE_X;
++      new_ctrl2 = gmac_ctrl2 & ~MVNETA_GMAC2_INBAND_AN_ENABLE;
++      new_clk = gmac_clk & ~MVNETA_GMAC_1MS_CLOCK_ENABLE;
++      new_an = gmac_an & ~(MVNETA_GMAC_INBAND_AN_ENABLE |
++                           MVNETA_GMAC_INBAND_RESTART_AN |
++                           MVNETA_GMAC_CONFIG_MII_SPEED |
++                           MVNETA_GMAC_CONFIG_GMII_SPEED |
++                           MVNETA_GMAC_AN_SPEED_EN |
++                           MVNETA_GMAC_ADVERT_SYM_FLOW_CTRL |
++                           MVNETA_GMAC_CONFIG_FLOW_CTRL |
++                           MVNETA_GMAC_AN_FLOW_CTRL_EN |
++                           MVNETA_GMAC_CONFIG_FULL_DUPLEX |
++                           MVNETA_GMAC_AN_DUPLEX_EN);
++
++      if (state->advertising & ADVERTISED_Pause)
++              new_an |= MVNETA_GMAC_ADVERT_SYM_FLOW_CTRL;
++
++      switch (mode) {
++      case MLO_AN_SGMII:
++              /* SGMII mode receives the state from the PHY */
++              new_ctrl2 |= MVNETA_GMAC2_INBAND_AN_ENABLE;
++              new_clk |= MVNETA_GMAC_1MS_CLOCK_ENABLE;
++              new_an = (new_an & ~(MVNETA_GMAC_FORCE_LINK_DOWN |
++                                   MVNETA_GMAC_FORCE_LINK_PASS)) |
++                       MVNETA_GMAC_INBAND_AN_ENABLE |
++                       MVNETA_GMAC_AN_SPEED_EN |
++                       MVNETA_GMAC_AN_DUPLEX_EN;
++              break;
++
++      case MLO_AN_8023Z:
++              /* 802.3z negotiation - only 1000base-X */
++              new_ctrl0 |= MVNETA_GMAC0_PORT_1000BASE_X;
++              new_clk |= MVNETA_GMAC_1MS_CLOCK_ENABLE;
++              new_an = (new_an & ~(MVNETA_GMAC_FORCE_LINK_DOWN |
++                                   MVNETA_GMAC_FORCE_LINK_PASS)) |
++                       MVNETA_GMAC_INBAND_AN_ENABLE |
++                       MVNETA_GMAC_CONFIG_GMII_SPEED |
++                       /* The MAC only supports FD mode */
++                       MVNETA_GMAC_CONFIG_FULL_DUPLEX;
++
++              if (state->an_enabled)
++                      new_an |= MVNETA_GMAC_AN_FLOW_CTRL_EN;
++              break;
+-              pp->link = phydev->link;
+-              status_change = 1;
++      default:
++              /* Phy or fixed speed */
++              if (state->duplex)
++                      new_an |= MVNETA_GMAC_CONFIG_FULL_DUPLEX;
++
++              if (state->speed == SPEED_1000)
++                      new_an |= MVNETA_GMAC_CONFIG_GMII_SPEED;
++              else if (state->speed == SPEED_100)
++                      new_an |= MVNETA_GMAC_CONFIG_MII_SPEED;
++              break;
+       }
+-      if (status_change) {
+-              if (phydev->link) {
+-                      if (!pp->use_inband_status) {
+-                              u32 val = mvreg_read(pp,
+-                                                MVNETA_GMAC_AUTONEG_CONFIG);
+-                              val &= ~MVNETA_GMAC_FORCE_LINK_DOWN;
+-                              val |= MVNETA_GMAC_FORCE_LINK_PASS;
+-                              mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG,
+-                                          val);
+-                      }
+-                      mvneta_port_up(pp);
+-              } else {
+-                      if (!pp->use_inband_status) {
+-                              u32 val = mvreg_read(pp,
+-                                                MVNETA_GMAC_AUTONEG_CONFIG);
+-                              val &= ~MVNETA_GMAC_FORCE_LINK_PASS;
+-                              val |= MVNETA_GMAC_FORCE_LINK_DOWN;
+-                              mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG,
+-                                          val);
+-                      }
+-                      mvneta_port_down(pp);
+-              }
+-              phy_print_status(phydev);
++      /* Armada 370 documentation says we can only change the port mode
++       * and in-band enable when the link is down, so force it down
++       * while making these changes. We also do this for GMAC_CTRL2 */
++      if ((new_ctrl0 ^ gmac_ctrl0) & MVNETA_GMAC0_PORT_1000BASE_X ||
++          (new_ctrl2 ^ gmac_ctrl2) & MVNETA_GMAC2_INBAND_AN_ENABLE ||
++          (new_an  ^ gmac_an) & MVNETA_GMAC_INBAND_AN_ENABLE) {
++              mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG,
++                          (gmac_an & ~MVNETA_GMAC_FORCE_LINK_PASS) |
++                          MVNETA_GMAC_FORCE_LINK_DOWN);
++      }
++
++      if (new_ctrl0 != gmac_ctrl0)
++              mvreg_write(pp, MVNETA_GMAC_CTRL_0, new_ctrl0);
++      if (new_ctrl2 != gmac_ctrl2)
++              mvreg_write(pp, MVNETA_GMAC_CTRL_2, new_ctrl2);
++      if (new_clk != gmac_clk)
++              mvreg_write(pp, MVNETA_GMAC_CLOCK_DIVIDER, new_clk);
++      if (new_an != gmac_an)
++              mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, new_an);
++}
++
++static void mvneta_mac_link_down(struct net_device *ndev, unsigned int mode)
++{
++      struct mvneta_port *pp = netdev_priv(ndev);
++      u32 val;
++
++      mvneta_port_down(pp);
++
++      if (mode == MLO_AN_PHY || mode == MLO_AN_FIXED) {
++              val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG);
++              val &= ~MVNETA_GMAC_FORCE_LINK_PASS;
++              val |= MVNETA_GMAC_FORCE_LINK_DOWN;
++              mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val);
+       }
+ }
+-static int mvneta_mdio_probe(struct mvneta_port *pp)
++static void mvneta_mac_link_up(struct net_device *ndev, unsigned int mode,
++                             struct phy_device *phy)
+ {
+-      struct phy_device *phy_dev;
++      struct mvneta_port *pp = netdev_priv(ndev);
++      u32 val;
++
++      if (mode == MLO_AN_PHY || mode == MLO_AN_FIXED) {
++              val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG);
++              val &= ~MVNETA_GMAC_FORCE_LINK_DOWN;
++              val |= MVNETA_GMAC_FORCE_LINK_PASS;
++              mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val);
++      }
+-      phy_dev = 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;
+-      }
+-
+-      phy_dev->supported &= PHY_GBIT_FEATURES;
+-      phy_dev->advertising = phy_dev->supported;
+-
+-      pp->phy_dev = phy_dev;
+-      pp->link    = 0;
+-      pp->duplex  = 0;
+-      pp->speed   = 0;
++      mvneta_port_up(pp);
++}
+-      return 0;
++static const struct phylink_mac_ops mvneta_phylink_ops = {
++      .mac_get_support = mvneta_mac_support,
++      .mac_link_state = mvneta_mac_link_state,
++      .mac_an_restart = mvneta_mac_an_restart,
++      .mac_config = mvneta_mac_config,
++      .mac_link_down = mvneta_mac_link_down,
++      .mac_link_up = mvneta_mac_link_up,
++};
++
++static int mvneta_mdio_probe(struct mvneta_port *pp)
++{
++      int err = phylink_of_phy_connect(pp->phylink, pp->dn);
++      if (err)
++              netdev_err(pp->dev, "could not attach PHY\n");
++
++      return err;
+ }
+ static void mvneta_mdio_remove(struct mvneta_port *pp)
+ {
+-      phy_disconnect(pp->phy_dev);
+-      pp->phy_dev = NULL;
++      phylink_disconnect_phy(pp->phylink);
+ }
+ /* Electing a CPU must be done in an atomic way: it should be done
+@@ -3501,10 +3568,7 @@ static int mvneta_ioctl(struct net_devic
+ {
+       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 */
+@@ -3514,54 +3578,15 @@ int mvneta_ethtool_get_settings(struct n
+ {
+       struct mvneta_port *pp = netdev_priv(dev);
+-      if (!pp->phy_dev)
+-              return -ENODEV;
+-
+-      return phy_ethtool_gset(pp->phy_dev, cmd);
++      return phylink_ethtool_get_settings(pp->phylink, cmd);
+ }
+ /* Set settings (phy address, speed) for ethtools */
+ int mvneta_ethtool_set_settings(struct net_device *dev, struct ethtool_cmd *cmd)
+ {
+       struct mvneta_port *pp = netdev_priv(dev);
+-      struct phy_device *phydev = pp->phy_dev;
+-
+-      if (!phydev)
+-              return -ENODEV;
+-      if ((cmd->autoneg == AUTONEG_ENABLE) != pp->use_inband_status) {
+-              u32 val;
+-
+-              mvneta_set_autoneg(pp, cmd->autoneg == AUTONEG_ENABLE);
+-
+-              if (cmd->autoneg == AUTONEG_DISABLE) {
+-                      val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG);
+-                      val &= ~(MVNETA_GMAC_CONFIG_MII_SPEED |
+-                               MVNETA_GMAC_CONFIG_GMII_SPEED |
+-                               MVNETA_GMAC_CONFIG_FULL_DUPLEX);
+-
+-                      if (phydev->duplex)
+-                              val |= MVNETA_GMAC_CONFIG_FULL_DUPLEX;
+-
+-                      if (phydev->speed == SPEED_1000)
+-                              val |= MVNETA_GMAC_CONFIG_GMII_SPEED;
+-                      else if (phydev->speed == SPEED_100)
+-                              val |= MVNETA_GMAC_CONFIG_MII_SPEED;
+-
+-                      mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val);
+-              }
+-
+-              pp->use_inband_status = (cmd->autoneg == AUTONEG_ENABLE);
+-              netdev_info(pp->dev, "autoneg status set to %i\n",
+-                          pp->use_inband_status);
+-
+-              if (netif_running(dev)) {
+-                      mvneta_port_down(pp);
+-                      mvneta_port_up(pp);
+-              }
+-      }
+-
+-      return phy_ethtool_sset(pp->phy_dev, cmd);
++      return phylink_ethtool_set_settings(pp->phylink, cmd);
+ }
+ /* Set interrupt coalescing for ethtools */
+@@ -3669,7 +3694,8 @@ static void mvneta_ethtool_update_stats(
+ {
+       const struct mvneta_statistic *s;
+       void __iomem *base = pp->base;
+-      u32 high, low, val;
++      u32 high, low;
++      u64 val;
+       u64 val64;
+       int i;
+@@ -3964,14 +3990,13 @@ static int mvneta_probe(struct platform_
+       const struct mbus_dram_target_info *dram_target_info;
+       struct resource *res;
+       struct device_node *dn = pdev->dev.of_node;
+-      struct device_node *phy_node;
+       struct device_node *bm_node;
+       struct mvneta_port *pp;
+       struct net_device *dev;
++      struct phylink *phylink;
+       const char *dt_mac_addr;
+       char hw_mac_addr[ETH_ALEN];
+       const char *mac_from;
+-      const char *managed;
+       int tx_csum_limit;
+       int phy_mode;
+       int err;
+@@ -3987,31 +4012,11 @@ static int mvneta_probe(struct platform_
+               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;
+@@ -4022,12 +4027,7 @@ static int mvneta_probe(struct platform_
+       pp = netdev_priv(dev);
+       spin_lock_init(&pp->lock);
+-      pp->phy_node = phy_node;
+-      pp->phy_interface = phy_mode;
+-
+-      err = of_property_read_string(dn, "managed", &managed);
+-      pp->use_inband_status = (err == 0 &&
+-                               strcmp(managed, "in-band-status") == 0);
++      pp->dn = dn;
+       pp->cpu_notifier.notifier_call = mvneta_percpu_notifier;
+       pp->rxq_def = rxq_def;
+@@ -4037,7 +4037,7 @@ static int mvneta_probe(struct platform_
+       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);
+@@ -4140,6 +4140,14 @@ static int mvneta_probe(struct platform_
+       dev->priv_flags |= IFF_UNICAST_FLT | IFF_LIVE_ADDR_CHANGE;
+       dev->gso_max_segs = MVNETA_MAX_TSO_SEGS;
++      phylink = phylink_create(dev, dn, phy_mode, &mvneta_phylink_ops);
++      if (IS_ERR(phylink)) {
++              err = PTR_ERR(phylink);
++              goto err_free_stats;
++      }
++
++      pp->phylink = phylink;
++
+       err = register_netdev(dev);
+       if (err < 0) {
+               dev_err(&pdev->dev, "failed to register\n");
+@@ -4151,13 +4159,6 @@ static int mvneta_probe(struct platform_
+       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;
+@@ -4169,13 +4170,13 @@ err_netdev:
+                                      1 << pp->id);
+       }
+ err_free_stats:
++      if (pp->phylink)
++              phylink_destroy(pp->phylink);
+       free_percpu(pp->stats);
+ err_free_ports:
+       free_percpu(pp->ports);
+ err_clk:
+       clk_disable_unprepare(pp->clk);
+-err_put_phy_node:
+-      of_node_put(phy_node);
+ err_free_irq:
+       irq_dispose_mapping(dev->irq);
+ err_free_netdev:
+@@ -4194,7 +4195,7 @@ static int mvneta_remove(struct platform
+       free_percpu(pp->ports);
+       free_percpu(pp->stats);
+       irq_dispose_mapping(dev->irq);
+-      of_node_put(pp->phy_node);
++      phylink_destroy(pp->phylink);
+       free_netdev(dev);
+       if (pp->bm_priv) {
diff --git a/target/linux/mvebu/patches-4.4/135-phy-fixed-phy-remove-fixed_phy_update_state.patch b/target/linux/mvebu/patches-4.4/135-phy-fixed-phy-remove-fixed_phy_update_state.patch
new file mode 100644 (file)
index 0000000..58c9aab
--- /dev/null
@@ -0,0 +1,80 @@
+From 9be436bdb67c1f4aa9f33f2477f94e1f58a0ff02 Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Fri, 2 Oct 2015 22:46:54 +0100
+Subject: [PATCH 723/744] phy: fixed-phy: remove fixed_phy_update_state()
+
+mvneta is the only user of fixed_phy_update_state(), which has been
+converted to use phylink instead.  Remove fixed_phy_update_state().
+
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+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(-)
+
+--- 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
+ }
+ 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)
+--- 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_upd
+ {
+       return -ENODEV;
+ }
+-static inline int fixed_phy_update_state(struct phy_device *phydev,
+-                         const struct fixed_phy_status *status,
+-                         const struct fixed_phy_status *changed)
+-{
+-      return -ENODEV;
+-}
+ #endif /* CONFIG_FIXED_PHY */
+ #endif /* __PHY_FIXED_H */
diff --git a/target/linux/mvebu/patches-4.4/136-phylink-add-ethtool-nway_reset-support.patch b/target/linux/mvebu/patches-4.4/136-phylink-add-ethtool-nway_reset-support.patch
new file mode 100644 (file)
index 0000000..ade904e
--- /dev/null
@@ -0,0 +1,48 @@
+From d6bd25b692378ec17bdb1023d398c03c45829947 Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Thu, 1 Oct 2015 20:27:19 +0100
+Subject: [PATCH 724/744] phylink: add ethtool nway_reset support
+
+Add ethtool nway_reset support to phylink, to allow userspace to
+request a re-negotiation of the link.
+
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+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(+)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -687,6 +687,20 @@ int phylink_ethtool_set_settings(struct
+ }
+ EXPORT_SYMBOL_GPL(phylink_ethtool_set_settings);
++int phylink_ethtool_nway_reset(struct phylink *pl)
++{
++      int ret = 0;
++
++      mutex_lock(&pl->config_mutex);
++      if (pl->phydev)
++              ret = genphy_restart_aneg(pl->phydev);
++      phylink_mac_an_restart(pl);
++      mutex_unlock(&pl->config_mutex);
++
++      return ret;
++}
++EXPORT_SYMBOL_GPL(phylink_ethtool_nway_reset);
++
+ /* This emulates MII registers for a fixed-mode phy operating as per the
+  * passed in state. "aneg" defines if we report negotiation is possible.
+  *
+--- 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);
diff --git a/target/linux/mvebu/patches-4.4/137-net-mvneta-add-nway_reset-support.patch b/target/linux/mvebu/patches-4.4/137-net-mvneta-add-nway_reset-support.patch
new file mode 100644 (file)
index 0000000..034b596
--- /dev/null
@@ -0,0 +1,38 @@
+From 244bee2889d08f876c64c335765a8ea6de0f5381 Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Thu, 1 Oct 2015 19:40:31 +0100
+Subject: [PATCH 725/744] net: mvneta: add nway_reset support
+
+Add ethtool nway_reset support to mvneta via phylink, so that userspace
+can request the link in whatever mode to be renegotiated via
+ethtool -r ethX.
+
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ drivers/net/ethernet/marvell/mvneta.c | 8 ++++++++
+ 1 file changed, 8 insertions(+)
+
+--- a/drivers/net/ethernet/marvell/mvneta.c
++++ b/drivers/net/ethernet/marvell/mvneta.c
+@@ -3589,6 +3589,13 @@ int mvneta_ethtool_set_settings(struct n
+       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)
+@@ -3853,6 +3860,7 @@ const struct ethtool_ops mvneta_eth_tool
+       .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,
diff --git a/target/linux/mvebu/patches-4.4/138-phylink-add-flow-control-support.patch b/target/linux/mvebu/patches-4.4/138-phylink-add-flow-control-support.patch
new file mode 100644 (file)
index 0000000..95b8a81
--- /dev/null
@@ -0,0 +1,262 @@
+From f566177aa6661e646b83526f24391a568ffd1a75 Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Thu, 1 Oct 2015 20:32:07 +0100
+Subject: [PATCH 726/744] phylink: add flow control support
+
+Add flow control support, including ethtool support, to phylink.  We
+add support to allow ethtool to get and set the current flow control
+settings, and the 802.3 specified resolution for the local and remote
+link partner abilities.
+
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ drivers/net/phy/phylink.c | 145 +++++++++++++++++++++++++++++++++++++++++-----
+ include/linux/phylink.h   |   8 +++
+ 2 files changed, 139 insertions(+), 14 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -91,10 +91,12 @@ static int phylink_parse_fixedlink(struc
+               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;
++
++              /* 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"))
+@@ -118,7 +120,6 @@ static int phylink_parse_fixedlink(struc
+                       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]))
+@@ -130,16 +131,6 @@ static int phylink_parse_fixedlink(struc
+       }
+       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",
+@@ -242,6 +233,56 @@ static void phylink_get_fixed_state(stru
+               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)
+@@ -257,6 +298,7 @@ static void phylink_resolve(struct work_
+               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:
+@@ -265,9 +307,12 @@ static void phylink_resolve(struct work_
+               case MLO_AN_SGMII:
+                       phylink_get_mac_state(pl, &link_state);
+-                      if (pl->phydev)
++                      if (pl->phydev) {
+                               link_state.link = link_state.link &&
+                                                 pl->phy_state.link;
++                              link_state.pause |= pl->phy_state.pause;
++                              phylink_resolve_flow(pl, &link_state);
++                      }
+                       break;
+               case MLO_AN_8023Z:
+@@ -297,7 +342,7 @@ static void phylink_resolve(struct work_
+                                   "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);
+@@ -326,6 +371,7 @@ struct phylink *phylink_create(struct ne
+       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);
+@@ -507,6 +553,7 @@ void phylink_start(struct phylink *pl)
+        * a fixed-link to start with the correct parameters, and also
+        * ensures that we set the appropriate advertisment for Serdes links.
+        */
++      phylink_resolve_flow(pl, &pl->link_config);
+       phylink_mac_config(pl, &pl->link_config);
+       clear_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state);
+@@ -701,6 +748,76 @@ int phylink_ethtool_nway_reset(struct ph
+ }
+ EXPORT_SYMBOL_GPL(phylink_ethtool_nway_reset);
++void phylink_ethtool_get_pauseparam(struct phylink *pl,
++                                  struct ethtool_pauseparam *pause)
++{
++      mutex_lock(&pl->config_mutex);
++
++      pause->autoneg = !!(pl->link_config.pause & MLO_PAUSE_AN);
++      pause->rx_pause = !!(pl->link_config.pause & MLO_PAUSE_RX);
++      pause->tx_pause = !!(pl->link_config.pause & MLO_PAUSE_TX);
++
++      mutex_unlock(&pl->config_mutex);
++}
++EXPORT_SYMBOL_GPL(phylink_ethtool_get_pauseparam);
++
++static int __phylink_ethtool_set_pauseparam(struct phylink *pl,
++                                          struct ethtool_pauseparam *pause)
++{
++      struct phylink_link_state *config = &pl->link_config;
++
++      if (!(config->supported & (SUPPORTED_Pause | SUPPORTED_Asym_Pause)))
++              return -EOPNOTSUPP;
++
++      if (!(config->supported & SUPPORTED_Asym_Pause) &&
++          !pause->autoneg && pause->rx_pause != pause->tx_pause)
++              return -EINVAL;
++
++      config->pause &= ~(MLO_PAUSE_AN | MLO_PAUSE_TXRX_MASK);
++
++      if (pause->autoneg)
++              config->pause |= MLO_PAUSE_AN;
++      if (pause->rx_pause)
++              config->pause |= MLO_PAUSE_RX;
++      if (pause->tx_pause)
++              config->pause |= MLO_PAUSE_TX;
++
++      switch (pl->link_an_mode) {
++      case MLO_AN_PHY:
++              /* Silently mark the carrier down, and then trigger a resolve */
++              netif_carrier_off(pl->netdev);
++              phylink_run_resolve(pl);
++              break;
++
++      case MLO_AN_FIXED:
++              /* Should we allow fixed links to change against the config? */
++              phylink_resolve_flow(pl, config);
++              phylink_mac_config(pl, config);
++              break;
++
++      case MLO_AN_SGMII:
++      case MLO_AN_8023Z:
++              phylink_mac_config(pl, config);
++              phylink_mac_an_restart(pl);
++              break;
++      }
++
++      return 0;
++}
++
++int phylink_ethtool_set_pauseparam(struct phylink *pl,
++                                 struct ethtool_pauseparam *pause)
++{
++      int ret;
++
++      mutex_lock(&pl->config_mutex);
++      ret = __phylink_ethtool_set_pauseparam(pl, pause);
++      mutex_unlock(&pl->config_mutex);
++
++      return ret;
++}
++EXPORT_SYMBOL_GPL(phylink_ethtool_set_pauseparam);
++
+ /* This emulates MII registers for a fixed-mode phy operating as per the
+  * passed in state. "aneg" defines if we report negotiation is possible.
+  *
+--- 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);
diff --git a/target/linux/mvebu/patches-4.4/139-net-mvneta-add-flow-control-support-via-phylink.patch b/target/linux/mvebu/patches-4.4/139-net-mvneta-add-flow-control-support-via-phylink.patch
new file mode 100644 (file)
index 0000000..75cd46e
--- /dev/null
@@ -0,0 +1,66 @@
+From 7bd34822b9922beb22a6384d9190646105d259d8 Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Thu, 1 Oct 2015 17:41:44 +0100
+Subject: [PATCH 727/744] net: mvneta: add flow control support via phylink
+
+Add flow control support to mvneta, including the ethtool hooks.  This
+uses the phylink code to calculate the result of autonegotiation where
+a phy is attached, and to handle the ethtool settings.
+
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ drivers/net/ethernet/marvell/mvneta.c | 22 +++++++++++++++++++++-
+ 1 file changed, 21 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/ethernet/marvell/mvneta.c
++++ b/drivers/net/ethernet/marvell/mvneta.c
+@@ -3208,6 +3208,8 @@ static void mvneta_mac_config(struct net
+       if (state->advertising & ADVERTISED_Pause)
+               new_an |= MVNETA_GMAC_ADVERT_SYM_FLOW_CTRL;
++      if (state->pause & MLO_PAUSE_TXRX_MASK)
++              new_an |= MVNETA_GMAC_CONFIG_FLOW_CTRL;
+       switch (mode) {
+       case MLO_AN_SGMII:
+@@ -3232,7 +3234,7 @@ static void mvneta_mac_config(struct net
+                        /* The MAC only supports FD mode */
+                        MVNETA_GMAC_CONFIG_FULL_DUPLEX;
+-              if (state->an_enabled)
++              if (state->pause & MLO_PAUSE_AN && state->an_enabled)
+                       new_an |= MVNETA_GMAC_AN_FLOW_CTRL_EN;
+               break;
+@@ -3685,6 +3687,22 @@ static int mvneta_ethtool_set_ringparam(
+       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)
+ {
+@@ -3866,6 +3884,8 @@ const struct ethtool_ops mvneta_eth_tool
+       .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,
diff --git a/target/linux/mvebu/patches-4.4/140-net-mvneta-enable-flow-control-for-PHY-connections.patch b/target/linux/mvebu/patches-4.4/140-net-mvneta-enable-flow-control-for-PHY-connections.patch
new file mode 100644 (file)
index 0000000..e10574c
--- /dev/null
@@ -0,0 +1,32 @@
+From 62f8a12044265df11531750a240e516a5f1ff433 Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Thu, 1 Oct 2015 00:34:08 +0100
+Subject: [PATCH 728/744] net: mvneta: enable flow control for PHY connections
+
+Enable flow control support for PHY connections by indicating our
+support via the ethtool capabilities.  phylink takes care of the
+appropriate handling.
+
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ drivers/net/ethernet/marvell/mvneta.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+--- a/drivers/net/ethernet/marvell/mvneta.c
++++ b/drivers/net/ethernet/marvell/mvneta.c
+@@ -3127,12 +3127,14 @@ static int mvneta_mac_support(struct net
+               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;
diff --git a/target/linux/mvebu/patches-4.4/141-net-mvneta-enable-flow-control-for-fixed-connections.patch b/target/linux/mvebu/patches-4.4/141-net-mvneta-enable-flow-control-for-fixed-connections.patch
new file mode 100644 (file)
index 0000000..16ffab3
--- /dev/null
@@ -0,0 +1,53 @@
+From 4c3e2dc08a11fb1273ca62467f1d06e59866bad3 Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@armlinux.org.uk>
+Date: Tue, 12 Jul 2016 00:04:13 +0100
+Subject: [PATCH 729/744] net: mvneta: enable flow control for fixed
+ connections
+
+Allow symetric flow control to be enabled for fixed link connections as
+well as other types of connections by setting the supported and
+advertised capability bits.
+
+Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
+---
+ drivers/net/ethernet/marvell/mvneta.c | 11 +++++++----
+ 1 file changed, 7 insertions(+), 4 deletions(-)
+
+--- a/drivers/net/ethernet/marvell/mvneta.c
++++ b/drivers/net/ethernet/marvell/mvneta.c
+@@ -3114,9 +3114,9 @@ static int mvneta_mac_support(struct net
+       switch (mode) {
+       case MLO_AN_8023Z:
+               state->supported = SUPPORTED_1000baseT_Full |
+-                                 SUPPORTED_Autoneg | SUPPORTED_Pause;
++                                 SUPPORTED_Autoneg;
+               state->advertising = ADVERTISED_1000baseT_Full |
+-                                   ADVERTISED_Autoneg | ADVERTISED_Pause;
++                                   ADVERTISED_Autoneg;
+               state->an_enabled = 1;
+               break;
+@@ -3127,18 +3127,21 @@ static int mvneta_mac_support(struct net
+               state->supported = PHY_10BT_FEATURES |
+                                  PHY_100BT_FEATURES |
+                                  SUPPORTED_1000baseT_Full |
+-                                 SUPPORTED_Pause |
+                                  SUPPORTED_Autoneg;
+               state->advertising = ADVERTISED_10baseT_Half |
+                                    ADVERTISED_10baseT_Full |
+                                    ADVERTISED_100baseT_Half |
+                                    ADVERTISED_100baseT_Full |
+                                    ADVERTISED_1000baseT_Full |
+-                                   ADVERTISED_Pause |
+                                    ADVERTISED_Autoneg;
+               state->an_enabled = 1;
+               break;
+       }
++
++      /* All modes support flow control */
++      state->supported |= SUPPORTED_Pause;
++      state->advertising |= ADVERTISED_Pause;
++
+       return 0;
+ }
diff --git a/target/linux/mvebu/patches-4.4/142-phylink-add-EEE-support.patch b/target/linux/mvebu/patches-4.4/142-phylink-add-EEE-support.patch
new file mode 100644 (file)
index 0000000..b06ec76
--- /dev/null
@@ -0,0 +1,111 @@
+From ffba226d73a2be262fff12d30aecf76d107b2ace Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Thu, 1 Oct 2015 21:19:53 +0100
+Subject: [PATCH 730/744] phylink: add EEE support
+
+Add EEE hooks to phylink to allow the phylib EEE functions for the
+connected phy to be safely accessed.
+
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ drivers/net/phy/phylink.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++-
+ include/linux/phylink.h   |  7 +++++-
+ 2 files changed, 63 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -334,7 +334,8 @@ static void phylink_resolve(struct work_
+                       if (pl->phydev)
+                               phylink_mac_config(pl, &link_state);
+-                      pl->ops->mac_link_up(ndev, pl->link_an_mode);
++                      pl->ops->mac_link_up(ndev, pl->link_an_mode,
++                                           pl->phydev);
+                       netif_carrier_on(ndev);
+@@ -818,6 +819,61 @@ int phylink_ethtool_set_pauseparam(struc
+ }
+ 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.
+  *
+--- 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(stru
+                                   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);
diff --git a/target/linux/mvebu/patches-4.4/143-net-mvneta-add-EEE-support.patch b/target/linux/mvebu/patches-4.4/143-net-mvneta-add-EEE-support.patch
new file mode 100644 (file)
index 0000000..b904356
--- /dev/null
@@ -0,0 +1,182 @@
+From b7dacf514e41d6efff0ccc170f660cc6dc2aeae2 Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Tue, 29 Sep 2015 15:17:39 +0100
+Subject: [PATCH 731/744] net: mvneta: add EEE support
+
+Add EEE support to mvneta.  This allows us to enable the low power idle
+support at MAC level if there is a PHY attached through phylink which
+supports LPI.  The appropriate ethtool support is provided to allow the
+feature to be controlled, including ethtool statistics for EEE wakeup
+errors.
+
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ drivers/net/ethernet/marvell/mvneta.c | 87 +++++++++++++++++++++++++++++++++++
+ 1 file changed, 87 insertions(+)
+
+--- a/drivers/net/ethernet/marvell/mvneta.c
++++ b/drivers/net/ethernet/marvell/mvneta.c
+@@ -243,6 +243,12 @@
+ #define MVNETA_TXQ_TOKEN_SIZE_REG(q)             (0x3e40 + ((q) << 2))
+ #define      MVNETA_TXQ_TOKEN_SIZE_MAX           0x7fffffff
++#define MVNETA_LPI_CTRL_0                        0x2cc0
++#define MVNETA_LPI_CTRL_1                        0x2cc4
++#define      MVNETA_LPI_REQUEST_ENABLE           BIT(0)
++#define MVNETA_LPI_CTRL_2                        0x2cc8
++#define MVNETA_LPI_STATUS                        0x2ccc
++
+ #define MVNETA_CAUSE_TXQ_SENT_DESC_ALL_MASK    0xff
+ /* Descriptor ring Macros */
+@@ -316,6 +322,11 @@
+ #define MVNETA_RX_GET_BM_POOL_ID(rxd) \
+       (((rxd)->status & MVNETA_RXD_BM_POOL_MASK) >> MVNETA_RXD_BM_POOL_SHIFT)
++enum {
++      ETHTOOL_STAT_EEE_WAKEUP,
++      ETHTOOL_MAX_STATS,
++};
++
+ struct mvneta_statistic {
+       unsigned short offset;
+       unsigned short type;
+@@ -324,6 +335,7 @@ struct mvneta_statistic {
+ #define T_REG_32      32
+ #define T_REG_64      64
++#define T_SW          1
+ static const struct mvneta_statistic mvneta_statistics[] = {
+       { 0x3000, T_REG_64, "good_octets_received", },
+@@ -358,6 +370,7 @@ static const struct mvneta_statistic mvn
+       { 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 {
+@@ -413,6 +426,10 @@ struct mvneta_port {
+       struct mvneta_bm_pool *pool_short;
+       int bm_win_id;
++      bool eee_enabled;
++      bool eee_active;
++      bool tx_lpi_enabled;
++
+       u64 ethtool_stats[ARRAY_SIZE(mvneta_statistics)];
+       u32 indir[MVNETA_RSS_LU_TABLE_SIZE];
+@@ -3276,6 +3293,18 @@ static void mvneta_mac_config(struct net
+               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);
+@@ -3289,6 +3318,9 @@ static void mvneta_mac_link_down(struct
+               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,
+@@ -3305,6 +3337,11 @@ static void mvneta_mac_link_up(struct ne
+       }
+       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 = {
+@@ -3744,6 +3781,13 @@ static void mvneta_ethtool_update_stats(
+                       val64 = (u64)high << 32 | low;
+                       pp->ethtool_stats[i] += val64;
+                       break;
++              case T_SW:
++                      switch (s->offset) {
++                      case ETHTOOL_STAT_EEE_WAKEUP:
++                              val = phylink_get_eee_err(pp->phylink);
++                              break;
++                      }
++                      break;
+               }
+       }
+ }
+@@ -3867,6 +3911,47 @@ static int mvneta_ethtool_get_rxfh(struc
+       return 0;
+ }
++static int mvneta_ethtool_get_eee(struct net_device *dev,
++                                struct ethtool_eee *eee)
++{
++      struct mvneta_port *pp = netdev_priv(dev);
++      u32 lpi_ctl0;
++
++      lpi_ctl0 = mvreg_read(pp, MVNETA_LPI_CTRL_0);
++
++      eee->eee_enabled = pp->eee_enabled;
++      eee->eee_active = pp->eee_active;
++      eee->tx_lpi_enabled = pp->tx_lpi_enabled;
++      eee->tx_lpi_timer = (lpi_ctl0) >> 8; // * scale;
++
++      return phylink_ethtool_get_eee(pp->phylink, eee);
++}
++
++static int mvneta_ethtool_set_eee(struct net_device *dev,
++                                struct ethtool_eee *eee)
++{
++      struct mvneta_port *pp = netdev_priv(dev);
++      u32 lpi_ctl0;
++
++      /* The Armada 37x documents do not give limits for this other than
++       * it being an 8-bit register. */
++      if (eee->tx_lpi_enabled &&
++          (eee->tx_lpi_timer < 0 || eee->tx_lpi_timer > 255))
++              return -EINVAL;
++
++      lpi_ctl0 = mvreg_read(pp, MVNETA_LPI_CTRL_0);
++      lpi_ctl0 &= ~(0xff << 8);
++      lpi_ctl0 |= eee->tx_lpi_timer << 8;
++      mvreg_write(pp, MVNETA_LPI_CTRL_0, lpi_ctl0);
++
++      pp->eee_enabled = eee->eee_enabled;
++      pp->tx_lpi_enabled = eee->tx_lpi_enabled;
++
++      mvneta_set_eee(pp, eee->tx_lpi_enabled && eee->eee_enabled);
++
++      return phylink_ethtool_set_eee(pp->phylink, eee);
++}
++
+ static const struct net_device_ops mvneta_netdev_ops = {
+       .ndo_open            = mvneta_open,
+       .ndo_stop            = mvneta_stop,
+@@ -3898,6 +3983,8 @@ const struct ethtool_ops mvneta_eth_tool
+       .get_rxnfc      = mvneta_ethtool_get_rxnfc,
+       .get_rxfh       = mvneta_ethtool_get_rxfh,
+       .set_rxfh       = mvneta_ethtool_set_rxfh,
++      .get_eee        = mvneta_ethtool_get_eee,
++      .set_eee        = mvneta_ethtool_set_eee,
+ };
+ /* Initialize hw */
diff --git a/target/linux/mvebu/patches-4.4/144-phylink-add-module-EEPROM-support.patch b/target/linux/mvebu/patches-4.4/144-phylink-add-module-EEPROM-support.patch
new file mode 100644 (file)
index 0000000..f57f70d
--- /dev/null
@@ -0,0 +1,137 @@
+From 5419ccb638aa5c353ea88815e98953d9fc02e6ca Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Thu, 1 Oct 2015 23:10:05 +0100
+Subject: [PATCH 732/744] phylink: add module EEPROM support
+
+Add support for reading module EEPROMs through phylink.
+
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+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(+)
+
+--- a/drivers/net/phy/phylink.c
++++ b/drivers/net/phy/phylink.c
+@@ -60,6 +60,9 @@ struct phylink {
+       struct work_struct resolve;
+       bool mac_link_up;
++
++      const struct phylink_module_ops *module_ops;
++      void *module_data;
+ };
+ static const char *phylink_an_mode_str(unsigned int mode)
+@@ -819,6 +822,36 @@ int phylink_ethtool_set_pauseparam(struc
+ }
+ EXPORT_SYMBOL_GPL(phylink_ethtool_set_pauseparam);
++int phylink_ethtool_get_module_info(struct phylink *pl,
++                                  struct ethtool_modinfo *modinfo)
++{
++      int ret = -EOPNOTSUPP;
++
++      mutex_lock(&pl->config_mutex);
++      if (pl->module_ops)
++              ret = pl->module_ops->get_module_info(pl->module_data,
++                                                    modinfo);
++      mutex_unlock(&pl->config_mutex);
++
++      return ret;
++}
++EXPORT_SYMBOL_GPL(phylink_ethtool_get_module_info);
++
++int phylink_ethtool_get_module_eeprom(struct phylink *pl,
++                                    struct ethtool_eeprom *ee, u8 *buf)
++{
++      int ret = -EOPNOTSUPP;
++
++      mutex_lock(&pl->config_mutex);
++      if (pl->module_ops)
++              ret = pl->module_ops->get_module_eeprom(pl->module_data, ee,
++                                                      buf);
++      mutex_unlock(&pl->config_mutex);
++
++      return ret;
++}
++EXPORT_SYMBOL_GPL(phylink_ethtool_get_module_eeprom);
++
+ int phylink_init_eee(struct phylink *pl, bool clk_stop_enable)
+ {
+       int ret = -EPROTONOSUPPORT;
+@@ -1016,6 +1049,39 @@ EXPORT_SYMBOL_GPL(phylink_mii_ioctl);
++int phylink_register_module(struct phylink *pl, void *data,
++                          const struct phylink_module_ops *ops)
++{
++      int ret = -EBUSY;
++
++      mutex_lock(&pl->config_mutex);
++      if (!pl->module_ops) {
++              pl->module_ops = ops;
++              pl->module_data = data;
++              ret = 0;
++      }
++      mutex_unlock(&pl->config_mutex);
++
++      return ret;
++}
++EXPORT_SYMBOL_GPL(phylink_register_module);
++
++int phylink_unregister_module(struct phylink *pl, void *data)
++{
++      int ret = -EINVAL;
++
++      mutex_lock(&pl->config_mutex);
++      if (pl->module_data == data) {
++              pl->module_ops = NULL;
++              pl->module_data = NULL;
++              ret = 0;
++      }
++      mutex_unlock(&pl->config_mutex);
++
++      return ret;
++}
++EXPORT_SYMBOL_GPL(phylink_unregister_module);
++
+ void phylink_disable(struct phylink *pl)
+ {
+       set_bit(PHYLINK_DISABLE_LINK, &pl->phylink_disable_state);
+--- 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(stru
+                                   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);
diff --git a/target/linux/mvebu/patches-4.4/145-net-mvneta-add-module-EEPROM-reading-support.patch b/target/linux/mvebu/patches-4.4/145-net-mvneta-add-module-EEPROM-reading-support.patch
new file mode 100644 (file)
index 0000000..b3f9039
--- /dev/null
@@ -0,0 +1,44 @@
+From 665e1fe77dedcfc6b5669214ebfd252c803290d4 Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Thu, 1 Oct 2015 23:32:39 +0100
+Subject: [PATCH 733/744] net: mvneta: add module EEPROM reading support
+
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ drivers/net/ethernet/marvell/mvneta.c | 18 ++++++++++++++++++
+ 1 file changed, 18 insertions(+)
+
+--- a/drivers/net/ethernet/marvell/mvneta.c
++++ b/drivers/net/ethernet/marvell/mvneta.c
+@@ -3911,6 +3911,22 @@ static int mvneta_ethtool_get_rxfh(struc
+       return 0;
+ }
++static int mvneta_ethtool_get_module_info(struct net_device *dev,
++                                        struct ethtool_modinfo *modinfo)
++{
++      struct mvneta_port *pp = netdev_priv(dev);
++
++      return phylink_ethtool_get_module_info(pp->phylink, modinfo);
++}
++
++static int mvneta_ethtool_get_module_eeprom(struct net_device *dev,
++                                          struct ethtool_eeprom *ee, u8 *buf)
++{
++      struct mvneta_port *pp = netdev_priv(dev);
++
++      return phylink_ethtool_get_module_eeprom(pp->phylink, ee, buf);
++}
++
+ static int mvneta_ethtool_get_eee(struct net_device *dev,
+                                 struct ethtool_eee *eee)
+ {
+@@ -3983,6 +3999,8 @@ const struct ethtool_ops mvneta_eth_tool
+       .get_rxnfc      = mvneta_ethtool_get_rxnfc,
+       .get_rxfh       = mvneta_ethtool_get_rxfh,
+       .set_rxfh       = mvneta_ethtool_set_rxfh,
++      .get_module_info = mvneta_ethtool_get_module_info,
++      .get_module_eeprom = mvneta_ethtool_get_module_eeprom,
+       .get_eee        = mvneta_ethtool_get_eee,
+       .set_eee        = mvneta_ethtool_set_eee,
+ };
diff --git a/target/linux/mvebu/patches-4.4/146-sfp-phylink-hook-up-eeprom-functions.patch b/target/linux/mvebu/patches-4.4/146-sfp-phylink-hook-up-eeprom-functions.patch
new file mode 100644 (file)
index 0000000..f37e652
--- /dev/null
@@ -0,0 +1,68 @@
+From a7091ef24223ed39b39c6b73b77c55c8a607f34a Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Thu, 8 Oct 2015 23:49:47 +0100
+Subject: [PATCH 734/744] sfp/phylink: hook up eeprom functions
+
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ drivers/net/phy/sfp.c | 19 +++++++++++--------
+ 1 file changed, 11 insertions(+), 8 deletions(-)
+
+--- a/drivers/net/phy/sfp.c
++++ b/drivers/net/phy/sfp.c
+@@ -901,11 +901,9 @@ static void sfp_sm_event(struct sfp *sfp
+       mutex_unlock(&sfp->sm_mutex);
+ }
+-#if 0
+-static int sfp_phy_module_info(struct phy_device *phy,
+-                             struct ethtool_modinfo *modinfo)
++static int sfp_module_info(void *priv, struct ethtool_modinfo *modinfo)
+ {
+-      struct sfp *sfp = phy->priv;
++      struct sfp *sfp = priv;
+       /* locking... and check module is present */
+@@ -919,10 +917,9 @@ static int sfp_phy_module_info(struct ph
+       return 0;
+ }
+-static int sfp_phy_module_eeprom(struct phy_device *phy,
+-      struct ethtool_eeprom *ee, u8 *data)
++static int sfp_module_eeprom(void *priv, struct ethtool_eeprom *ee, u8 *data)
+ {
+-      struct sfp *sfp = phy->priv;
++      struct sfp *sfp = priv;
+       unsigned int first, last, len;
+       int ret;
+@@ -953,7 +950,11 @@ static int sfp_phy_module_eeprom(struct
+       }
+       return 0;
+ }
+-#endif
++
++static const struct phylink_module_ops sfp_module_ops = {
++      .get_module_info = sfp_module_info,
++      .get_module_eeprom = sfp_module_eeprom,
++};
+ static void sfp_timeout(struct work_struct *work)
+ {
+@@ -1029,6 +1030,7 @@ static int sfp_netdev_notify(struct noti
+       case NETDEV_UNREGISTER:
+               if (sfp->mod_phy && sfp->phylink)
+                       phylink_disconnect_phy(sfp->phylink);
++              phylink_unregister_module(sfp->phylink, sfp);
+               sfp->phylink = NULL;
+               dev_put(sfp->ndev);
+               sfp->ndev = NULL;
+@@ -1145,6 +1147,7 @@ static int sfp_probe(struct platform_dev
+               }
+               phylink_disable(sfp->phylink);
++              phylink_register_module(sfp->phylink, sfp, &sfp_module_ops);
+       }
+       sfp->state = sfp_get_state(sfp);
diff --git a/target/linux/mvebu/patches-4.4/210-ARM-dts-armada388-clearfog-add-SFP-module-support.patch b/target/linux/mvebu/patches-4.4/210-ARM-dts-armada388-clearfog-add-SFP-module-support.patch
new file mode 100644 (file)
index 0000000..9efcff9
--- /dev/null
@@ -0,0 +1,84 @@
+From 63ff73593c2f5d3fc1cba479321d192caaca48aa Mon Sep 17 00:00:00 2001
+From: Russell King <rmk+kernel@arm.linux.org.uk>
+Date: Sat, 12 Sep 2015 18:43:39 +0100
+Subject: [PATCH 738/744] ARM: dts: armada388-clearfog: add SFP module support
+
+Add SFP module support for Clearfog using the SFP phylink support.
+
+Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
+---
+ arch/arm/boot/dts/armada-388-clearfog.dts | 44 ++++++++-----------------------
+ 1 file changed, 11 insertions(+), 33 deletions(-)
+
+--- a/arch/arm/boot/dts/armada-388-clearfog.dts
++++ b/arch/arm/boot/dts/armada-388-clearfog.dts
+@@ -90,16 +90,12 @@
+                       };
+                       ethernet@34000 {
++                              managed = "in-band-status";
+                               phy-mode = "sgmii";
+                               buffer-manager = <&bm>;
+                               bm,pool-long = <3>;
+                               bm,pool-short = <1>;
+                               status = "okay";
+-
+-                              fixed-link {
+-                                      speed = <1000>;
+-                                      full-duplex;
+-                              };
+                       };
+                       i2c@11000 {
+@@ -183,34 +179,6 @@
+                                               output-low;
+                                               line-name = "m.2 devslp";
+                                       };
+-                                      sfp_los {
+-                                              /* SFP loss of signal */
+-                                              gpio-hog;
+-                                              gpios = <12 GPIO_ACTIVE_HIGH>;
+-                                              input;
+-                                              line-name = "sfp-los";
+-                                      };
+-                                      sfp_tx_fault {
+-                                              /* SFP laser fault */
+-                                              gpio-hog;
+-                                              gpios = <13 GPIO_ACTIVE_HIGH>;
+-                                              input;
+-                                              line-name = "sfp-tx-fault";
+-                                      };
+-                                      sfp_tx_disable {
+-                                              /* SFP transmit disable */
+-                                              gpio-hog;
+-                                              gpios = <14 GPIO_ACTIVE_HIGH>;
+-                                              output-low;
+-                                              line-name = "sfp-tx-disable";
+-                                      };
+-                                      sfp_mod_def0 {
+-                                              /* SFP module present */
+-                                              gpio-hog;
+-                                              gpios = <15 GPIO_ACTIVE_LOW>;
+-                                              input;
+-                                              line-name = "sfp-mod-def0";
+-                                      };
+                               };
+                               /* The MCP3021 is 100kHz clock only */
+@@ -374,6 +342,16 @@
+               };
+       };
++      sfp: sfp {
++              compatible = "sff,sfp";
++              i2c-bus = <&i2c1>;
++              los-gpio = <&expander0 12 GPIO_ACTIVE_HIGH>;
++              moddef0-gpio = <&expander0 15 GPIO_ACTIVE_LOW>;
++              sfp,ethernet = <&eth2>;
++              tx-disable-gpio = <&expander0 14 GPIO_ACTIVE_HIGH>;
++              tx-fault-gpio = <&expander0 13 GPIO_ACTIVE_HIGH>;
++      };
++
+       dsa@0 {
+               compatible = "marvell,dsa";
+               dsa,ethernet = <&eth1>;
diff --git a/target/linux/mvebu/patches-4.4/300-reprobe_sfp_phy.patch b/target/linux/mvebu/patches-4.4/300-reprobe_sfp_phy.patch
new file mode 100644 (file)
index 0000000..42614ef
--- /dev/null
@@ -0,0 +1,96 @@
+From 28baa5e2635285b178326b301f534ed95c65dd01 Mon Sep 17 00:00:00 2001
+From: Jonas Gorski <jonas.gorski@gmail.com>
+Date: Thu, 29 Sep 2016 11:44:39 +0200
+Subject: [PATCH] sfp: retry phy probe if unsuccessful
+
+Some phys seem to take longer than 50 ms to come out of reset, so retry
+until we find a phy.
+
+Signed-off-by: Jonas Gorski <jonas.gorski@gmail.com>
+---
+ drivers/net/phy/sfp.c | 38 +++++++++++++++++++++++++-------------
+ 1 file changed, 25 insertions(+), 13 deletions(-)
+
+--- a/drivers/net/phy/sfp.c
++++ b/drivers/net/phy/sfp.c
+@@ -488,7 +488,7 @@ static void sfp_sm_phy_detach(struct sfp
+       sfp->mod_phy = NULL;
+ }
+-static void sfp_sm_probe_phy(struct sfp *sfp)
++static int sfp_sm_probe_phy(struct sfp *sfp)
+ {
+       struct phy_device *phy;
+       int err;
+@@ -498,11 +498,11 @@ static void sfp_sm_probe_phy(struct sfp
+       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;
++              return PTR_ERR(phy);
+       }
+       if (!phy) {
+-              dev_info(sfp->dev, "no PHY detected\n");
+-              return;
++              dev_dbg(sfp->dev, "no PHY detected\n");
++              return -EAGAIN;
+       }
+       err = phylink_connect_phy(sfp->phylink, phy);
+@@ -510,11 +510,13 @@ static void sfp_sm_probe_phy(struct sfp
+               phy_device_remove(phy);
+               phy_device_free(phy);
+               dev_err(sfp->dev, "phylink_connect_phy failed: %d\n", err);
+-              return;
++              return err;
+       }
+       sfp->mod_phy = phy;
+       phy_start(phy);
++
++      return 0;
+ }
+ static void sfp_sm_link_up(struct sfp *sfp)
+@@ -565,13 +567,6 @@ static void sfp_sm_mod_init(struct sfp *
+ {
+       sfp_module_tx_enable(sfp);
+-      /* Wait t_init before indicating that the link is up, provided the
+-       * current state indicates no TX_FAULT.  If TX_FAULT clears before
+-       * this time, that's fine too.
+-       */
+-      sfp_sm_next(sfp, SFP_S_INIT, T_INIT_JIFFIES);
+-      sfp->sm_retries = 5;
+-
+       if (sfp->phylink) {
+               /* Setting the serdes link mode is guesswork: there's no
+                * field in the EEPROM which indicates what mode should
+@@ -587,9 +582,26 @@ static void sfp_sm_mod_init(struct sfp *
+                   !sfp->id.base.e100_base_fx) {
+                       phylink_set_link_an_mode(sfp->phylink, MLO_AN_8023Z);
+               } else {
++                      int ret;
++
+                       phylink_set_link_an_mode(sfp->phylink, MLO_AN_SGMII);
+-                      sfp_sm_probe_phy(sfp);
++
++                      ret = sfp_sm_probe_phy(sfp);
++                      if (ret) {
++                              if (ret == -EAGAIN)
++                                      sfp_sm_set_timer(sfp, T_PROBE_RETRY);
++                              else
++                                      sfp_sm_next(sfp, SFP_S_TX_DISABLE, 0);
++                              return;
++                      }
+               }
++
++              /* 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;
+       }
+ }