summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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);