aboutsummaryrefslogtreecommitdiff
path: root/drivers/net/phy
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/phy')
-rw-r--r--drivers/net/phy/Kconfig7
-rw-r--r--drivers/net/phy/Makefile1
-rw-r--r--drivers/net/phy/at803x.c135
-rw-r--r--drivers/net/phy/bcm7xxx.c1
-rw-r--r--drivers/net/phy/marvell-88q2xxx.c263
-rw-r--r--drivers/net/phy/marvell-88x2222.c1
-rw-r--r--drivers/net/phy/mdio_bus.c37
-rw-r--r--drivers/net/phy/mediatek-ge-soc.c2
-rw-r--r--drivers/net/phy/motorcomm.c118
-rw-r--r--drivers/net/phy/phy-c45.c63
-rw-r--r--drivers/net/phy/phylink.c178
-rw-r--r--drivers/net/phy/smsc.c252
12 files changed, 939 insertions, 119 deletions
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index 78e6981650d9..67aaeb75301f 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -217,6 +217,12 @@ config MARVELL_10G_PHY
help
Support for the Marvell Alaska MV88X3310 and compatible PHYs.
+config MARVELL_88Q2XXX_PHY
+ tristate "Marvell 88Q2XXX PHY"
+ help
+ Support for the Marvell 88Q2XXX 100/1000BASE-T1 Automotive Ethernet
+ PHYs.
+
config MARVELL_88X2222_PHY
tristate "Marvell 88X2222 PHY"
help
@@ -344,6 +350,7 @@ config ROCKCHIP_PHY
config SMSC_PHY
tristate "SMSC PHYs"
+ select CRC16
help
Currently supports the LAN83C185, LAN8187 and LAN8700 PHYs
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index 2fe51ea83bab..35142780fc9d 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -66,6 +66,7 @@ obj-$(CONFIG_LSI_ET1011C_PHY) += et1011c.o
obj-$(CONFIG_LXT_PHY) += lxt.o
obj-$(CONFIG_MARVELL_10G_PHY) += marvell10g.o
obj-$(CONFIG_MARVELL_PHY) += marvell.o
+obj-$(CONFIG_MARVELL_88Q2XXX_PHY) += marvell-88q2xxx.o
obj-$(CONFIG_MARVELL_88X2222_PHY) += marvell-88x2222.o
obj-$(CONFIG_MAXLINEAR_GPHY) += mxl-gpy.o
obj-$(CONFIG_MEDIATEK_GE_PHY) += mediatek-ge.o
diff --git a/drivers/net/phy/at803x.c b/drivers/net/phy/at803x.c
index c1f307d90518..13c4121fa309 100644
--- a/drivers/net/phy/at803x.c
+++ b/drivers/net/phy/at803x.c
@@ -272,6 +272,13 @@
#define QCA808X_CDT_STATUS_STAT_OPEN 2
#define QCA808X_CDT_STATUS_STAT_SHORT 3
+/* QCA808X 1G chip type */
+#define QCA808X_PHY_MMD7_CHIP_TYPE 0x901d
+#define QCA808X_PHY_CHIP_TYPE_1G BIT(0)
+
+#define QCA8081_PHY_SERDES_MMD1_FIFO_CTRL 0x9072
+#define QCA8081_PHY_FIFO_RSTN BIT(11)
+
MODULE_DESCRIPTION("Qualcomm Atheros AR803x and QCA808X PHY driver");
MODULE_AUTHOR("Matus Ujhelyi");
MODULE_LICENSE("GPL");
@@ -897,15 +904,6 @@ static int at803x_get_features(struct phy_device *phydev)
if (err)
return err;
- if (phydev->drv->phy_id == QCA8081_PHY_ID) {
- err = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PMA_NG_EXTABLE);
- if (err < 0)
- return err;
-
- linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->supported,
- err & MDIO_PMA_NG_EXTABLE_2_5GBT);
- }
-
if (phydev->drv->phy_id != ATH8031_PHY_ID)
return 0;
@@ -1734,24 +1732,30 @@ static int qca808x_phy_fast_retrain_config(struct phy_device *phydev)
return 0;
}
-static int qca808x_phy_ms_random_seed_set(struct phy_device *phydev)
+static int qca808x_phy_ms_seed_enable(struct phy_device *phydev, bool enable)
{
- u16 seed_value = get_random_u32_below(QCA808X_MASTER_SLAVE_SEED_RANGE);
+ u16 seed_value;
+
+ if (!enable)
+ return at803x_debug_reg_mask(phydev, QCA808X_PHY_DEBUG_LOCAL_SEED,
+ QCA808X_MASTER_SLAVE_SEED_ENABLE, 0);
+ seed_value = get_random_u32_below(QCA808X_MASTER_SLAVE_SEED_RANGE);
return at803x_debug_reg_mask(phydev, QCA808X_PHY_DEBUG_LOCAL_SEED,
- QCA808X_MASTER_SLAVE_SEED_CFG,
- FIELD_PREP(QCA808X_MASTER_SLAVE_SEED_CFG, seed_value));
+ QCA808X_MASTER_SLAVE_SEED_CFG | QCA808X_MASTER_SLAVE_SEED_ENABLE,
+ FIELD_PREP(QCA808X_MASTER_SLAVE_SEED_CFG, seed_value) |
+ QCA808X_MASTER_SLAVE_SEED_ENABLE);
}
-static int qca808x_phy_ms_seed_enable(struct phy_device *phydev, bool enable)
+static bool qca808x_is_prefer_master(struct phy_device *phydev)
{
- u16 seed_enable = 0;
-
- if (enable)
- seed_enable = QCA808X_MASTER_SLAVE_SEED_ENABLE;
+ return (phydev->master_slave_get == MASTER_SLAVE_CFG_MASTER_FORCE) ||
+ (phydev->master_slave_get == MASTER_SLAVE_CFG_MASTER_PREFERRED);
+}
- return at803x_debug_reg_mask(phydev, QCA808X_PHY_DEBUG_LOCAL_SEED,
- QCA808X_MASTER_SLAVE_SEED_ENABLE, seed_enable);
+static bool qca808x_has_fast_retrain_or_slave_seed(struct phy_device *phydev)
+{
+ return linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->supported);
}
static int qca808x_config_init(struct phy_device *phydev)
@@ -1770,20 +1774,25 @@ static int qca808x_config_init(struct phy_device *phydev)
if (ret)
return ret;
- /* Config the fast retrain for the link 2500M */
- ret = qca808x_phy_fast_retrain_config(phydev);
- if (ret)
- return ret;
+ if (qca808x_has_fast_retrain_or_slave_seed(phydev)) {
+ /* Config the fast retrain for the link 2500M */
+ ret = qca808x_phy_fast_retrain_config(phydev);
+ if (ret)
+ return ret;
- /* Configure lower ramdom seed to make phy linked as slave mode */
- ret = qca808x_phy_ms_random_seed_set(phydev);
- if (ret)
- return ret;
+ ret = genphy_read_master_slave(phydev);
+ if (ret < 0)
+ return ret;
- /* Enable seed */
- ret = qca808x_phy_ms_seed_enable(phydev, true);
- if (ret)
- return ret;
+ if (!qca808x_is_prefer_master(phydev)) {
+ /* Enable seed and configure lower ramdom seed to make phy
+ * linked as slave mode.
+ */
+ ret = qca808x_phy_ms_seed_enable(phydev, true);
+ if (ret)
+ return ret;
+ }
+ }
/* Configure adc threshold as 100mv for the link 10M */
return at803x_debug_reg_mask(phydev, QCA808X_PHY_DEBUG_ADC_THRESHOLD,
@@ -1816,17 +1825,21 @@ static int qca808x_read_status(struct phy_device *phydev)
phydev->interface = PHY_INTERFACE_MODE_SGMII;
} else {
/* generate seed as a lower random value to make PHY linked as SLAVE easily,
- * except for master/slave configuration fault detected.
+ * except for master/slave configuration fault detected or the master mode
+ * preferred.
+ *
* the reason for not putting this code into the function link_change_notify is
* the corner case where the link partner is also the qca8081 PHY and the seed
* value is configured as the same value, the link can't be up and no link change
* occurs.
*/
- if (phydev->master_slave_state == MASTER_SLAVE_STATE_ERR) {
- qca808x_phy_ms_seed_enable(phydev, false);
- } else {
- qca808x_phy_ms_random_seed_set(phydev);
- qca808x_phy_ms_seed_enable(phydev, true);
+ if (qca808x_has_fast_retrain_or_slave_seed(phydev)) {
+ if (phydev->master_slave_state == MASTER_SLAVE_STATE_ERR ||
+ qca808x_is_prefer_master(phydev)) {
+ qca808x_phy_ms_seed_enable(phydev, false);
+ } else {
+ qca808x_phy_ms_seed_enable(phydev, true);
+ }
}
}
@@ -1841,7 +1854,10 @@ static int qca808x_soft_reset(struct phy_device *phydev)
if (ret < 0)
return ret;
- return qca808x_phy_ms_seed_enable(phydev, true);
+ if (qca808x_has_fast_retrain_or_slave_seed(phydev))
+ ret = qca808x_phy_ms_seed_enable(phydev, true);
+
+ return ret;
}
static bool qca808x_cdt_fault_length_valid(int cdt_code)
@@ -1991,6 +2007,44 @@ static int qca808x_cable_test_get_status(struct phy_device *phydev, bool *finish
return 0;
}
+static int qca808x_get_features(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = genphy_c45_pma_read_abilities(phydev);
+ if (ret)
+ return ret;
+
+ /* The autoneg ability is not existed in bit3 of MMD7.1,
+ * but it is supported by qca808x PHY, so we add it here
+ * manually.
+ */
+ linkmode_set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->supported);
+
+ /* As for the qca8081 1G version chip, the 2500baseT ability is also
+ * existed in the bit0 of MMD1.21, we need to remove it manually if
+ * it is the qca8081 1G chip according to the bit0 of MMD7.0x901d.
+ */
+ ret = phy_read_mmd(phydev, MDIO_MMD_AN, QCA808X_PHY_MMD7_CHIP_TYPE);
+ if (ret < 0)
+ return ret;
+
+ if (QCA808X_PHY_CHIP_TYPE_1G & ret)
+ linkmode_clear_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->supported);
+
+ return 0;
+}
+
+static void qca808x_link_change_notify(struct phy_device *phydev)
+{
+ /* Assert interface sgmii fifo on link down, deassert it on link up,
+ * the interface device address is always phy address added by 1.
+ */
+ mdiobus_c45_modify_changed(phydev->mdio.bus, phydev->mdio.addr + 1,
+ MDIO_MMD_PMAPMD, QCA8081_PHY_SERDES_MMD1_FIFO_CTRL,
+ QCA8081_PHY_FIFO_RSTN, phydev->link ? QCA8081_PHY_FIFO_RSTN : 0);
+}
+
static struct phy_driver at803x_driver[] = {
{
/* Qualcomm Atheros AR8035 */
@@ -2160,7 +2214,7 @@ static struct phy_driver at803x_driver[] = {
.set_tunable = at803x_set_tunable,
.set_wol = at803x_set_wol,
.get_wol = at803x_get_wol,
- .get_features = at803x_get_features,
+ .get_features = qca808x_get_features,
.config_aneg = at803x_config_aneg,
.suspend = genphy_suspend,
.resume = genphy_resume,
@@ -2169,6 +2223,7 @@ static struct phy_driver at803x_driver[] = {
.soft_reset = qca808x_soft_reset,
.cable_test_start = qca808x_cable_test_start,
.cable_test_get_status = qca808x_cable_test_get_status,
+ .link_change_notify = qca808x_link_change_notify,
}, };
module_phy_driver(at803x_driver);
diff --git a/drivers/net/phy/bcm7xxx.c b/drivers/net/phy/bcm7xxx.c
index f8c17a253f8b..8478b081c058 100644
--- a/drivers/net/phy/bcm7xxx.c
+++ b/drivers/net/phy/bcm7xxx.c
@@ -913,6 +913,7 @@ static struct phy_driver bcm7xxx_driver[] = {
BCM7XXX_28NM_GPHY(PHY_ID_BCM7278, "Broadcom BCM7278"),
BCM7XXX_28NM_GPHY(PHY_ID_BCM7364, "Broadcom BCM7364"),
BCM7XXX_28NM_GPHY(PHY_ID_BCM7366, "Broadcom BCM7366"),
+ BCM7XXX_16NM_EPHY(PHY_ID_BCM74165, "Broadcom BCM74165"),
BCM7XXX_28NM_GPHY(PHY_ID_BCM74371, "Broadcom BCM74371"),
BCM7XXX_28NM_GPHY(PHY_ID_BCM7439, "Broadcom BCM7439"),
BCM7XXX_28NM_GPHY(PHY_ID_BCM7439_2, "Broadcom BCM7439 (2)"),
diff --git a/drivers/net/phy/marvell-88q2xxx.c b/drivers/net/phy/marvell-88q2xxx.c
new file mode 100644
index 000000000000..1c3ff77de56b
--- /dev/null
+++ b/drivers/net/phy/marvell-88q2xxx.c
@@ -0,0 +1,263 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Marvell 88Q2XXX automotive 100BASE-T1/1000BASE-T1 PHY driver
+ */
+#include <linux/ethtool_netlink.h>
+#include <linux/marvell_phy.h>
+#include <linux/phy.h>
+
+#define MDIO_MMD_AN_MV_STAT 32769
+#define MDIO_MMD_AN_MV_STAT_ANEG 0x0100
+#define MDIO_MMD_AN_MV_STAT_LOCAL_RX 0x1000
+#define MDIO_MMD_AN_MV_STAT_REMOTE_RX 0x2000
+#define MDIO_MMD_AN_MV_STAT_LOCAL_MASTER 0x4000
+#define MDIO_MMD_AN_MV_STAT_MS_CONF_FAULT 0x8000
+
+#define MDIO_MMD_PCS_MV_100BT1_STAT1 33032
+#define MDIO_MMD_PCS_MV_100BT1_STAT1_IDLE_ERROR 0x00FF
+#define MDIO_MMD_PCS_MV_100BT1_STAT1_JABBER 0x0100
+#define MDIO_MMD_PCS_MV_100BT1_STAT1_LINK 0x0200
+#define MDIO_MMD_PCS_MV_100BT1_STAT1_LOCAL_RX 0x1000
+#define MDIO_MMD_PCS_MV_100BT1_STAT1_REMOTE_RX 0x2000
+#define MDIO_MMD_PCS_MV_100BT1_STAT1_LOCAL_MASTER 0x4000
+
+#define MDIO_MMD_PCS_MV_100BT1_STAT2 33033
+#define MDIO_MMD_PCS_MV_100BT1_STAT2_JABBER 0x0001
+#define MDIO_MMD_PCS_MV_100BT1_STAT2_POL 0x0002
+#define MDIO_MMD_PCS_MV_100BT1_STAT2_LINK 0x0004
+#define MDIO_MMD_PCS_MV_100BT1_STAT2_ANGE 0x0008
+
+static int mv88q2xxx_soft_reset(struct phy_device *phydev)
+{
+ int ret;
+ int val;
+
+ ret = phy_write_mmd(phydev, MDIO_MMD_PCS,
+ MDIO_PCS_1000BT1_CTRL, MDIO_PCS_1000BT1_CTRL_RESET);
+ if (ret < 0)
+ return ret;
+
+ return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_PCS,
+ MDIO_PCS_1000BT1_CTRL, val,
+ !(val & MDIO_PCS_1000BT1_CTRL_RESET),
+ 50000, 600000, true);
+}
+
+static int mv88q2xxx_read_link_gbit(struct phy_device *phydev)
+{
+ int ret;
+ bool link = false;
+
+ /* Read vendor specific Auto-Negotiation status register to get local
+ * and remote receiver status according to software initialization
+ * guide.
+ */
+ ret = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_MMD_AN_MV_STAT);
+ if (ret < 0) {
+ return ret;
+ } else if ((ret & MDIO_MMD_AN_MV_STAT_LOCAL_RX) &&
+ (ret & MDIO_MMD_AN_MV_STAT_REMOTE_RX)) {
+ /* The link state is latched low so that momentary link
+ * drops can be detected. Do not double-read the status
+ * in polling mode to detect such short link drops except
+ * the link was already down.
+ */
+ if (!phy_polling_mode(phydev) || !phydev->link) {
+ ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_PCS_1000BT1_STAT);
+ if (ret < 0)
+ return ret;
+ else if (ret & MDIO_PCS_1000BT1_STAT_LINK)
+ link = true;
+ }
+
+ if (!link) {
+ ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_PCS_1000BT1_STAT);
+ if (ret < 0)
+ return ret;
+ else if (ret & MDIO_PCS_1000BT1_STAT_LINK)
+ link = true;
+ }
+ }
+
+ phydev->link = link;
+
+ return 0;
+}
+
+static int mv88q2xxx_read_link_100m(struct phy_device *phydev)
+{
+ int ret;
+
+ /* The link state is latched low so that momentary link
+ * drops can be detected. Do not double-read the status
+ * in polling mode to detect such short link drops except
+ * the link was already down. In case we are not polling,
+ * we always read the realtime status.
+ */
+ if (!phy_polling_mode(phydev) || !phydev->link) {
+ ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_MMD_PCS_MV_100BT1_STAT1);
+ if (ret < 0)
+ return ret;
+ else if (ret & MDIO_MMD_PCS_MV_100BT1_STAT1_LINK)
+ goto out;
+ }
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_MMD_PCS_MV_100BT1_STAT1);
+ if (ret < 0)
+ return ret;
+
+out:
+ /* Check if we have link and if the remote and local receiver are ok */
+ if ((ret & MDIO_MMD_PCS_MV_100BT1_STAT1_LINK) &&
+ (ret & MDIO_MMD_PCS_MV_100BT1_STAT1_LOCAL_RX) &&
+ (ret & MDIO_MMD_PCS_MV_100BT1_STAT1_REMOTE_RX))
+ phydev->link = true;
+ else
+ phydev->link = false;
+
+ return 0;
+}
+
+static int mv88q2xxx_read_link(struct phy_device *phydev)
+{
+ int ret;
+
+ /* The 88Q2XXX PHYs do not have the PMA/PMD status register available,
+ * therefore we need to read the link status from the vendor specific
+ * registers depending on the speed.
+ */
+ if (phydev->speed == SPEED_1000)
+ ret = mv88q2xxx_read_link_gbit(phydev);
+ else
+ ret = mv88q2xxx_read_link_100m(phydev);
+
+ return ret;
+}
+
+static int mv88q2xxx_read_status(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = mv88q2xxx_read_link(phydev);
+ if (ret < 0)
+ return ret;
+
+ return genphy_c45_read_pma(phydev);
+}
+
+static int mv88q2xxx_get_features(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = genphy_c45_pma_read_abilities(phydev);
+ if (ret)
+ return ret;
+
+ /* We need to read the baset1 extended abilities manually because the
+ * PHY does not signalize it has the extended abilities register
+ * available.
+ */
+ ret = genphy_c45_pma_baset1_read_abilities(phydev);
+ if (ret)
+ return ret;
+
+ /* The PHY signalizes it supports autonegotiation. Unfortunately, so
+ * far it was not possible to get a link even when following the init
+ * sequence provided by Marvell. Disable it for now until a proper
+ * workaround is found or a new PHY revision is released.
+ */
+ linkmode_clear_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->supported);
+
+ return 0;
+}
+
+static int mv88q2xxx_config_aneg(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = genphy_c45_config_aneg(phydev);
+ if (ret)
+ return ret;
+
+ return mv88q2xxx_soft_reset(phydev);
+}
+
+static int mv88q2xxx_config_init(struct phy_device *phydev)
+{
+ int ret;
+
+ /* The 88Q2XXX PHYs do have the extended ability register available, but
+ * register MDIO_PMA_EXTABLE where they should signalize it does not
+ * work according to specification. Therefore, we force it here.
+ */
+ phydev->pma_extable = MDIO_PMA_EXTABLE_BT1;
+
+ /* Read the current PHY configuration */
+ ret = genphy_c45_read_pma(phydev);
+ if (ret)
+ return ret;
+
+ return mv88q2xxx_config_aneg(phydev);
+}
+
+static int mv88q2xxxx_get_sqi(struct phy_device *phydev)
+{
+ int ret;
+
+ if (phydev->speed == SPEED_100) {
+ /* Read the SQI from the vendor specific receiver status
+ * register
+ */
+ ret = phy_read_mmd(phydev, MDIO_MMD_PCS, 0x8230);
+ if (ret < 0)
+ return ret;
+
+ ret = ret >> 12;
+ } else {
+ /* Read from vendor specific registers, they are not documented
+ * but can be found in the Software Initialization Guide. Only
+ * revisions >= A0 are supported.
+ */
+ ret = phy_modify_mmd(phydev, MDIO_MMD_PCS, 0xFC5D, 0x00FF, 0x00AC);
+ if (ret < 0)
+ return ret;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_PCS, 0xfc88);
+ if (ret < 0)
+ return ret;
+ }
+
+ return ret & 0x0F;
+}
+
+static int mv88q2xxxx_get_sqi_max(struct phy_device *phydev)
+{
+ return 15;
+}
+
+static struct phy_driver mv88q2xxx_driver[] = {
+ {
+ .phy_id = MARVELL_PHY_ID_88Q2110,
+ .phy_id_mask = MARVELL_PHY_ID_MASK,
+ .name = "mv88q2110",
+ .get_features = mv88q2xxx_get_features,
+ .config_aneg = mv88q2xxx_config_aneg,
+ .config_init = mv88q2xxx_config_init,
+ .read_status = mv88q2xxx_read_status,
+ .soft_reset = mv88q2xxx_soft_reset,
+ .set_loopback = genphy_c45_loopback,
+ .get_sqi = mv88q2xxxx_get_sqi,
+ .get_sqi_max = mv88q2xxxx_get_sqi_max,
+ },
+};
+
+module_phy_driver(mv88q2xxx_driver);
+
+static struct mdio_device_id __maybe_unused mv88q2xxx_tbl[] = {
+ { MARVELL_PHY_ID_88Q2110, MARVELL_PHY_ID_MASK },
+ { /*sentinel*/ }
+};
+MODULE_DEVICE_TABLE(mdio, mv88q2xxx_tbl);
+
+MODULE_DESCRIPTION("Marvell 88Q2XXX 100/1000BASE-T1 Automotive Ethernet PHY driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/phy/marvell-88x2222.c b/drivers/net/phy/marvell-88x2222.c
index f83cae64585d..e3aa30dad2e6 100644
--- a/drivers/net/phy/marvell-88x2222.c
+++ b/drivers/net/phy/marvell-88x2222.c
@@ -14,7 +14,6 @@
#include <linux/mdio.h>
#include <linux/marvell_phy.h>
#include <linux/of.h>
-#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/sfp.h>
#include <linux/netdevice.h>
diff --git a/drivers/net/phy/mdio_bus.c b/drivers/net/phy/mdio_bus.c
index 8b3618d3da4a..25dcaa49ab8b 100644
--- a/drivers/net/phy/mdio_bus.c
+++ b/drivers/net/phy/mdio_bus.c
@@ -107,16 +107,21 @@ int mdiobus_unregister_device(struct mdio_device *mdiodev)
}
EXPORT_SYMBOL(mdiobus_unregister_device);
-struct phy_device *mdiobus_get_phy(struct mii_bus *bus, int addr)
+static struct mdio_device *mdiobus_find_device(struct mii_bus *bus, int addr)
{
bool addr_valid = addr >= 0 && addr < ARRAY_SIZE(bus->mdio_map);
- struct mdio_device *mdiodev;
if (WARN_ONCE(!addr_valid, "addr %d out of range\n", addr))
return NULL;
- mdiodev = bus->mdio_map[addr];
+ return bus->mdio_map[addr];
+}
+
+struct phy_device *mdiobus_get_phy(struct mii_bus *bus, int addr)
+{
+ struct mdio_device *mdiodev;
+ mdiodev = mdiobus_find_device(bus, addr);
if (!mdiodev)
return NULL;
@@ -129,7 +134,7 @@ EXPORT_SYMBOL(mdiobus_get_phy);
bool mdiobus_is_registered_device(struct mii_bus *bus, int addr)
{
- return bus->mdio_map[addr];
+ return mdiobus_find_device(bus, addr) != NULL;
}
EXPORT_SYMBOL(mdiobus_is_registered_device);
@@ -1210,6 +1215,26 @@ int mdiobus_c45_write_nested(struct mii_bus *bus, int addr, int devad,
}
EXPORT_SYMBOL(mdiobus_c45_write_nested);
+/*
+ * __mdiobus_modify - Convenience function for modifying a given mdio device
+ * register
+ * @bus: the mii_bus struct
+ * @addr: the phy address
+ * @regnum: register number to write
+ * @mask: bit mask of bits to clear
+ * @set: bit mask of bits to set
+ */
+int __mdiobus_modify(struct mii_bus *bus, int addr, u32 regnum, u16 mask,
+ u16 set)
+{
+ int err;
+
+ err = __mdiobus_modify_changed(bus, addr, regnum, mask, set);
+
+ return err < 0 ? err : 0;
+}
+EXPORT_SYMBOL_GPL(__mdiobus_modify);
+
/**
* mdiobus_modify - Convenience function for modifying a given mdio device
* register
@@ -1224,10 +1249,10 @@ int mdiobus_modify(struct mii_bus *bus, int addr, u32 regnum, u16 mask, u16 set)
int err;
mutex_lock(&bus->mdio_lock);
- err = __mdiobus_modify_changed(bus, addr, regnum, mask, set);
+ err = __mdiobus_modify(bus, addr, regnum, mask, set);
mutex_unlock(&bus->mdio_lock);
- return err < 0 ? err : 0;
+ return err;
}
EXPORT_SYMBOL_GPL(mdiobus_modify);
diff --git a/drivers/net/phy/mediatek-ge-soc.c b/drivers/net/phy/mediatek-ge-soc.c
index 95369171a7ba..da512fab0eb0 100644
--- a/drivers/net/phy/mediatek-ge-soc.c
+++ b/drivers/net/phy/mediatek-ge-soc.c
@@ -2,8 +2,6 @@
#include <linux/bitfield.h>
#include <linux/module.h>
#include <linux/nvmem-consumer.h>
-#include <linux/of_address.h>
-#include <linux/of_platform.h>
#include <linux/pinctrl/consumer.h>
#include <linux/phy.h>
diff --git a/drivers/net/phy/motorcomm.c b/drivers/net/phy/motorcomm.c
index 2fa5a90e073b..7a11fdb687cc 100644
--- a/drivers/net/phy/motorcomm.c
+++ b/drivers/net/phy/motorcomm.c
@@ -163,6 +163,10 @@
#define YT8521_CHIP_CONFIG_REG 0xA001
#define YT8521_CCR_SW_RST BIT(15)
+#define YT8531_RGMII_LDO_VOL_MASK GENMASK(5, 4)
+#define YT8531_LDO_VOL_3V3 0x0
+#define YT8531_LDO_VOL_1V8 0x2
+
/* 1b0 disable 1.9ns rxc clock delay *default*
* 1b1 enable 1.9ns rxc clock delay
*/
@@ -236,6 +240,12 @@
*/
#define YTPHY_WCR_TYPE_PULSE BIT(0)
+#define YTPHY_PAD_DRIVE_STRENGTH_REG 0xA010
+#define YT8531_RGMII_RXC_DS_MASK GENMASK(15, 13)
+#define YT8531_RGMII_RXD_DS_HI_MASK BIT(12) /* Bit 2 of rxd_ds */
+#define YT8531_RGMII_RXD_DS_LOW_MASK GENMASK(5, 4) /* Bit 1/0 of rxd_ds */
+#define YT8531_RGMII_RX_DS_DEFAULT 0x3
+
#define YTPHY_SYNCE_CFG_REG 0xA012
#define YT8521_SCR_SYNCE_ENABLE BIT(5)
/* 1b0 output 25m clock
@@ -835,6 +845,110 @@ static int ytphy_rgmii_clk_delay_config_with_lock(struct phy_device *phydev)
}
/**
+ * struct ytphy_ldo_vol_map - map a current value to a register value
+ * @vol: ldo voltage
+ * @ds: value in the register
+ * @cur: value in device configuration
+ */
+struct ytphy_ldo_vol_map {
+ u32 vol;
+ u32 ds;
+ u32 cur;
+};
+
+static const struct ytphy_ldo_vol_map yt8531_ldo_vol[] = {
+ {.vol = YT8531_LDO_VOL_1V8, .ds = 0, .cur = 1200},
+ {.vol = YT8531_LDO_VOL_1V8, .ds = 1, .cur = 2100},
+ {.vol = YT8531_LDO_VOL_1V8, .ds = 2, .cur = 2700},
+ {.vol = YT8531_LDO_VOL_1V8, .ds = 3, .cur = 2910},
+ {.vol = YT8531_LDO_VOL_1V8, .ds = 4, .cur = 3110},
+ {.vol = YT8531_LDO_VOL_1V8, .ds = 5, .cur = 3600},
+ {.vol = YT8531_LDO_VOL_1V8, .ds = 6, .cur = 3970},
+ {.vol = YT8531_LDO_VOL_1V8, .ds = 7, .cur = 4350},
+ {.vol = YT8531_LDO_VOL_3V3, .ds = 0, .cur = 3070},
+ {.vol = YT8531_LDO_VOL_3V3, .ds = 1, .cur = 4080},
+ {.vol = YT8531_LDO_VOL_3V3, .ds = 2, .cur = 4370},
+ {.vol = YT8531_LDO_VOL_3V3, .ds = 3, .cur = 4680},
+ {.vol = YT8531_LDO_VOL_3V3, .ds = 4, .cur = 5020},
+ {.vol = YT8531_LDO_VOL_3V3, .ds = 5, .cur = 5450},
+ {.vol = YT8531_LDO_VOL_3V3, .ds = 6, .cur = 5740},
+ {.vol = YT8531_LDO_VOL_3V3, .ds = 7, .cur = 6140},
+};
+
+static u32 yt8531_get_ldo_vol(struct phy_device *phydev)
+{
+ u32 val;
+
+ val = ytphy_read_ext_with_lock(phydev, YT8521_CHIP_CONFIG_REG);
+ val = FIELD_GET(YT8531_RGMII_LDO_VOL_MASK, val);
+
+ return val <= YT8531_LDO_VOL_1V8 ? val : YT8531_LDO_VOL_1V8;
+}
+
+static int yt8531_get_ds_map(struct phy_device *phydev, u32 cur)
+{
+ u32 vol;
+ int i;
+
+ vol = yt8531_get_ldo_vol(phydev);
+ for (i = 0; i < ARRAY_SIZE(yt8531_ldo_vol); i++) {
+ if (yt8531_ldo_vol[i].vol == vol && yt8531_ldo_vol[i].cur == cur)
+ return yt8531_ldo_vol[i].ds;
+ }
+
+ return -EINVAL;
+}
+
+static int yt8531_set_ds(struct phy_device *phydev)
+{
+ struct device_node *node = phydev->mdio.dev.of_node;
+ u32 ds_field_low, ds_field_hi, val;
+ int ret, ds;
+
+ /* set rgmii rx clk driver strength */
+ if (!of_property_read_u32(node, "motorcomm,rx-clk-drv-microamp", &val)) {
+ ds = yt8531_get_ds_map(phydev, val);
+ if (ds < 0)
+ return dev_err_probe(&phydev->mdio.dev, ds,
+ "No matching current value was found.\n");
+ } else {
+ ds = YT8531_RGMII_RX_DS_DEFAULT;
+ }
+
+ ret = ytphy_modify_ext_with_lock(phydev,
+ YTPHY_PAD_DRIVE_STRENGTH_REG,
+ YT8531_RGMII_RXC_DS_MASK,
+ FIELD_PREP(YT8531_RGMII_RXC_DS_MASK, ds));
+ if (ret < 0)
+ return ret;
+
+ /* set rgmii rx data driver strength */
+ if (!of_property_read_u32(node, "motorcomm,rx-data-drv-microamp", &val)) {
+ ds = yt8531_get_ds_map(phydev, val);
+ if (ds < 0)
+ return dev_err_probe(&phydev->mdio.dev, ds,
+ "No matching current value was found.\n");
+ } else {
+ ds = YT8531_RGMII_RX_DS_DEFAULT;
+ }
+
+ ds_field_hi = FIELD_GET(BIT(2), ds);
+ ds_field_hi = FIELD_PREP(YT8531_RGMII_RXD_DS_HI_MASK, ds_field_hi);
+
+ ds_field_low = FIELD_GET(GENMASK(1, 0), ds);
+ ds_field_low = FIELD_PREP(YT8531_RGMII_RXD_DS_LOW_MASK, ds_field_low);
+
+ ret = ytphy_modify_ext_with_lock(phydev,
+ YTPHY_PAD_DRIVE_STRENGTH_REG,
+ YT8531_RGMII_RXD_DS_LOW_MASK | YT8531_RGMII_RXD_DS_HI_MASK,
+ ds_field_low | ds_field_hi);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+/**
* yt8521_probe() - read chip config then set suitable polling_mode
* @phydev: a pointer to a &struct phy_device
*
@@ -1518,6 +1632,10 @@ static int yt8531_config_init(struct phy_device *phydev)
return ret;
}
+ ret = yt8531_set_ds(phydev);
+ if (ret < 0)
+ return ret;
+
return 0;
}
diff --git a/drivers/net/phy/phy-c45.c b/drivers/net/phy/phy-c45.c
index 93ed07223377..8e6fd4962c48 100644
--- a/drivers/net/phy/phy-c45.c
+++ b/drivers/net/phy/phy-c45.c
@@ -108,7 +108,7 @@ EXPORT_SYMBOL_GPL(genphy_c45_pma_baset1_setup_master_slave);
*/
int genphy_c45_pma_setup_forced(struct phy_device *phydev)
{
- int ctrl1, ctrl2, ret;
+ int bt1_ctrl, ctrl1, ctrl2, ret;
/* Half duplex is not supported */
if (phydev->duplex != DUPLEX_FULL)
@@ -176,6 +176,15 @@ int genphy_c45_pma_setup_forced(struct phy_device *phydev)
ret = genphy_c45_pma_baset1_setup_master_slave(phydev);
if (ret < 0)
return ret;
+
+ bt1_ctrl = 0;
+ if (phydev->speed == SPEED_1000)
+ bt1_ctrl = MDIO_PMA_PMD_BT1_CTRL_STRAP_B1000;
+
+ ret = phy_modify_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PMA_PMD_BT1_CTRL,
+ MDIO_PMA_PMD_BT1_CTRL_STRAP, bt1_ctrl);
+ if (ret < 0)
+ return ret;
}
return genphy_c45_an_disable_aneg(phydev);
@@ -873,6 +882,44 @@ int genphy_c45_an_config_eee_aneg(struct phy_device *phydev)
}
/**
+ * genphy_c45_pma_baset1_read_abilities - read supported baset1 link modes from PMA
+ * @phydev: target phy_device struct
+ *
+ * Read the supported link modes from the extended BASE-T1 ability register
+ */
+int genphy_c45_pma_baset1_read_abilities(struct phy_device *phydev)
+{
+ int val;
+
+ val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PMA_PMD_BT1);
+ if (val < 0)
+ return val;
+
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_10baseT1L_Full_BIT,
+ phydev->supported,
+ val & MDIO_PMA_PMD_BT1_B10L_ABLE);
+
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_100baseT1_Full_BIT,
+ phydev->supported,
+ val & MDIO_PMA_PMD_BT1_B100_ABLE);
+
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseT1_Full_BIT,
+ phydev->supported,
+ val & MDIO_PMA_PMD_BT1_B1000_ABLE);
+
+ val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_T1_STAT);
+ if (val < 0)
+ return val;
+
+ linkmode_mod_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
+ phydev->supported,
+ val & MDIO_AN_STAT1_ABLE);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(genphy_c45_pma_baset1_read_abilities);
+
+/**
* genphy_c45_pma_read_abilities - read supported link modes from PMA
* @phydev: target phy_device struct
*
@@ -968,21 +1015,9 @@ int genphy_c45_pma_read_abilities(struct phy_device *phydev)
}
if (val & MDIO_PMA_EXTABLE_BT1) {
- val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PMA_PMD_BT1);
+ val = genphy_c45_pma_baset1_read_abilities(phydev);
if (val < 0)
return val;
-
- linkmode_mod_bit(ETHTOOL_LINK_MODE_10baseT1L_Full_BIT,
- phydev->supported,
- val & MDIO_PMA_PMD_BT1_B10L_ABLE);
-
- val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_T1_STAT);
- if (val < 0)
- return val;
-
- linkmode_mod_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
- phydev->supported,
- val & MDIO_AN_STAT1_ABLE);
}
}
diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index d0aaa5cad853..4f1c8bb199e9 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -34,6 +34,10 @@ enum {
PHYLINK_DISABLE_STOPPED,
PHYLINK_DISABLE_LINK,
PHYLINK_DISABLE_MAC_WOL,
+
+ PCS_STATE_DOWN = 0,
+ PCS_STATE_STARTING,
+ PCS_STATE_STARTED,
};
/**
@@ -72,6 +76,7 @@ struct phylink {
struct phylink_link_state phy_state;
struct work_struct resolve;
unsigned int pcs_neg_mode;
+ unsigned int pcs_state;
bool mac_link_dropped;
bool using_mac_select_pcs;
@@ -993,6 +998,40 @@ static void phylink_resolve_an_pause(struct phylink_link_state *state)
}
}
+static void phylink_pcs_pre_config(struct phylink_pcs *pcs,
+ phy_interface_t interface)
+{
+ if (pcs && pcs->ops->pcs_pre_config)
+ pcs->ops->pcs_pre_config(pcs, interface);
+}
+
+static int phylink_pcs_post_config(struct phylink_pcs *pcs,
+ phy_interface_t interface)
+{
+ int err = 0;
+
+ if (pcs && pcs->ops->pcs_post_config)
+ err = pcs->ops->pcs_post_config(pcs, interface);
+
+ return err;
+}
+
+static void phylink_pcs_disable(struct phylink_pcs *pcs)
+{
+ if (pcs && pcs->ops->pcs_disable)
+ pcs->ops->pcs_disable(pcs);
+}
+
+static int phylink_pcs_enable(struct phylink_pcs *pcs)
+{
+ int err = 0;
+
+ if (pcs && pcs->ops->pcs_enable)
+ err = pcs->ops->pcs_enable(pcs);
+
+ return err;
+}
+
static int phylink_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
const struct phylink_link_state *state,
bool permit_pause_to_mac)
@@ -1027,30 +1066,33 @@ static void phylink_pcs_poll_start(struct phylink *pl)
static void phylink_mac_config(struct phylink *pl,
const struct phylink_link_state *state)
{
+ struct phylink_link_state st = *state;
+
+ /* Stop drivers incorrectly using these */
+ linkmode_zero(st.lp_advertising);
+ st.speed = SPEED_UNKNOWN;
+ st.duplex = DUPLEX_UNKNOWN;
+ st.an_complete = false;
+ st.link = false;
+
phylink_dbg(pl,
- "%s: mode=%s/%s/%s/%s/%s adv=%*pb pause=%02x link=%u\n",
+ "%s: mode=%s/%s/%s adv=%*pb pause=%02x\n",
__func__, phylink_an_mode_str(pl->cur_link_an_mode),
- phy_modes(state->interface),
- phy_speed_to_str(state->speed),
- phy_duplex_to_str(state->duplex),
- phy_rate_matching_to_str(state->rate_matching),
- __ETHTOOL_LINK_MODE_MASK_NBITS, state->advertising,
- state->pause, state->link);
+ phy_modes(st.interface),
+ phy_rate_matching_to_str(st.rate_matching),
+ __ETHTOOL_LINK_MODE_MASK_NBITS, st.advertising,
+ st.pause);
- pl->mac_ops->mac_config(pl->config, pl->cur_link_an_mode, state);
+ pl->mac_ops->mac_config(pl->config, pl->cur_link_an_mode, &st);
}
-static void phylink_mac_pcs_an_restart(struct phylink *pl)
+static void phylink_pcs_an_restart(struct phylink *pl)
{
- if (linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
- pl->link_config.advertising) &&
+ if (pl->pcs && linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
+ pl->link_config.advertising) &&
phy_interface_mode_is_8023z(pl->link_config.interface) &&
- phylink_autoneg_inband(pl->cur_link_an_mode)) {
- if (pl->pcs)
- pl->pcs->ops->pcs_an_restart(pl->pcs);
- else if (pl->config->legacy_pre_march2020)
- pl->mac_ops->mac_an_restart(pl->config);
- }
+ phylink_autoneg_inband(pl->cur_link_an_mode))
+ pl->pcs->ops->pcs_an_restart(pl->pcs);
}
static void phylink_major_config(struct phylink *pl, bool restart,
@@ -1095,11 +1137,28 @@ static void phylink_major_config(struct phylink *pl, bool restart,
/* If we have a new PCS, switch to the new PCS after preparing the MAC
* for the change.
*/
- if (pcs_changed)
+ if (pcs_changed) {
+ phylink_pcs_disable(pl->pcs);
+
+ if (pl->pcs)
+ pl->pcs->phylink = NULL;
+
+ pcs->phylink = pl;
+
pl->pcs = pcs;
+ }
+
+ if (pl->pcs)
+ phylink_pcs_pre_config(pl->pcs, state->interface);
phylink_mac_config(pl, state);
+ if (pl->pcs)
+ phylink_pcs_post_config(pl->pcs, state->interface);
+
+ if (pl->pcs_state == PCS_STATE_STARTING || pcs_changed)
+ phylink_pcs_enable(pl->pcs);
+
neg_mode = pl->cur_link_an_mode;
if (pl->pcs && pl->pcs->neg_mode)
neg_mode = pl->pcs_neg_mode;
@@ -1113,7 +1172,7 @@ static void phylink_major_config(struct phylink *pl, bool restart,
restart = true;
if (restart)
- phylink_mac_pcs_an_restart(pl);
+ phylink_pcs_an_restart(pl);
if (pl->mac_ops->mac_finish) {
err = pl->mac_ops->mac_finish(pl->config, pl->cur_link_an_mode,
@@ -1146,13 +1205,6 @@ static int phylink_change_inband_advert(struct phylink *pl)
if (test_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state))
return 0;
- if (!pl->pcs && pl->config->legacy_pre_march2020) {
- /* Legacy method */
- phylink_mac_config(pl, &pl->link_config);
- phylink_mac_pcs_an_restart(pl);
- return 0;
- }
-
phylink_dbg(pl, "%s: mode=%s/%s adv=%*pb pause=%02x\n", __func__,
phylink_an_mode_str(pl->cur_link_an_mode),
phy_modes(pl->link_config.interface),
@@ -1178,7 +1230,7 @@ static int phylink_change_inband_advert(struct phylink *pl)
return ret;
if (ret > 0)
- phylink_mac_pcs_an_restart(pl);
+ phylink_pcs_an_restart(pl);
return 0;
}
@@ -1205,9 +1257,6 @@ static void phylink_mac_pcs_get_state(struct phylink *pl,
if (pl->pcs)
pl->pcs->ops->pcs_get_state(pl->pcs, state);
- else if (pl->mac_ops->mac_pcs_get_state &&
- pl->config->legacy_pre_march2020)
- pl->mac_ops->mac_pcs_get_state(pl->config, state);
else
state->link = 0;
}
@@ -1440,13 +1489,6 @@ static void phylink_resolve(struct work_struct *w)
}
phylink_major_config(pl, false, &link_state);
pl->link_config.interface = link_state.interface;
- } else if (!pl->pcs && pl->config->legacy_pre_march2020) {
- /* The interface remains unchanged, only the speed,
- * duplex or pause settings have changed. Call the
- * old mac_config() method to configure the MAC/PCS
- * only if we do not have a legacy MAC driver.
- */
- phylink_mac_config(pl, &link_state);
}
}
@@ -1586,6 +1628,7 @@ struct phylink *phylink_create(struct phylink_config *config,
pl->link_config.pause = MLO_PAUSE_AN;
pl->link_config.speed = SPEED_UNKNOWN;
pl->link_config.duplex = DUPLEX_UNKNOWN;
+ pl->pcs_state = PCS_STATE_DOWN;
pl->mac_ops = mac_ops;
__set_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state);
timer_setup(&pl->link_poll, phylink_fixed_poll, 0);
@@ -1939,6 +1982,14 @@ void phylink_disconnect_phy(struct phylink *pl)
}
EXPORT_SYMBOL_GPL(phylink_disconnect_phy);
+static void phylink_link_changed(struct phylink *pl, bool up, const char *what)
+{
+ if (!up)
+ pl->mac_link_dropped = true;
+ phylink_run_resolve(pl);
+ phylink_dbg(pl, "%s link %s\n", what, up ? "up" : "down");
+}
+
/**
* phylink_mac_change() - notify phylink of a change in MAC state
* @pl: a pointer to a &struct phylink returned from phylink_create()
@@ -1949,13 +2000,30 @@ EXPORT_SYMBOL_GPL(phylink_disconnect_phy);
*/
void phylink_mac_change(struct phylink *pl, bool up)
{
- if (!up)
- pl->mac_link_dropped = true;
- phylink_run_resolve(pl);
- phylink_dbg(pl, "mac link %s\n", up ? "up" : "down");
+ phylink_link_changed(pl, up, "mac");
}
EXPORT_SYMBOL_GPL(phylink_mac_change);
+/**
+ * phylink_pcs_change() - notify phylink of a change to PCS link state
+ * @pcs: pointer to &struct phylink_pcs
+ * @up: indicates whether the link is currently up.
+ *
+ * The PCS driver should call this when the state of its link changes
+ * (e.g. link failure, new negotiation results, etc.) Note: it should
+ * not determine "up" by reading the BMSR. If in doubt about the link
+ * state at interrupt time, then pass true if pcs_get_state() returns
+ * the latched link-down state, otherwise pass false.
+ */
+void phylink_pcs_change(struct phylink_pcs *pcs, bool up)
+{
+ struct phylink *pl = pcs->phylink;
+
+ if (pl)
+ phylink_link_changed(pl, up, "pcs");
+}
+EXPORT_SYMBOL_GPL(phylink_pcs_change);
+
static irqreturn_t phylink_link_handler(int irq, void *data)
{
struct phylink *pl = data;
@@ -1987,6 +2055,8 @@ void phylink_start(struct phylink *pl)
if (pl->netdev)
netif_carrier_off(pl->netdev);
+ pl->pcs_state = PCS_STATE_STARTING;
+
/* 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 advertisement for Serdes links.
@@ -1997,6 +2067,8 @@ void phylink_start(struct phylink *pl)
*/
phylink_mac_initial_config(pl, true);
+ pl->pcs_state = PCS_STATE_STARTED;
+
phylink_enable_and_run_resolve(pl, PHYLINK_DISABLE_STOPPED);
if (pl->cfg_link_an_mode == MLO_AN_FIXED && pl->link_gpio) {
@@ -2015,15 +2087,9 @@ void phylink_start(struct phylink *pl)
poll = true;
}
- switch (pl->cfg_link_an_mode) {
- case MLO_AN_FIXED:
+ if (pl->cfg_link_an_mode == MLO_AN_FIXED)
poll |= pl->config->poll_fixed_state;
- break;
- case MLO_AN_INBAND:
- if (pl->pcs)
- poll |= pl->pcs->poll;
- break;
- }
+
if (poll)
mod_timer(&pl->link_poll, jiffies + HZ);
if (pl->phydev)
@@ -2060,6 +2126,10 @@ void phylink_stop(struct phylink *pl)
}
phylink_run_resolve_and_disable(pl, PHYLINK_DISABLE_STOPPED);
+
+ pl->pcs_state = PCS_STATE_DOWN;
+
+ phylink_pcs_disable(pl->pcs);
}
EXPORT_SYMBOL_GPL(phylink_stop);
@@ -2449,7 +2519,7 @@ int phylink_ethtool_nway_reset(struct phylink *pl)
if (pl->phydev)
ret = phy_restart_aneg(pl->phydev);
- phylink_mac_pcs_an_restart(pl);
+ phylink_pcs_an_restart(pl);
return ret;
}
@@ -3433,7 +3503,7 @@ static void phylink_decode_usgmii_word(struct phylink_link_state *state,
*
* Parse the Clause 37 or Cisco SGMII link partner negotiation word into
* the phylink @state structure. This is suitable to be used for implementing
- * the mac_pcs_get_state() member of the struct phylink_mac_ops structure if
+ * the pcs_get_state() member of the struct phylink_pcs_ops structure if
* accessing @bmsr and @lpa cannot be done with MDIO directly.
*/
void phylink_mii_c22_pcs_decode_state(struct phylink_link_state *state,
@@ -3483,7 +3553,7 @@ EXPORT_SYMBOL_GPL(phylink_mii_c22_pcs_decode_state);
* Read the MAC PCS state from the MII device configured in @config and
* parse the Clause 37 or Cisco SGMII link partner negotiation word into
* the phylink @state structure. This is suitable to be directly plugged
- * into the mac_pcs_get_state() member of the struct phylink_mac_ops
+ * into the pcs_get_state() member of the struct phylink_pcs_ops
* structure.
*/
void phylink_mii_c22_pcs_get_state(struct mdio_device *pcs,
@@ -3594,8 +3664,8 @@ EXPORT_SYMBOL_GPL(phylink_mii_c22_pcs_config);
* clause 37 negotiation.
*
* Restart the clause 37 negotiation with the link partner. This is
- * suitable to be directly plugged into the mac_pcs_get_state() member
- * of the struct phylink_mac_ops structure.
+ * suitable to be directly plugged into the pcs_get_state() member
+ * of the struct phylink_pcs_ops structure.
*/
void phylink_mii_c22_pcs_an_restart(struct mdio_device *pcs)
{
diff --git a/drivers/net/phy/smsc.c b/drivers/net/phy/smsc.c
index 692930750215..c88edb19d2e7 100644
--- a/drivers/net/phy/smsc.c
+++ b/drivers/net/phy/smsc.c
@@ -20,6 +20,8 @@
#include <linux/of.h>
#include <linux/phy.h>
#include <linux/netdevice.h>
+#include <linux/crc16.h>
+#include <linux/etherdevice.h>
#include <linux/smscphy.h>
/* Vendor-specific PHY Definitions */
@@ -51,6 +53,7 @@ struct smsc_phy_priv {
unsigned int edpd_enable:1;
unsigned int edpd_mode_set_by_user:1;
unsigned int edpd_max_wait_ms;
+ bool wol_arp;
};
static int smsc_phy_ack_interrupt(struct phy_device *phydev)
@@ -258,6 +261,243 @@ int lan87xx_read_status(struct phy_device *phydev)
}
EXPORT_SYMBOL_GPL(lan87xx_read_status);
+static int lan874x_phy_config_init(struct phy_device *phydev)
+{
+ u16 val;
+ int rc;
+
+ /* Setup LED2/nINT/nPME pin to function as nPME. May need user option
+ * to use LED1/nINT/nPME.
+ */
+ val = MII_LAN874X_PHY_PME2_SET;
+
+ /* The bits MII_LAN874X_PHY_WOL_PFDA_FR, MII_LAN874X_PHY_WOL_WUFR,
+ * MII_LAN874X_PHY_WOL_MPR, and MII_LAN874X_PHY_WOL_BCAST_FR need to
+ * be cleared to de-assert PME signal after a WoL event happens, but
+ * using PME auto clear gets around that.
+ */
+ val |= MII_LAN874X_PHY_PME_SELF_CLEAR;
+ rc = phy_write_mmd(phydev, MDIO_MMD_PCS, MII_LAN874X_PHY_MMD_WOL_WUCSR,
+ val);
+ if (rc < 0)
+ return rc;
+
+ /* set nPME self clear delay time */
+ rc = phy_write_mmd(phydev, MDIO_MMD_PCS, MII_LAN874X_PHY_MMD_MCFGR,
+ MII_LAN874X_PHY_PME_SELF_CLEAR_DELAY);
+ if (rc < 0)
+ return rc;
+
+ return smsc_phy_config_init(phydev);
+}
+
+static void lan874x_get_wol(struct phy_device *phydev,
+ struct ethtool_wolinfo *wol)
+{
+ struct smsc_phy_priv *priv = phydev->priv;
+ int rc;
+
+ wol->supported = (WAKE_UCAST | WAKE_BCAST | WAKE_MAGIC |
+ WAKE_ARP | WAKE_MCAST);
+ wol->wolopts = 0;
+
+ rc = phy_read_mmd(phydev, MDIO_MMD_PCS, MII_LAN874X_PHY_MMD_WOL_WUCSR);
+ if (rc < 0)
+ return;
+
+ if (rc & MII_LAN874X_PHY_WOL_PFDAEN)
+ wol->wolopts |= WAKE_UCAST;
+
+ if (rc & MII_LAN874X_PHY_WOL_BCSTEN)
+ wol->wolopts |= WAKE_BCAST;
+
+ if (rc & MII_LAN874X_PHY_WOL_MPEN)
+ wol->wolopts |= WAKE_MAGIC;
+
+ if (rc & MII_LAN874X_PHY_WOL_WUEN) {
+ if (priv->wol_arp)
+ wol->wolopts |= WAKE_ARP;
+ else
+ wol->wolopts |= WAKE_MCAST;
+ }
+}
+
+static u16 smsc_crc16(const u8 *buffer, size_t len)
+{
+ return bitrev16(crc16(0xFFFF, buffer, len));
+}
+
+static int lan874x_chk_wol_pattern(const u8 pattern[], const u16 *mask,
+ u8 len, u8 *data, u8 *datalen)
+{
+ size_t i, j, k;
+ int ret = 0;
+ u16 bits;
+
+ /* Pattern filtering can match up to 128 bytes of frame data. There
+ * are 8 registers to program the 16-bit masks, where each bit means
+ * the byte will be compared. The frame data will then go through a
+ * CRC16 calculation for hardware comparison. This helper function
+ * makes sure only relevant frame data are included in this
+ * calculation. It provides a warning when the masks and expected
+ * data size do not match.
+ */
+ i = 0;
+ k = 0;
+ while (len > 0) {
+ bits = *mask;
+ for (j = 0; j < 16; j++, i++, len--) {
+ /* No more pattern. */
+ if (!len) {
+ /* The rest of bitmap is not empty. */
+ if (bits)
+ ret = i + 1;
+ break;
+ }
+ if (bits & 1)
+ data[k++] = pattern[i];
+ bits >>= 1;
+ }
+ mask++;
+ }
+ *datalen = k;
+ return ret;
+}
+
+static int lan874x_set_wol_pattern(struct phy_device *phydev, u16 val,
+ const u8 data[], u8 datalen,
+ const u16 *mask, u8 masklen)
+{
+ u16 crc, reg;
+ int rc;
+
+ /* Starting pattern offset is set before calling this function. */
+ val |= MII_LAN874X_PHY_WOL_FILTER_EN;
+ rc = phy_write_mmd(phydev, MDIO_MMD_PCS,
+ MII_LAN874X_PHY_MMD_WOL_WUF_CFGA, val);
+ if (rc < 0)
+ return rc;
+
+ crc = smsc_crc16(data, datalen);
+ rc = phy_write_mmd(phydev, MDIO_MMD_PCS,
+ MII_LAN874X_PHY_MMD_WOL_WUF_CFGB, crc);
+ if (rc < 0)
+ return rc;
+
+ masklen = (masklen + 15) & ~0xf;
+ reg = MII_LAN874X_PHY_MMD_WOL_WUF_MASK7;
+ while (masklen >= 16) {
+ rc = phy_write_mmd(phydev, MDIO_MMD_PCS, reg, *mask);
+ if (rc < 0)
+ return rc;
+ reg--;
+ mask++;
+ masklen -= 16;
+ }
+
+ /* Clear out the rest of mask registers. */
+ while (reg != MII_LAN874X_PHY_MMD_WOL_WUF_MASK0) {
+ phy_write_mmd(phydev, MDIO_MMD_PCS, reg, 0);
+ reg--;
+ }
+ return rc;
+}
+
+static int lan874x_set_wol(struct phy_device *phydev,
+ struct ethtool_wolinfo *wol)
+{
+ struct net_device *ndev = phydev->attached_dev;
+ struct smsc_phy_priv *priv = phydev->priv;
+ u16 val, val_wucsr;
+ u8 data[128];
+ u8 datalen;
+ int rc;
+
+ /* lan874x has only one WoL filter pattern */
+ if ((wol->wolopts & (WAKE_ARP | WAKE_MCAST)) ==
+ (WAKE_ARP | WAKE_MCAST)) {
+ phydev_info(phydev,
+ "lan874x WoL supports one of ARP|MCAST at a time\n");
+ return -EOPNOTSUPP;
+ }
+
+ rc = phy_read_mmd(phydev, MDIO_MMD_PCS, MII_LAN874X_PHY_MMD_WOL_WUCSR);
+ if (rc < 0)
+ return rc;
+
+ val_wucsr = rc;
+
+ if (wol->wolopts & WAKE_UCAST)
+ val_wucsr |= MII_LAN874X_PHY_WOL_PFDAEN;
+ else
+ val_wucsr &= ~MII_LAN874X_PHY_WOL_PFDAEN;
+
+ if (wol->wolopts & WAKE_BCAST)
+ val_wucsr |= MII_LAN874X_PHY_WOL_BCSTEN;
+ else
+ val_wucsr &= ~MII_LAN874X_PHY_WOL_BCSTEN;
+
+ if (wol->wolopts & WAKE_MAGIC)
+ val_wucsr |= MII_LAN874X_PHY_WOL_MPEN;
+ else
+ val_wucsr &= ~MII_LAN874X_PHY_WOL_MPEN;
+
+ /* Need to use pattern matching */
+ if (wol->wolopts & (WAKE_ARP | WAKE_MCAST))
+ val_wucsr |= MII_LAN874X_PHY_WOL_WUEN;
+ else
+ val_wucsr &= ~MII_LAN874X_PHY_WOL_WUEN;
+
+ if (wol->wolopts & WAKE_ARP) {
+ const u8 pattern[2] = { 0x08, 0x06 };
+ const u16 mask[1] = { 0x0003 };
+
+ rc = lan874x_chk_wol_pattern(pattern, mask, 2, data,
+ &datalen);
+ if (rc)
+ phydev_dbg(phydev, "pattern not valid at %d\n", rc);
+
+ /* Need to match broadcast destination address and provided
+ * data pattern at offset 12.
+ */
+ val = 12 | MII_LAN874X_PHY_WOL_FILTER_BCSTEN;
+ rc = lan874x_set_wol_pattern(phydev, val, data, datalen, mask,
+ 2);
+ if (rc < 0)
+ return rc;
+ priv->wol_arp = true;
+ }
+
+ if (wol->wolopts & WAKE_MCAST) {
+ /* Need to match multicast destination address. */
+ val = MII_LAN874X_PHY_WOL_FILTER_MCASTTEN;
+ rc = lan874x_set_wol_pattern(phydev, val, data, 0, NULL, 0);
+ if (rc < 0)
+ return rc;
+ priv->wol_arp = false;
+ }
+
+ if (wol->wolopts & (WAKE_MAGIC | WAKE_UCAST)) {
+ const u8 *mac = (const u8 *)ndev->dev_addr;
+ int i, reg;
+
+ reg = MII_LAN874X_PHY_MMD_WOL_RX_ADDRC;
+ for (i = 0; i < 6; i += 2, reg--) {
+ rc = phy_write_mmd(phydev, MDIO_MMD_PCS, reg,
+ ((mac[i + 1] << 8) | mac[i]));
+ if (rc < 0)
+ return rc;
+ }
+ }
+
+ rc = phy_write_mmd(phydev, MDIO_MMD_PCS, MII_LAN874X_PHY_MMD_WOL_WUCSR,
+ val_wucsr);
+ if (rc < 0)
+ return rc;
+
+ return 0;
+}
+
static int smsc_get_sset_count(struct phy_device *phydev)
{
return ARRAY_SIZE(smsc_hw_stats);
@@ -533,7 +773,7 @@ static struct phy_driver smsc_phy_driver[] = {
/* basic functions */
.read_status = lan87xx_read_status,
- .config_init = smsc_phy_config_init,
+ .config_init = lan874x_phy_config_init,
.soft_reset = smsc_phy_reset,
/* IRQ related */
@@ -548,6 +788,10 @@ static struct phy_driver smsc_phy_driver[] = {
.get_tunable = smsc_phy_get_tunable,
.set_tunable = smsc_phy_set_tunable,
+ /* WoL */
+ .set_wol = lan874x_set_wol,
+ .get_wol = lan874x_get_wol,
+
.suspend = genphy_suspend,
.resume = genphy_resume,
}, {
@@ -566,7 +810,7 @@ static struct phy_driver smsc_phy_driver[] = {
/* basic functions */
.read_status = lan87xx_read_status,
- .config_init = smsc_phy_config_init,
+ .config_init = lan874x_phy_config_init,
.soft_reset = smsc_phy_reset,
/* IRQ related */
@@ -581,6 +825,10 @@ static struct phy_driver smsc_phy_driver[] = {
.get_tunable = smsc_phy_get_tunable,
.set_tunable = smsc_phy_set_tunable,
+ /* WoL */
+ .set_wol = lan874x_set_wol,
+ .get_wol = lan874x_get_wol,
+
.suspend = genphy_suspend,
.resume = genphy_resume,
} };