From 42265f3b470984357b074adcd4a452d384a8b444 Mon Sep 17 00:00:00 2001 From: Jiaxun Yang Date: Thu, 19 Mar 2020 21:29:27 +0800 Subject: [PATCH 1/2] ramips: mt7530: Implement set_port_link set_port_link is required by swconfig to setup link mode. Here we implemented set_port_link by touching MII PHY registers. For autoneg enabled case, we set advertise registers to let autoneg reach the target mode and then retrigger autoneg. For non autoneg case, we set BMCR register to force PHY enter desired mode. This patch have been tested on both MT7620 and MT7621. Signed-off-by: Jiaxun Yang --- .../drivers/net/ethernet/mediatek/mt7530.c | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/target/linux/ramips/files-4.14/drivers/net/ethernet/mediatek/mt7530.c b/target/linux/ramips/files-4.14/drivers/net/ethernet/mediatek/mt7530.c index d1e56a76e92d8..7003f3efe4be8 100644 --- a/target/linux/ramips/files-4.14/drivers/net/ethernet/mediatek/mt7530.c +++ b/target/linux/ramips/files-4.14/drivers/net/ethernet/mediatek/mt7530.c @@ -781,6 +781,65 @@ mt7530_get_port_link(struct switch_dev *dev, int port, return 0; } +static int mt7530_set_port_link(struct switch_dev *sw_dev, int port, + struct switch_port_link *link) +{ + if (port < 0 || port >= MT7530_NUM_PORTS) + return -EINVAL; + + /* Setup autoneg advertise here */ + if (link->aneg) { + u16 bmsr, adv, gctrl; + bool ercap; + + sw_dev->ops->phy_read16(sw_dev, port, MII_BMSR, &bmsr); + /* ERCAP means we have MII_CTRL1000 register */ + ercap = !!(bmsr | BMSR_ERCAP); + + adv = ADVERTISE_CSMA | ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM; + gctrl = CTL1000_ENABLE_MASTER; + + switch (link->speed) { + case SWITCH_PORT_SPEED_10: + if (link->duplex) + adv |= ADVERTISE_10FULL; + else + adv |= ADVERTISE_10HALF; + break; + case SWITCH_PORT_SPEED_100: + if (link->duplex) + adv |= ADVERTISE_100FULL; + else + adv |= ADVERTISE_100HALF; + break; + case SWITCH_PORT_SPEED_1000: + if (!ercap || !link->duplex) + return -ENOTSUPP; + /* PHY only supports 1000FULL */ + gctrl |= ADVERTISE_1000FULL; + break; + default: + /* For unknown input speed just enable all speed grades */ + if (link->duplex) { + adv |= ADVERTISE_FULL; + gctrl |= ADVERTISE_1000FULL; + } else { + adv |= ADVERTISE_10HALF | ADVERTISE_100HALF; + gctrl = 0x0; + } + break; + } + + sw_dev->ops->phy_write16(sw_dev, port, MII_ADVERTISE, adv); + if (ercap) + sw_dev->ops->phy_write16(sw_dev, port, MII_CTRL1000, gctrl); + /* Autoneg restart will be triggered in switch_generic_set_link */ + } + + /* Let switch_generic_set_link handle not autoneg case */ + return switch_generic_set_link(sw_dev, port, link); +} + static u64 get_mib_counter(struct mt7530_priv *priv, int i, int port) { unsigned int port_base; @@ -921,6 +980,24 @@ static int mt7621_get_port_stats(struct switch_dev *dev, int port, return 0; } +static int mt7530_phy_read16(struct switch_dev *dev, int addr, + u8 reg, u16 *value) +{ + struct mt7530_priv *priv = container_of(dev, struct mt7530_priv, swdev); + + *value = mdiobus_read(priv->bus, addr, reg); + + return 0; +} + +static int mt7530_phy_write16(struct switch_dev *dev, int addr, + u8 reg, u16 value) +{ + struct mt7530_priv *priv = container_of(dev, struct mt7530_priv, swdev); + + return mdiobus_write(priv->bus, addr, reg, value); +} + static const struct switch_attr mt7530_global[] = { { .type = SWITCH_TYPE_INT, @@ -1034,9 +1111,12 @@ static const struct switch_dev_ops mt7621_ops = { .get_port_pvid = mt7530_get_port_pvid, .set_port_pvid = mt7530_set_port_pvid, .get_port_link = mt7530_get_port_link, + .set_port_link = mt7530_set_port_link, .get_port_stats = mt7621_get_port_stats, .apply_config = mt7530_apply_config, .reset_switch = mt7530_reset_switch, + .phy_read16 = mt7530_phy_read16, + .phy_write16 = mt7530_phy_write16, }; static const struct switch_dev_ops mt7530_ops = { @@ -1057,9 +1137,12 @@ static const struct switch_dev_ops mt7530_ops = { .get_port_pvid = mt7530_get_port_pvid, .set_port_pvid = mt7530_set_port_pvid, .get_port_link = mt7530_get_port_link, + .set_port_link = mt7530_set_port_link, .get_port_stats = mt7530_get_port_stats, .apply_config = mt7530_apply_config, .reset_switch = mt7530_reset_switch, + .phy_read16 = mt7530_phy_read16, + .phy_write16 = mt7530_phy_write16, }; int From be0e374cd9892094bb7394ede563cb4bb648cf9b Mon Sep 17 00:00:00 2001 From: Jiaxun Yang Date: Fri, 20 Mar 2020 11:35:02 +0800 Subject: [PATCH 2/2] kernel: swconfig: reset PHY in switch_generic_set_link For some PHYs, a reset is required to establish new link. As it's very hard to identify these models, we simply deploy a reset in switch_generic_set_link for all kinds of PHYs. It's not harmful to PHY models that don't require a reset to establish new link. Signed-off-by: Jiaxun Yang --- .../generic/files/drivers/net/phy/swconfig.c | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/target/linux/generic/files/drivers/net/phy/swconfig.c b/target/linux/generic/files/drivers/net/phy/swconfig.c index e7da45d0f7400..6bd5023f27e0f 100644 --- a/target/linux/generic/files/drivers/net/phy/swconfig.c +++ b/target/linux/generic/files/drivers/net/phy/swconfig.c @@ -1260,15 +1260,20 @@ int switch_generic_set_link(struct switch_dev *dev, int port, struct switch_port_link *link) { - if (WARN_ON(!dev->ops->phy_write16)) + /* PHY reset can take up to 0.5s according to spec */ + int timeout = 500; + u16 bmcr = BMCR_RESET; + + if (WARN_ON(!dev->ops->phy_write16) || + WARN_ON(!dev->ops->phy_read16)) return -ENOTSUPP; /* Generic implementation */ if (link->aneg) { dev->ops->phy_write16(dev, port, MII_BMCR, 0x0000); - dev->ops->phy_write16(dev, port, MII_BMCR, BMCR_ANENABLE | BMCR_ANRESTART); + dev->ops->phy_write16(dev, port, MII_BMCR, BMCR_RESET | + BMCR_ANENABLE | BMCR_ANRESTART); } else { - u16 bmcr = 0; if (link->duplex) bmcr |= BMCR_FULLDPLX; @@ -1289,6 +1294,19 @@ switch_generic_set_link(struct switch_dev *dev, int port, dev->ops->phy_write16(dev, port, MII_BMCR, bmcr); } + /* Poll until reset bit get cleared */ + do{ + usleep_range(1000, 2000); + dev->ops->phy_read16(dev, port, MII_BMCR, &bmcr); + timeout--; + } while (timeout > 0 || bmcr & BMCR_RESET); + + if (!timeout) { + pr_warn("switch: %s: Port %d PHY failed to reset\n", + dev->name, port); + return -ENAVAIL; + } + return 0; } EXPORT_SYMBOL_GPL(switch_generic_set_link);