net: dsa: mv88e6xxx: support in-band signalling on SGMII ports with external PHYs
authorHeiner Kallweit <hkallweit1@gmail.com>
Fri, 1 Mar 2019 19:41:00 +0000 (20:41 +0100)
committerDavid S. Miller <davem@davemloft.net>
Mon, 4 Mar 2019 18:52:39 +0000 (10:52 -0800)
If an external PHY is connected via SGMII and uses in-band signalling
then the auto-negotiated values aren't propagated to the port,
resulting in a broken link. See discussion in [0]. This patch adds
this propagation. We need to call mv88e6xxx_port_setup_mac(),
therefore export it from chip.c.

Successfully tested on a ZII DTU with 88E6390 switch and an
Aquantia AQCS109 PHY connected via SGMII to port 9.

[0] https://marc.info/?t=155130287200001&r=1&w=2

Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/dsa/mv88e6xxx/chip.c
drivers/net/dsa/mv88e6xxx/chip.h
drivers/net/dsa/mv88e6xxx/serdes.c
drivers/net/dsa/mv88e6xxx/serdes.h

index e4ad16b2dc382df64cb6095cc8e920bff2d41b41..cb2b9c3bc08c6001002f98abf575491380ac6ee5 100644 (file)
@@ -549,9 +549,9 @@ int mv88e6xxx_update(struct mv88e6xxx_chip *chip, int addr, int reg, u16 update)
        return mv88e6xxx_write(chip, addr, reg, val);
 }
 
-static int mv88e6xxx_port_setup_mac(struct mv88e6xxx_chip *chip, int port,
-                                   int link, int speed, int duplex, int pause,
-                                   phy_interface_t mode)
+int mv88e6xxx_port_setup_mac(struct mv88e6xxx_chip *chip, int port, int link,
+                            int speed, int duplex, int pause,
+                            phy_interface_t mode)
 {
        int err;
 
index 546651d8c3e1fd5e395d2da1527c7a04f02a00f3..adcf6077989555d023b08e5b87a9e1a6c562a8ab 100644 (file)
@@ -579,6 +579,9 @@ int mv88e6xxx_write(struct mv88e6xxx_chip *chip, int addr, int reg, u16 val);
 int mv88e6xxx_update(struct mv88e6xxx_chip *chip, int addr, int reg,
                     u16 update);
 int mv88e6xxx_wait(struct mv88e6xxx_chip *chip, int addr, int reg, u16 mask);
+int mv88e6xxx_port_setup_mac(struct mv88e6xxx_chip *chip, int port, int link,
+                            int speed, int duplex, int pause,
+                            phy_interface_t mode);
 struct mii_bus *mv88e6xxx_default_mdio_bus(struct mv88e6xxx_chip *chip);
 
 #endif /* _MV88E6XXX_CHIP_H */
index 1bfc5ff8d81dc51a9b9c03551615126b0750bc77..6a5de1b72f6c810e451ab8ae7ecee13645e719cf 100644 (file)
@@ -510,21 +510,48 @@ static void mv88e6390_serdes_irq_link_sgmii(struct mv88e6xxx_chip *chip,
                                            int port, int lane)
 {
        struct dsa_switch *ds = chip->ds;
+       int duplex = DUPLEX_UNKNOWN;
+       int speed = SPEED_UNKNOWN;
+       int link, err;
        u16 status;
-       bool up;
 
-       mv88e6390_serdes_read(chip, lane, MDIO_MMD_PHYXS,
-                             MV88E6390_SGMII_STATUS, &status);
+       err = mv88e6390_serdes_read(chip, lane, MDIO_MMD_PHYXS,
+                                   MV88E6390_SGMII_PHY_STATUS, &status);
+       if (err) {
+               dev_err(chip->dev, "can't read SGMII PHY status: %d\n", err);
+               return;
+       }
 
-       /* Status must be read twice in order to give the current link
-        * status. Otherwise the change in link status since the last
-        * read of the register is returned.
-        */
-       mv88e6390_serdes_read(chip, lane, MDIO_MMD_PHYXS,
-                             MV88E6390_SGMII_STATUS, &status);
-       up = status & MV88E6390_SGMII_STATUS_LINK;
+       link = status & MV88E6390_SGMII_PHY_STATUS_LINK ?
+              LINK_FORCED_UP : LINK_FORCED_DOWN;
+
+       if (status & MV88E6390_SGMII_PHY_STATUS_SPD_DPL_VALID) {
+               duplex = status & MV88E6390_SGMII_PHY_STATUS_DUPLEX_FULL ?
+                        DUPLEX_FULL : DUPLEX_HALF;
+
+               switch (status & MV88E6390_SGMII_PHY_STATUS_SPEED_MASK) {
+               case MV88E6390_SGMII_PHY_STATUS_SPEED_1000:
+                       speed = SPEED_1000;
+                       break;
+               case MV88E6390_SGMII_PHY_STATUS_SPEED_100:
+                       speed = SPEED_100;
+                       break;
+               case MV88E6390_SGMII_PHY_STATUS_SPEED_10:
+                       speed = SPEED_10;
+                       break;
+               default:
+                       dev_err(chip->dev, "invalid PHY speed\n");
+                       return;
+               }
+       }
 
-       dsa_port_phylink_mac_change(ds, port, up);
+       err = mv88e6xxx_port_setup_mac(chip, port, link, speed, duplex,
+                                      PAUSE_OFF, PHY_INTERFACE_MODE_NA);
+       if (err)
+               dev_err(chip->dev, "can't propagate PHY settings to MAC: %d\n",
+                       err);
+       else
+               dsa_port_phylink_mac_change(ds, port, link == LINK_FORCED_UP);
 }
 
 static int mv88e6390_serdes_irq_enable_sgmii(struct mv88e6xxx_chip *chip,
index 573dce8b1eb4a5380d87254cbcdd19abbd893439..c2e7eedfa9b9f8290b2bedd4f19104f44c8acfd8 100644 (file)
 #define MV88E6390_SGMII_INT_SYMBOL_ERROR       BIT(8)
 #define MV88E6390_SGMII_INT_FALSE_CARRIER      BIT(7)
 #define MV88E6390_SGMII_INT_STATUS     0xa002
+#define MV88E6390_SGMII_PHY_STATUS     0xa003
+#define MV88E6390_SGMII_PHY_STATUS_SPEED_MASK  GENMASK(15, 14)
+#define MV88E6390_SGMII_PHY_STATUS_SPEED_1000  0x8000
+#define MV88E6390_SGMII_PHY_STATUS_SPEED_100   0x4000
+#define MV88E6390_SGMII_PHY_STATUS_SPEED_10    0x0000
+#define MV88E6390_SGMII_PHY_STATUS_DUPLEX_FULL BIT(13)
+#define MV88E6390_SGMII_PHY_STATUS_SPD_DPL_VALID BIT(11)
+#define MV88E6390_SGMII_PHY_STATUS_LINK                BIT(10)
 
 int mv88e6390x_serdes_get_lane(struct mv88e6xxx_chip *chip, int port);
 int mv88e6341_serdes_power(struct mv88e6xxx_chip *chip, int port, bool on);