net: dsa: microchip: implement PHY loopback configuration for KSZ8794 and KSZ8873
authorOleksij Rempel <o.rempel@pengutronix.de>
Wed, 24 Jan 2024 12:33:14 +0000 (13:33 +0100)
committerJakub Kicinski <kuba@kernel.org>
Sat, 27 Jan 2024 05:25:25 +0000 (21:25 -0800)
Correct the PHY loopback bit handling in the ksz8_w_phy_bmcr and
ksz8_r_phy_bmcr functions for KSZ8794 and KSZ8873 variants in the ksz8795
driver. Previously, the code erroneously used Bit 7 of port register 0xD
for both chip variants, which is actually for LED configuration. This
update ensures the correct registers and bits are used for the PHY
loopback feature:

- For KSZ8794: Use 0xF / Bit 7.
- For KSZ8873: Use 0xD / Bit 0.

The lack of loopback support was seen on KSZ8873 system by using
"ethtool -t lanX". After this patch, the ethtool selftest will work,
but only if port is not part of a bridge.

Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
Reviewed-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Acked-by: Arun Ramadoss <arun.ramadoss@microchip.com>
Link: https://lore.kernel.org/r/20240124123314.734815-4-o.rempel@pengutronix.de
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
drivers/net/dsa/microchip/ksz8795.c
drivers/net/dsa/microchip/ksz8795_reg.h

index 51e0194453df36d8c1b9457666e1e624362d4552..50351cef6ca5950f96523b3845fa40dcf55d0655 100644 (file)
@@ -632,6 +632,57 @@ static void ksz8_w_vlan_table(struct ksz_device *dev, u16 vid, u16 vlan)
        ksz8_w_table(dev, TABLE_VLAN, addr, buf);
 }
 
+/**
+ * ksz879x_get_loopback - KSZ879x specific function to get loopback
+ *                        configuration status for a specific port
+ * @dev: Pointer to the device structure
+ * @port: Port number to query
+ * @val: Pointer to store the result
+ *
+ * This function reads the SMI registers to determine whether loopback mode
+ * is enabled for a specific port.
+ *
+ * Return: 0 on success, error code on failure.
+ */
+static int ksz879x_get_loopback(struct ksz_device *dev, u16 port,
+                               u16 *val)
+{
+       u8 stat3;
+       int ret;
+
+       ret = ksz_pread8(dev, port, REG_PORT_STATUS_3, &stat3);
+       if (ret)
+               return ret;
+
+       if (stat3 & PORT_PHY_LOOPBACK)
+               *val |= BMCR_LOOPBACK;
+
+       return 0;
+}
+
+/**
+ * ksz879x_set_loopback - KSZ879x specific function  to set loopback mode for
+ *                       a specific port
+ * @dev: Pointer to the device structure.
+ * @port: Port number to modify.
+ * @val: Value indicating whether to enable or disable loopback mode.
+ *
+ * This function translates loopback bit of the BMCR register into the
+ * corresponding hardware register bit value and writes it to the SMI interface.
+ *
+ * Return: 0 on success, error code on failure.
+ */
+static int ksz879x_set_loopback(struct ksz_device *dev, u16 port, u16 val)
+{
+       u8 stat3 = 0;
+
+       if (val & BMCR_LOOPBACK)
+               stat3 |= PORT_PHY_LOOPBACK;
+
+       return ksz_prmw8(dev, port, REG_PORT_STATUS_3, PORT_PHY_LOOPBACK,
+                        stat3);
+}
+
 /**
  * ksz8_r_phy_ctrl - Translates and reads from the SMI interface to a MIIM PHY
  *                  Control register (Reg. 31).
@@ -731,16 +782,20 @@ static int ksz8_r_phy_bmcr(struct ksz_device *dev, u16 port, u16 *val)
        if (ret)
                return ret;
 
-       if (restart & PORT_PHY_LOOPBACK)
-               *val |= BMCR_LOOPBACK;
-
        if (ctrl & PORT_FORCE_100_MBIT)
                *val |= BMCR_SPEED100;
 
        if (ksz_is_ksz88x3(dev)) {
+               if (restart & KSZ8873_PORT_PHY_LOOPBACK)
+                       *val |= BMCR_LOOPBACK;
+
                if ((ctrl & PORT_AUTO_NEG_ENABLE))
                        *val |= BMCR_ANENABLE;
        } else {
+               ret = ksz879x_get_loopback(dev, port, val);
+               if (ret)
+                       return ret;
+
                if (!(ctrl & PORT_AUTO_NEG_DISABLE))
                        *val |= BMCR_ANENABLE;
        }
@@ -1001,8 +1056,7 @@ static int ksz8_w_phy_bmcr(struct ksz_device *dev, u16 port, u16 val)
 
        restart = 0;
        restart_mask = PORT_LED_OFF | PORT_TX_DISABLE | PORT_AUTO_NEG_RESTART |
-               PORT_POWER_DOWN | PORT_AUTO_MDIX_DISABLE | PORT_FORCE_MDIX |
-               PORT_PHY_LOOPBACK;
+               PORT_POWER_DOWN | PORT_AUTO_MDIX_DISABLE | PORT_FORCE_MDIX;
 
        if (val & KSZ886X_BMCR_DISABLE_LED)
                restart |= PORT_LED_OFF;
@@ -1022,8 +1076,16 @@ static int ksz8_w_phy_bmcr(struct ksz_device *dev, u16 port, u16 val)
        if (val & KSZ886X_BMCR_FORCE_MDI)
                restart |= PORT_FORCE_MDIX;
 
-       if (val & BMCR_LOOPBACK)
-               restart |= PORT_PHY_LOOPBACK;
+       if (ksz_is_ksz88x3(dev)) {
+               restart_mask |= KSZ8873_PORT_PHY_LOOPBACK;
+
+               if (val & BMCR_LOOPBACK)
+                       restart |= KSZ8873_PORT_PHY_LOOPBACK;
+       } else {
+               ret = ksz879x_set_loopback(dev, port, val);
+               if (ret)
+                       return ret;
+       }
 
        return ksz_prmw8(dev, port, regs[P_NEG_RESTART_CTRL], restart_mask,
                         restart);
index beca974e0171c7a305d06d32a5152ef46f8e70c3..7c9341ef73b030c9e285221fc489e5218dc88d0d 100644 (file)
 #define PORT_AUTO_MDIX_DISABLE         BIT(2)
 #define PORT_FORCE_MDIX                        BIT(1)
 #define PORT_MAC_LOOPBACK              BIT(0)
+#define KSZ8873_PORT_PHY_LOOPBACK      BIT(0)
 
 #define REG_PORT_1_STATUS_2            0x1E
 #define REG_PORT_2_STATUS_2            0x2E