summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid S. Miller <davem@davemloft.net>2017-06-06 21:14:21 -0400
committerDavid S. Miller <davem@davemloft.net>2017-06-06 21:14:21 -0400
commit9747e2313838ee8f5d8073fd6aa7289255c3c51b (patch)
treeb0fb2db74a1881f13303261ddd9083328b7500df
parent92046578ac88e0a93f8ef03240e6c832b0189aa7 (diff)
parent20b2af32ff3f0ac74f2bfd0bc2c175b56002d1f1 (diff)
downloadlinux-9747e2313838ee8f5d8073fd6aa7289255c3c51b.tar.bz2
Merge branch 'phylib-support-for-MV88X3310-10G-phy'
Russell King says: ==================== net: Add phylib support for MV88X3310 10G phy This patch series adds support for the Marvell 88x3310 PHY found on the SolidRun Macchiatobin board. The first patch introduces a set of generic Clause 45 PHY helpers that C45 PHY drivers can make use of if they wish. Patch 2 ensures that the Clause 22 aneg_done function will not be called for incompatible Clause 45 PHYs. Patch 3 fixes the aneg restart to be compatible with C45 PHYs - it can currently only cope with C22 PHYs. Patch 4 moves the "gen10g" driver into the Clause 45 code, grouping all core clause 45 code together. Patch 5 adds the phy_interface_t types for XAUI and 10GBase-KR links. As 10GBase-KR appears to be compatible with XFI and SFI, XFI and SFI, I currently see no reason to add XFI and SFI interface modes. There seems to be vendor code out there using these, but they all alias back to the same hardware settings. Patch 6 adds support for the MV88X3310 PHY, which supports both the copper and fiber interfaces. It should be noted that the MV88X3310 automatically switches its MAC facing interface between 10GBase-KR and SGMII depending on the negotiated speed. This was discussed with Florian, and we agreed to update the phy interface mode depending on the properties of the actual link mode to the PHY. v2: - update sysfs-class-net-phydev documentation - avoid genphy_aneg_done for non-C22 PHYs - expand comment about 0x30 constant - add comment about lack of reset - configure driver using MARVELL_10G_PHY ==================== Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r--Documentation/ABI/testing/sysfs-class-net-phydev2
-rw-r--r--Documentation/devicetree/bindings/net/ethernet.txt2
-rw-r--r--MAINTAINERS6
-rw-r--r--drivers/net/phy/Kconfig5
-rw-r--r--drivers/net/phy/Makefile3
-rw-r--r--drivers/net/phy/marvell10g.c368
-rw-r--r--drivers/net/phy/phy-c45.c298
-rw-r--r--drivers/net/phy/phy.c29
-rw-r--r--drivers/net/phy/phy_device.c113
-rw-r--r--include/linux/phy.h20
10 files changed, 748 insertions, 98 deletions
diff --git a/Documentation/ABI/testing/sysfs-class-net-phydev b/Documentation/ABI/testing/sysfs-class-net-phydev
index c768d5fd8496..6ebabfb27912 100644
--- a/Documentation/ABI/testing/sysfs-class-net-phydev
+++ b/Documentation/ABI/testing/sysfs-class-net-phydev
@@ -32,5 +32,5 @@ Description:
<empty> (not available), mii, gmii, sgmii, tbi, rev-mii,
rmii, rgmii, rgmii-id, rgmii-rxid, rgmii-txid, rtbi, smii
xgmii, moca, qsgmii, trgmii, 1000base-x, 2500base-x, rxaui,
- unknown
+ xaui, 10gbase-kr, unknown
diff --git a/Documentation/devicetree/bindings/net/ethernet.txt b/Documentation/devicetree/bindings/net/ethernet.txt
index 3a6916909d90..d4abe9a98109 100644
--- a/Documentation/devicetree/bindings/net/ethernet.txt
+++ b/Documentation/devicetree/bindings/net/ethernet.txt
@@ -32,6 +32,8 @@ The following properties are common to the Ethernet controllers:
* "2000base-x",
* "2500base-x",
* "rxaui"
+ * "xaui"
+ * "10gbase-kr" (10GBASE-KR, XFI, SFI)
- phy-connection-type: the same as "phy-mode" property but described in ePAPR;
- phy-handle: phandle, specifies a reference to a node representing a PHY
device; this property is described in ePAPR and so preferred;
diff --git a/MAINTAINERS b/MAINTAINERS
index 6b7625ff9875..3cf8b0a22019 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7979,6 +7979,12 @@ S: Maintained
F: drivers/net/ethernet/marvell/mv643xx_eth.*
F: include/linux/mv643xx.h
+MARVELL MV88X3310 PHY DRIVER
+M: Russell King <rmk@armlinux.org.uk>
+L: netdev@vger.kernel.org
+S: Maintained
+F: drivers/net/phy/marvell10g.c
+
MARVELL MVNETA ETHERNET DRIVER
M: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
L: netdev@vger.kernel.org
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index 0c516d3229d0..65af31f24f01 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -292,6 +292,11 @@ config MARVELL_PHY
---help---
Currently has a driver for the 88E1011S
+config MARVELL_10G_PHY
+ tristate "Marvell Alaska 10Gbit PHYs"
+ ---help---
+ Support for the Marvell Alaska MV88X3310 and compatible PHYs.
+
config MESON_GXL_PHY
tristate "Amlogic Meson GXL Internal PHY"
depends on ARCH_MESON || COMPILE_TEST
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index e2fde094f63d..8e9b9f349384 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -1,6 +1,6 @@
# Makefile for Linux PHY drivers and MDIO bus drivers
-libphy-y := phy.o phy-core.o phy_device.o
+libphy-y := phy.o phy-c45.o phy-core.o phy_device.o
mdio-bus-y += mdio_bus.o mdio_device.o
ifdef CONFIG_MDIO_DEVICE
@@ -57,6 +57,7 @@ obj-$(CONFIG_INTEL_XWAY_PHY) += intel-xway.o
obj-$(CONFIG_LSI_ET1011C_PHY) += et1011c.o
obj-$(CONFIG_LXT_PHY) += lxt.o
obj-$(CONFIG_MARVELL_PHY) += marvell.o
+obj-$(CONFIG_MARVELL_10G_PHY) += marvell10g.o
obj-$(CONFIG_MESON_GXL_PHY) += meson-gxl.o
obj-$(CONFIG_MICREL_KS8995MA) += spi_ks8995.o
obj-$(CONFIG_MICREL_PHY) += micrel.o
diff --git a/drivers/net/phy/marvell10g.c b/drivers/net/phy/marvell10g.c
new file mode 100644
index 000000000000..aebc08beceba
--- /dev/null
+++ b/drivers/net/phy/marvell10g.c
@@ -0,0 +1,368 @@
+/*
+ * Marvell 10G 88x3310 PHY driver
+ *
+ * Based upon the ID registers, this PHY appears to be a mixture of IPs
+ * from two different companies.
+ *
+ * There appears to be several different data paths through the PHY which
+ * are automatically managed by the PHY. The following has been determined
+ * via observation and experimentation:
+ *
+ * SGMII PHYXS -- BASE-T PCS -- 10G PMA -- AN -- Copper (for <= 1G)
+ * 10GBASE-KR PHYXS -- BASE-T PCS -- 10G PMA -- AN -- Copper (for 10G)
+ * 10GBASE-KR PHYXS -- BASE-R PCS -- Fiber
+ *
+ * If both the fiber and copper ports are connected, the first to gain
+ * link takes priority and the other port is completely locked out.
+ */
+#include <linux/phy.h>
+
+enum {
+ MV_PCS_BASE_T = 0x0000,
+ MV_PCS_BASE_R = 0x1000,
+ MV_PCS_1000BASEX = 0x2000,
+
+ /* These registers appear at 0x800X and 0xa00X - the 0xa00X control
+ * registers appear to set themselves to the 0x800X when AN is
+ * restarted, but status registers appear readable from either.
+ */
+ MV_AN_CTRL1000 = 0x8000, /* 1000base-T control register */
+ MV_AN_STAT1000 = 0x8001, /* 1000base-T status register */
+
+ /* This register appears to reflect the copper status */
+ MV_AN_RESULT = 0xa016,
+ MV_AN_RESULT_SPD_10 = BIT(12),
+ MV_AN_RESULT_SPD_100 = BIT(13),
+ MV_AN_RESULT_SPD_1000 = BIT(14),
+ MV_AN_RESULT_SPD_10000 = BIT(15),
+};
+
+static int mv3310_modify(struct phy_device *phydev, int devad, u16 reg,
+ u16 mask, u16 bits)
+{
+ int old, val, ret;
+
+ old = phy_read_mmd(phydev, devad, reg);
+ if (old < 0)
+ return old;
+
+ val = (old & ~mask) | (bits & mask);
+ if (val == old)
+ return 0;
+
+ ret = phy_write_mmd(phydev, devad, reg, val);
+
+ return ret < 0 ? ret : 1;
+}
+
+static int mv3310_probe(struct phy_device *phydev)
+{
+ u32 mmd_mask = MDIO_DEVS_PMAPMD | MDIO_DEVS_AN;
+
+ if (!phydev->is_c45 ||
+ (phydev->c45_ids.devices_in_package & mmd_mask) != mmd_mask)
+ return -ENODEV;
+
+ return 0;
+}
+
+/*
+ * Resetting the MV88X3310 causes it to become non-responsive. Avoid
+ * setting the reset bit(s).
+ */
+static int mv3310_soft_reset(struct phy_device *phydev)
+{
+ return 0;
+}
+
+static int mv3310_config_init(struct phy_device *phydev)
+{
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(supported) = { 0, };
+ u32 mask;
+ int val;
+
+ /* Check that the PHY interface type is compatible */
+ if (phydev->interface != PHY_INTERFACE_MODE_SGMII &&
+ phydev->interface != PHY_INTERFACE_MODE_XGMII &&
+ phydev->interface != PHY_INTERFACE_MODE_XAUI &&
+ phydev->interface != PHY_INTERFACE_MODE_RXAUI &&
+ phydev->interface != PHY_INTERFACE_MODE_10GKR)
+ return -ENODEV;
+
+ __set_bit(ETHTOOL_LINK_MODE_Pause_BIT, supported);
+ __set_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, supported);
+
+ if (phydev->c45_ids.devices_in_package & MDIO_DEVS_AN) {
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1);
+ if (val < 0)
+ return val;
+
+ if (val & MDIO_AN_STAT1_ABLE)
+ __set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, supported);
+ }
+
+ val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_STAT2);
+ if (val < 0)
+ return val;
+
+ /* Ethtool does not support the WAN mode bits */
+ if (val & (MDIO_PMA_STAT2_10GBSR | MDIO_PMA_STAT2_10GBLR |
+ MDIO_PMA_STAT2_10GBER | MDIO_PMA_STAT2_10GBLX4 |
+ MDIO_PMA_STAT2_10GBSW | MDIO_PMA_STAT2_10GBLW |
+ MDIO_PMA_STAT2_10GBEW))
+ __set_bit(ETHTOOL_LINK_MODE_FIBRE_BIT, supported);
+ if (val & MDIO_PMA_STAT2_10GBSR)
+ __set_bit(ETHTOOL_LINK_MODE_10000baseSR_Full_BIT, supported);
+ if (val & MDIO_PMA_STAT2_10GBLR)
+ __set_bit(ETHTOOL_LINK_MODE_10000baseLR_Full_BIT, supported);
+ if (val & MDIO_PMA_STAT2_10GBER)
+ __set_bit(ETHTOOL_LINK_MODE_10000baseER_Full_BIT, supported);
+
+ if (val & MDIO_PMA_STAT2_EXTABLE) {
+ val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PMA_EXTABLE);
+ if (val < 0)
+ return val;
+
+ if (val & (MDIO_PMA_EXTABLE_10GBT | MDIO_PMA_EXTABLE_1000BT |
+ MDIO_PMA_EXTABLE_100BTX | MDIO_PMA_EXTABLE_10BT))
+ __set_bit(ETHTOOL_LINK_MODE_TP_BIT, supported);
+ if (val & MDIO_PMA_EXTABLE_10GBLRM)
+ __set_bit(ETHTOOL_LINK_MODE_FIBRE_BIT, supported);
+ if (val & (MDIO_PMA_EXTABLE_10GBKX4 | MDIO_PMA_EXTABLE_10GBKR |
+ MDIO_PMA_EXTABLE_1000BKX))
+ __set_bit(ETHTOOL_LINK_MODE_Backplane_BIT, supported);
+ if (val & MDIO_PMA_EXTABLE_10GBLRM)
+ __set_bit(ETHTOOL_LINK_MODE_10000baseLRM_Full_BIT,
+ supported);
+ if (val & MDIO_PMA_EXTABLE_10GBT)
+ __set_bit(ETHTOOL_LINK_MODE_10000baseT_Full_BIT,
+ supported);
+ if (val & MDIO_PMA_EXTABLE_10GBKX4)
+ __set_bit(ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT,
+ supported);
+ if (val & MDIO_PMA_EXTABLE_10GBKR)
+ __set_bit(ETHTOOL_LINK_MODE_10000baseKR_Full_BIT,
+ supported);
+ if (val & MDIO_PMA_EXTABLE_1000BT)
+ __set_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
+ supported);
+ if (val & MDIO_PMA_EXTABLE_1000BKX)
+ __set_bit(ETHTOOL_LINK_MODE_1000baseKX_Full_BIT,
+ supported);
+ if (val & MDIO_PMA_EXTABLE_100BTX)
+ __set_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT,
+ supported);
+ if (val & MDIO_PMA_EXTABLE_10BT)
+ __set_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT,
+ supported);
+ }
+
+ if (!ethtool_convert_link_mode_to_legacy_u32(&mask, supported))
+ dev_warn(&phydev->mdio.dev,
+ "PHY supports (%*pb) more modes than phylib supports, some modes not supported.\n",
+ __ETHTOOL_LINK_MODE_MASK_NBITS, supported);
+
+ phydev->supported &= mask;
+ phydev->advertising &= phydev->supported;
+
+ return 0;
+}
+
+static int mv3310_config_aneg(struct phy_device *phydev)
+{
+ bool changed = false;
+ u32 advertising;
+ int ret;
+
+ if (phydev->autoneg == AUTONEG_DISABLE) {
+ ret = genphy_c45_pma_setup_forced(phydev);
+ if (ret < 0)
+ return ret;
+
+ return genphy_c45_an_disable_aneg(phydev);
+ }
+
+ phydev->advertising &= phydev->supported;
+ advertising = phydev->advertising;
+
+ ret = mv3310_modify(phydev, MDIO_MMD_AN, MDIO_AN_ADVERTISE,
+ ADVERTISE_ALL | ADVERTISE_100BASE4 |
+ ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM,
+ ethtool_adv_to_mii_adv_t(advertising));
+ if (ret < 0)
+ return ret;
+ if (ret > 0)
+ changed = true;
+
+ ret = mv3310_modify(phydev, MDIO_MMD_AN, MV_AN_CTRL1000,
+ ADVERTISE_1000FULL | ADVERTISE_1000HALF,
+ ethtool_adv_to_mii_ctrl1000_t(advertising));
+ if (ret < 0)
+ return ret;
+ if (ret > 0)
+ changed = true;
+
+ /* 10G control register */
+ ret = mv3310_modify(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_CTRL,
+ MDIO_AN_10GBT_CTRL_ADV10G,
+ advertising & ADVERTISED_10000baseT_Full ?
+ MDIO_AN_10GBT_CTRL_ADV10G : 0);
+ if (ret < 0)
+ return ret;
+ if (ret > 0)
+ changed = true;
+
+ if (changed)
+ ret = genphy_c45_restart_aneg(phydev);
+
+ return ret;
+}
+
+static int mv3310_aneg_done(struct phy_device *phydev)
+{
+ int val;
+
+ val = phy_read_mmd(phydev, MDIO_MMD_PCS, MV_PCS_BASE_R + MDIO_STAT1);
+ if (val < 0)
+ return val;
+
+ if (val & MDIO_STAT1_LSTATUS)
+ return 1;
+
+ return genphy_c45_aneg_done(phydev);
+}
+
+/* 10GBASE-ER,LR,LRM,SR do not support autonegotiation. */
+static int mv3310_read_10gbr_status(struct phy_device *phydev)
+{
+ phydev->link = 1;
+ phydev->speed = SPEED_10000;
+ phydev->duplex = DUPLEX_FULL;
+
+ if (phydev->interface == PHY_INTERFACE_MODE_SGMII)
+ phydev->interface = PHY_INTERFACE_MODE_10GKR;
+
+ return 0;
+}
+
+static int mv3310_read_status(struct phy_device *phydev)
+{
+ u32 mmd_mask = phydev->c45_ids.devices_in_package;
+ int val;
+
+ /* The vendor devads do not report link status. Avoid the PHYXS
+ * instance as there are three, and its status depends on the MAC
+ * being appropriately configured for the negotiated speed.
+ */
+ mmd_mask &= ~(BIT(MDIO_MMD_VEND1) | BIT(MDIO_MMD_VEND2) |
+ BIT(MDIO_MMD_PHYXS));
+
+ phydev->speed = SPEED_UNKNOWN;
+ phydev->duplex = DUPLEX_UNKNOWN;
+ phydev->lp_advertising = 0;
+ phydev->link = 0;
+ phydev->pause = 0;
+ phydev->asym_pause = 0;
+
+ val = phy_read_mmd(phydev, MDIO_MMD_PCS, MV_PCS_BASE_R + MDIO_STAT1);
+ if (val < 0)
+ return val;
+
+ if (val & MDIO_STAT1_LSTATUS)
+ return mv3310_read_10gbr_status(phydev);
+
+ val = genphy_c45_read_link(phydev, mmd_mask);
+ if (val < 0)
+ return val;
+
+ phydev->link = val > 0 ? 1 : 0;
+
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1);
+ if (val < 0)
+ return val;
+
+ if (val & MDIO_AN_STAT1_COMPLETE) {
+ val = genphy_c45_read_lpa(phydev);
+ if (val < 0)
+ return val;
+
+ /* Read the link partner's 1G advertisment */
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, MV_AN_STAT1000);
+ if (val < 0)
+ return val;
+
+ phydev->lp_advertising |= mii_stat1000_to_ethtool_lpa_t(val);
+
+ if (phydev->autoneg == AUTONEG_ENABLE) {
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, MV_AN_RESULT);
+ if (val < 0)
+ return val;
+
+ if (val & MV_AN_RESULT_SPD_10000)
+ phydev->speed = SPEED_10000;
+ else if (val & MV_AN_RESULT_SPD_1000)
+ phydev->speed = SPEED_1000;
+ else if (val & MV_AN_RESULT_SPD_100)
+ phydev->speed = SPEED_100;
+ else if (val & MV_AN_RESULT_SPD_10)
+ phydev->speed = SPEED_10;
+
+ phydev->duplex = DUPLEX_FULL;
+ }
+ }
+
+ if (phydev->autoneg != AUTONEG_ENABLE) {
+ val = genphy_c45_read_pma(phydev);
+ if (val < 0)
+ return val;
+ }
+
+ if ((phydev->interface == PHY_INTERFACE_MODE_SGMII ||
+ phydev->interface == PHY_INTERFACE_MODE_10GKR) && phydev->link) {
+ /* The PHY automatically switches its serdes interface (and
+ * active PHYXS instance) between Cisco SGMII and 10GBase-KR
+ * modes according to the speed. Florian suggests setting
+ * phydev->interface to communicate this to the MAC. Only do
+ * this if we are already in either SGMII or 10GBase-KR mode.
+ */
+ if (phydev->speed == SPEED_10000)
+ phydev->interface = PHY_INTERFACE_MODE_10GKR;
+ else if (phydev->speed >= SPEED_10 &&
+ phydev->speed < SPEED_10000)
+ phydev->interface = PHY_INTERFACE_MODE_SGMII;
+ }
+
+ return 0;
+}
+
+static struct phy_driver mv3310_drivers[] = {
+ {
+ .phy_id = 0x002b09aa,
+ .phy_id_mask = 0xffffffff,
+ .name = "mv88x3310",
+ .features = SUPPORTED_10baseT_Full |
+ SUPPORTED_100baseT_Full |
+ SUPPORTED_1000baseT_Full |
+ SUPPORTED_Autoneg |
+ SUPPORTED_TP |
+ SUPPORTED_FIBRE |
+ SUPPORTED_10000baseT_Full |
+ SUPPORTED_Backplane,
+ .probe = mv3310_probe,
+ .soft_reset = mv3310_soft_reset,
+ .config_init = mv3310_config_init,
+ .config_aneg = mv3310_config_aneg,
+ .aneg_done = mv3310_aneg_done,
+ .read_status = mv3310_read_status,
+ },
+};
+
+module_phy_driver(mv3310_drivers);
+
+static struct mdio_device_id __maybe_unused mv3310_tbl[] = {
+ { 0x002b09aa, 0xffffffff },
+ { },
+};
+MODULE_DEVICE_TABLE(mdio, mv3310_tbl);
+MODULE_DESCRIPTION("Marvell Alaska X 10Gigabit Ethernet PHY driver (MV88X3310)");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/phy/phy-c45.c b/drivers/net/phy/phy-c45.c
new file mode 100644
index 000000000000..dada819c6b78
--- /dev/null
+++ b/drivers/net/phy/phy-c45.c
@@ -0,0 +1,298 @@
+/*
+ * Clause 45 PHY support
+ */
+#include <linux/ethtool.h>
+#include <linux/export.h>
+#include <linux/mdio.h>
+#include <linux/mii.h>
+#include <linux/phy.h>
+
+/**
+ * genphy_c45_setup_forced - configures a forced speed
+ * @phydev: target phy_device struct
+ */
+int genphy_c45_pma_setup_forced(struct phy_device *phydev)
+{
+ int ctrl1, ctrl2, ret;
+
+ /* Half duplex is not supported */
+ if (phydev->duplex != DUPLEX_FULL)
+ return -EINVAL;
+
+ ctrl1 = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL1);
+ if (ctrl1 < 0)
+ return ctrl1;
+
+ ctrl2 = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL2);
+ if (ctrl2 < 0)
+ return ctrl2;
+
+ ctrl1 &= ~MDIO_CTRL1_SPEEDSEL;
+ /*
+ * PMA/PMD type selection is 1.7.5:0 not 1.7.3:0. See 45.2.1.6.1
+ * in 802.3-2012 and 802.3-2015.
+ */
+ ctrl2 &= ~(MDIO_PMA_CTRL2_TYPE | 0x30);
+
+ switch (phydev->speed) {
+ case SPEED_10:
+ ctrl2 |= MDIO_PMA_CTRL2_10BT;
+ break;
+ case SPEED_100:
+ ctrl1 |= MDIO_PMA_CTRL1_SPEED100;
+ ctrl2 |= MDIO_PMA_CTRL2_100BTX;
+ break;
+ case SPEED_1000:
+ ctrl1 |= MDIO_PMA_CTRL1_SPEED1000;
+ /* Assume 1000base-T */
+ ctrl2 |= MDIO_PMA_CTRL2_1000BT;
+ break;
+ case SPEED_10000:
+ ctrl1 |= MDIO_CTRL1_SPEED10G;
+ /* Assume 10Gbase-T */
+ ctrl2 |= MDIO_PMA_CTRL2_10GBT;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = phy_write_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL1, ctrl1);
+ if (ret < 0)
+ return ret;
+
+ return phy_write_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL2, ctrl2);
+}
+EXPORT_SYMBOL_GPL(genphy_c45_pma_setup_forced);
+
+/**
+ * genphy_c45_an_disable_aneg - disable auto-negotiation
+ * @phydev: target phy_device struct
+ *
+ * Disable auto-negotiation in the Clause 45 PHY. The link parameters
+ * parameters are controlled through the PMA/PMD MMD registers.
+ *
+ * Returns zero on success, negative errno code on failure.
+ */
+int genphy_c45_an_disable_aneg(struct phy_device *phydev)
+{
+ int val;
+
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1);
+ if (val < 0)
+ return val;
+
+ val &= ~(MDIO_AN_CTRL1_ENABLE | MDIO_AN_CTRL1_RESTART);
+
+ return phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1, val);
+}
+EXPORT_SYMBOL_GPL(genphy_c45_an_disable_aneg);
+
+/**
+ * genphy_c45_restart_aneg - Enable and restart auto-negotiation
+ * @phydev: target phy_device struct
+ *
+ * This assumes that the auto-negotiation MMD is present.
+ *
+ * Enable and restart auto-negotiation.
+ */
+int genphy_c45_restart_aneg(struct phy_device *phydev)
+{
+ int val;
+
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1);
+ if (val < 0)
+ return val;
+
+ val |= MDIO_AN_CTRL1_ENABLE | MDIO_AN_CTRL1_RESTART;
+
+ return phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1, val);
+}
+EXPORT_SYMBOL_GPL(genphy_c45_restart_aneg);
+
+/**
+ * genphy_c45_aneg_done - return auto-negotiation complete status
+ * @phydev: target phy_device struct
+ *
+ * This assumes that the auto-negotiation MMD is present.
+ *
+ * Reads the status register from the auto-negotiation MMD, returning:
+ * - positive if auto-negotiation is complete
+ * - negative errno code on error
+ * - zero otherwise
+ */
+int genphy_c45_aneg_done(struct phy_device *phydev)
+{
+ int val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1);
+
+ return val < 0 ? val : val & MDIO_AN_STAT1_COMPLETE ? 1 : 0;
+}
+EXPORT_SYMBOL_GPL(genphy_c45_aneg_done);
+
+/**
+ * genphy_c45_read_link - read the overall link status from the MMDs
+ * @phydev: target phy_device struct
+ * @mmd_mask: MMDs to read status from
+ *
+ * Read the link status from the specified MMDs, and if they all indicate
+ * that the link is up, return positive. If an error is encountered,
+ * a negative errno will be returned, otherwise zero.
+ */
+int genphy_c45_read_link(struct phy_device *phydev, u32 mmd_mask)
+{
+ int val, devad;
+ bool link = true;
+
+ while (mmd_mask) {
+ devad = __ffs(mmd_mask);
+ mmd_mask &= ~BIT(devad);
+
+ /* The link state is latched low so that momentary link
+ * drops can be detected. Do not double-read the status
+ * register if the link is down.
+ */
+ val = phy_read_mmd(phydev, devad, MDIO_STAT1);
+ if (val < 0)
+ return val;
+
+ if (!(val & MDIO_STAT1_LSTATUS))
+ link = false;
+ }
+
+ return link;
+}
+EXPORT_SYMBOL_GPL(genphy_c45_read_link);
+
+/**
+ * genphy_c45_read_lpa - read the link partner advertisment and pause
+ * @phydev: target phy_device struct
+ *
+ * Read the Clause 45 defined base (7.19) and 10G (7.33) status registers,
+ * filling in the link partner advertisment, pause and asym_pause members
+ * in @phydev. This assumes that the auto-negotiation MMD is present, and
+ * the backplane bit (7.48.0) is clear. Clause 45 PHY drivers are expected
+ * to fill in the remainder of the link partner advert from vendor registers.
+ */
+int genphy_c45_read_lpa(struct phy_device *phydev)
+{
+ int val;
+
+ /* Read the link partner's base page advertisment */
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_LPA);
+ if (val < 0)
+ return val;
+
+ phydev->lp_advertising = mii_lpa_to_ethtool_lpa_t(val);
+ phydev->pause = val & LPA_PAUSE_CAP ? 1 : 0;
+ phydev->asym_pause = val & LPA_PAUSE_ASYM ? 1 : 0;
+
+ /* Read the link partner's 10G advertisment */
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_STAT);
+ if (val < 0)
+ return val;
+
+ if (val & MDIO_AN_10GBT_STAT_LP10G)
+ phydev->lp_advertising |= ADVERTISED_10000baseT_Full;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(genphy_c45_read_lpa);
+
+/**
+ * genphy_c45_read_pma - read link speed etc from PMA
+ * @phydev: target phy_device struct
+ */
+int genphy_c45_read_pma(struct phy_device *phydev)
+{
+ int val;
+
+ val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL1);
+ if (val < 0)
+ return val;
+
+ switch (val & MDIO_CTRL1_SPEEDSEL) {
+ case 0:
+ phydev->speed = SPEED_10;
+ break;
+ case MDIO_PMA_CTRL1_SPEED100:
+ phydev->speed = SPEED_100;
+ break;
+ case MDIO_PMA_CTRL1_SPEED1000:
+ phydev->speed = SPEED_1000;
+ break;
+ case MDIO_CTRL1_SPEED10G:
+ phydev->speed = SPEED_10000;
+ break;
+ default:
+ phydev->speed = SPEED_UNKNOWN;
+ break;
+ }
+
+ phydev->duplex = DUPLEX_FULL;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(genphy_c45_read_pma);
+
+/* The gen10g_* functions are the old Clause 45 stub */
+
+static int gen10g_config_aneg(struct phy_device *phydev)
+{
+ return 0;
+}
+
+static int gen10g_read_status(struct phy_device *phydev)
+{
+ u32 mmd_mask = phydev->c45_ids.devices_in_package;
+ int ret;
+
+ /* For now just lie and say it's 10G all the time */
+ phydev->speed = SPEED_10000;
+ phydev->duplex = DUPLEX_FULL;
+
+ /* Avoid reading the vendor MMDs */
+ mmd_mask &= ~(BIT(MDIO_MMD_VEND1) | BIT(MDIO_MMD_VEND2));
+
+ ret = genphy_c45_read_link(phydev, mmd_mask);
+
+ phydev->link = ret > 0 ? 1 : 0;
+
+ return 0;
+}
+
+static int gen10g_soft_reset(struct phy_device *phydev)
+{
+ /* Do nothing for now */
+ return 0;
+}
+
+static int gen10g_config_init(struct phy_device *phydev)
+{
+ /* Temporarily just say we support everything */
+ phydev->supported = SUPPORTED_10000baseT_Full;
+ phydev->advertising = SUPPORTED_10000baseT_Full;
+
+ return 0;
+}
+
+static int gen10g_suspend(struct phy_device *phydev)
+{
+ return 0;
+}
+
+static int gen10g_resume(struct phy_device *phydev)
+{
+ return 0;
+}
+
+struct phy_driver genphy_10g_driver = {
+ .phy_id = 0xffffffff,
+ .phy_id_mask = 0xffffffff,
+ .name = "Generic 10G PHY",
+ .soft_reset = gen10g_soft_reset,
+ .config_init = gen10g_config_init,
+ .features = 0,
+ .config_aneg = gen10g_config_aneg,
+ .read_status = gen10g_read_status,
+ .suspend = gen10g_suspend,
+ .resume = gen10g_resume,
+};
diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c
index 40f4c6a2ef6c..12548e5b6037 100644
--- a/drivers/net/phy/phy.c
+++ b/drivers/net/phy/phy.c
@@ -149,6 +149,25 @@ static int phy_config_interrupt(struct phy_device *phydev, u32 interrupts)
return 0;
}
+/**
+ * phy_restart_aneg - restart auto-negotiation
+ * @phydev: target phy_device struct
+ *
+ * Restart the autonegotiation on @phydev. Returns >= 0 on success or
+ * negative errno on error.
+ */
+int phy_restart_aneg(struct phy_device *phydev)
+{
+ int ret;
+
+ if (phydev->is_c45 && !(phydev->c45_ids.devices_in_package & BIT(0)))
+ ret = genphy_c45_restart_aneg(phydev);
+ else
+ ret = genphy_restart_aneg(phydev);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(phy_restart_aneg);
/**
* phy_aneg_done - return auto-negotiation status
@@ -163,6 +182,12 @@ int phy_aneg_done(struct phy_device *phydev)
if (phydev->drv && phydev->drv->aneg_done)
return phydev->drv->aneg_done(phydev);
+ /* Avoid genphy_aneg_done() if the Clause 45 PHY does not
+ * implement Clause 22 registers
+ */
+ if (phydev->is_c45 && !(phydev->c45_ids.devices_in_package & BIT(0)))
+ return -EINVAL;
+
return genphy_aneg_done(phydev);
}
EXPORT_SYMBOL(phy_aneg_done);
@@ -1391,7 +1416,7 @@ int phy_ethtool_set_eee(struct phy_device *phydev, struct ethtool_eee *data)
/* Restart autonegotiation so the new modes get sent to the
* link partner.
*/
- ret = genphy_restart_aneg(phydev);
+ ret = phy_restart_aneg(phydev);
if (ret < 0)
return ret;
}
@@ -1450,6 +1475,6 @@ int phy_ethtool_nway_reset(struct net_device *ndev)
if (!phydev->drv)
return -EIO;
- return genphy_restart_aneg(phydev);
+ return phy_restart_aneg(phydev);
}
EXPORT_SYMBOL(phy_ethtool_nway_reset);
diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
index 37a1e98908e3..acf00f071c9a 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -69,13 +69,8 @@ static void phy_mdio_device_remove(struct mdio_device *mdiodev)
phy_device_remove(phydev);
}
-enum genphy_driver {
- GENPHY_DRV_1G,
- GENPHY_DRV_10G,
- GENPHY_DRV_MAX
-};
-
-static struct phy_driver genphy_driver[GENPHY_DRV_MAX];
+static struct phy_driver genphy_driver;
+extern struct phy_driver genphy_10g_driver;
static LIST_HEAD(phy_fixup_list);
static DEFINE_MUTEX(phy_fixup_lock);
@@ -928,11 +923,9 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev,
*/
if (!d->driver) {
if (phydev->is_c45)
- d->driver =
- &genphy_driver[GENPHY_DRV_10G].mdiodrv.driver;
+ d->driver = &genphy_10g_driver.mdiodrv.driver;
else
- d->driver =
- &genphy_driver[GENPHY_DRV_1G].mdiodrv.driver;
+ d->driver = &genphy_driver.mdiodrv.driver;
using_genphy = true;
}
@@ -1069,7 +1062,6 @@ void phy_detach(struct phy_device *phydev)
struct net_device *dev = phydev->attached_dev;
struct module *ndev_owner = dev->dev.parent->driver->owner;
struct mii_bus *bus;
- int i;
if (phydev->sysfs_links) {
sysfs_remove_link(&dev->dev.kobj, "phydev");
@@ -1088,13 +1080,9 @@ void phy_detach(struct phy_device *phydev)
* from the generic driver so that there's a chance a
* real driver could be loaded
*/
- for (i = 0; i < ARRAY_SIZE(genphy_driver); i++) {
- if (phydev->mdio.dev.driver ==
- &genphy_driver[i].mdiodrv.driver) {
- device_release_driver(&phydev->mdio.dev);
- break;
- }
- }
+ if (phydev->mdio.dev.driver == &genphy_10g_driver.mdiodrv.driver ||
+ phydev->mdio.dev.driver == &genphy_driver.mdiodrv.driver)
+ device_release_driver(&phydev->mdio.dev);
/*
* The phydev might go away on the put_device() below, so avoid
@@ -1368,11 +1356,6 @@ int genphy_aneg_done(struct phy_device *phydev)
}
EXPORT_SYMBOL(genphy_aneg_done);
-static int gen10g_config_aneg(struct phy_device *phydev)
-{
- return 0;
-}
-
/**
* genphy_update_link - update link status in @phydev
* @phydev: target phy_device struct
@@ -1506,33 +1489,6 @@ int genphy_read_status(struct phy_device *phydev)
}
EXPORT_SYMBOL(genphy_read_status);
-static int gen10g_read_status(struct phy_device *phydev)
-{
- int devad, reg;
- u32 mmd_mask = phydev->c45_ids.devices_in_package;
-
- phydev->link = 1;
-
- /* For now just lie and say it's 10G all the time */
- phydev->speed = SPEED_10000;
- phydev->duplex = DUPLEX_FULL;
-
- for (devad = 0; mmd_mask; devad++, mmd_mask = mmd_mask >> 1) {
- if (!(mmd_mask & 1))
- continue;
-
- /* Read twice because link state is latched and a
- * read moves the current state into the register
- */
- phy_read_mmd(phydev, devad, MDIO_STAT1);
- reg = phy_read_mmd(phydev, devad, MDIO_STAT1);
- if (reg < 0 || !(reg & MDIO_STAT1_LSTATUS))
- phydev->link = 0;
- }
-
- return 0;
-}
-
/**
* genphy_soft_reset - software reset the PHY via BMCR_RESET bit
* @phydev: target phy_device struct
@@ -1598,21 +1554,6 @@ int genphy_config_init(struct phy_device *phydev)
}
EXPORT_SYMBOL(genphy_config_init);
-static int gen10g_soft_reset(struct phy_device *phydev)
-{
- /* Do nothing for now */
- return 0;
-}
-
-static int gen10g_config_init(struct phy_device *phydev)
-{
- /* Temporarily just say we support everything */
- phydev->supported = SUPPORTED_10000baseT_Full;
- phydev->advertising = SUPPORTED_10000baseT_Full;
-
- return 0;
-}
-
int genphy_suspend(struct phy_device *phydev)
{
int value;
@@ -1628,11 +1569,6 @@ int genphy_suspend(struct phy_device *phydev)
}
EXPORT_SYMBOL(genphy_suspend);
-static int gen10g_suspend(struct phy_device *phydev)
-{
- return 0;
-}
-
int genphy_resume(struct phy_device *phydev)
{
int value;
@@ -1648,11 +1584,6 @@ int genphy_resume(struct phy_device *phydev)
}
EXPORT_SYMBOL(genphy_resume);
-static int gen10g_resume(struct phy_device *phydev)
-{
- return 0;
-}
-
static int __set_phy_supported(struct phy_device *phydev, u32 max_speed)
{
/* The default values for phydev->supported are provided by the PHY
@@ -1884,8 +1815,7 @@ void phy_drivers_unregister(struct phy_driver *drv, int n)
}
EXPORT_SYMBOL(phy_drivers_unregister);
-static struct phy_driver genphy_driver[] = {
-{
+static struct phy_driver genphy_driver = {
.phy_id = 0xffffffff,
.phy_id_mask = 0xffffffff,
.name = "Generic PHY",
@@ -1899,18 +1829,7 @@ static struct phy_driver genphy_driver[] = {
.read_status = genphy_read_status,
.suspend = genphy_suspend,
.resume = genphy_resume,
-}, {
- .phy_id = 0xffffffff,
- .phy_id_mask = 0xffffffff,
- .name = "Generic 10G PHY",
- .soft_reset = gen10g_soft_reset,
- .config_init = gen10g_config_init,
- .features = 0,
- .config_aneg = gen10g_config_aneg,
- .read_status = gen10g_read_status,
- .suspend = gen10g_suspend,
- .resume = gen10g_resume,
-} };
+};
static int __init phy_init(void)
{
@@ -1920,18 +1839,24 @@ static int __init phy_init(void)
if (rc)
return rc;
- rc = phy_drivers_register(genphy_driver,
- ARRAY_SIZE(genphy_driver), THIS_MODULE);
+ rc = phy_driver_register(&genphy_10g_driver, THIS_MODULE);
if (rc)
+ goto err_10g;
+
+ rc = phy_driver_register(&genphy_driver, THIS_MODULE);
+ if (rc) {
+ phy_driver_unregister(&genphy_10g_driver);
+err_10g:
mdio_bus_exit();
+ }
return rc;
}
static void __exit phy_exit(void)
{
- phy_drivers_unregister(genphy_driver,
- ARRAY_SIZE(genphy_driver));
+ phy_driver_unregister(&genphy_10g_driver);
+ phy_driver_unregister(&genphy_driver);
mdio_bus_exit();
}
diff --git a/include/linux/phy.h b/include/linux/phy.h
index 748e526c0698..414242200a90 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -83,6 +83,9 @@ typedef enum {
PHY_INTERFACE_MODE_1000BASEX,
PHY_INTERFACE_MODE_2500BASEX,
PHY_INTERFACE_MODE_RXAUI,
+ PHY_INTERFACE_MODE_XAUI,
+ /* 10GBASE-KR, XFI, SFI - single lane 10G Serdes */
+ PHY_INTERFACE_MODE_10GKR,
PHY_INTERFACE_MODE_MAX,
} phy_interface_t;
@@ -149,6 +152,10 @@ static inline const char *phy_modes(phy_interface_t interface)
return "2500base-x";
case PHY_INTERFACE_MODE_RXAUI:
return "rxaui";
+ case PHY_INTERFACE_MODE_XAUI:
+ return "xaui";
+ case PHY_INTERFACE_MODE_10GKR:
+ return "10gbase-kr";
default:
return "unknown";
}
@@ -804,6 +811,7 @@ int phy_start_aneg(struct phy_device *phydev);
int phy_aneg_done(struct phy_device *phydev);
int phy_stop_interrupts(struct phy_device *phydev);
+int phy_restart_aneg(struct phy_device *phydev);
static inline int phy_read_status(struct phy_device *phydev)
{
@@ -827,6 +835,8 @@ static inline const char *phydev_name(const struct phy_device *phydev)
void phy_attached_print(struct phy_device *phydev, const char *fmt, ...)
__printf(2, 3);
void phy_attached_info(struct phy_device *phydev);
+
+/* Clause 22 PHY */
int genphy_config_init(struct phy_device *phydev);
int genphy_setup_forced(struct phy_device *phydev);
int genphy_restart_aneg(struct phy_device *phydev);
@@ -841,6 +851,16 @@ static inline int genphy_no_soft_reset(struct phy_device *phydev)
{
return 0;
}
+
+/* Clause 45 PHY */
+int genphy_c45_restart_aneg(struct phy_device *phydev);
+int genphy_c45_aneg_done(struct phy_device *phydev);
+int genphy_c45_read_link(struct phy_device *phydev, u32 mmd_mask);
+int genphy_c45_read_lpa(struct phy_device *phydev);
+int genphy_c45_read_pma(struct phy_device *phydev);
+int genphy_c45_pma_setup_forced(struct phy_device *phydev);
+int genphy_c45_an_disable_aneg(struct phy_device *phydev);
+
void phy_driver_unregister(struct phy_driver *drv);
void phy_drivers_unregister(struct phy_driver *drv, int n);
int phy_driver_register(struct phy_driver *new_driver, struct module *owner);