Skip to content

Commit

Permalink
Merge branch 'ksz886x-cable-test'
Browse files Browse the repository at this point in the history
Oleksij Rempel says:

====================
provide cable test support for the ksz886x switch

changes v5:
- drop resume() patch
- add Reviewed-by tags.
- rework dsa_slave_phy_connect() patch

changes v4:
- use fallthrough;
- use EOPNOTSUPP instead of ENOTSUPP
- drop flags variable in dsa_slave_phy_connect patch
- extend description for the "net: phy: micrel: apply resume errat"
  patch
- fix "use consistent alignments" patch

changes v3:
- remove RFC tag

changes v2:
- use generic MII_* defines where possible
- rework phylink validate
- remove phylink get state function
- reorder cabletest patches to make PHY flag patch in the right order
- fix MDI-X detection

This patches provide support for cable testing on the ksz886x switches.
Since it has one special port, we needed to add phylink with validation
and extra quirk for the PHY to signal, that one port will not provide
valid cable testing reports.
====================

Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
David S. Miller committed Jun 14, 2021
2 parents ffa85b7 + 49011e0 commit 645a7fe
Show file tree
Hide file tree
Showing 6 changed files with 573 additions and 237 deletions.
214 changes: 155 additions & 59 deletions drivers/net/dsa/microchip/ksz8795.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* Tristram Ha <Tristram.Ha@microchip.com>
*/

#include <linux/bitfield.h>
#include <linux/delay.h>
#include <linux/export.h>
#include <linux/gpio.h>
Expand All @@ -15,8 +16,10 @@
#include <linux/phy.h>
#include <linux/etherdevice.h>
#include <linux/if_bridge.h>
#include <linux/micrel_phy.h>
#include <net/dsa.h>
#include <net/switchdev.h>
#include <linux/phylink.h>

#include "ksz_common.h"
#include "ksz8795_reg.h"
Expand Down Expand Up @@ -727,92 +730,114 @@ static void ksz8_r_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 *val)
u8 restart, speed, ctrl, link;
const u8 *regs = ksz8->regs;
int processed = true;
u8 val1, val2;
u16 data = 0;
u8 p = phy;

switch (reg) {
case PHY_REG_CTRL:
case MII_BMCR:
ksz_pread8(dev, p, regs[P_NEG_RESTART_CTRL], &restart);
ksz_pread8(dev, p, regs[P_SPEED_STATUS], &speed);
ksz_pread8(dev, p, regs[P_FORCE_CTRL], &ctrl);
if (restart & PORT_PHY_LOOPBACK)
data |= PHY_LOOPBACK;
data |= BMCR_LOOPBACK;
if (ctrl & PORT_FORCE_100_MBIT)
data |= PHY_SPEED_100MBIT;
data |= BMCR_SPEED100;
if (ksz_is_ksz88x3(dev)) {
if ((ctrl & PORT_AUTO_NEG_ENABLE))
data |= PHY_AUTO_NEG_ENABLE;
data |= BMCR_ANENABLE;
} else {
if (!(ctrl & PORT_AUTO_NEG_DISABLE))
data |= PHY_AUTO_NEG_ENABLE;
data |= BMCR_ANENABLE;
}
if (restart & PORT_POWER_DOWN)
data |= PHY_POWER_DOWN;
data |= BMCR_PDOWN;
if (restart & PORT_AUTO_NEG_RESTART)
data |= PHY_AUTO_NEG_RESTART;
data |= BMCR_ANRESTART;
if (ctrl & PORT_FORCE_FULL_DUPLEX)
data |= PHY_FULL_DUPLEX;
data |= BMCR_FULLDPLX;
if (speed & PORT_HP_MDIX)
data |= PHY_HP_MDIX;
data |= KSZ886X_BMCR_HP_MDIX;
if (restart & PORT_FORCE_MDIX)
data |= PHY_FORCE_MDIX;
data |= KSZ886X_BMCR_FORCE_MDI;
if (restart & PORT_AUTO_MDIX_DISABLE)
data |= PHY_AUTO_MDIX_DISABLE;
data |= KSZ886X_BMCR_DISABLE_AUTO_MDIX;
if (restart & PORT_TX_DISABLE)
data |= PHY_TRANSMIT_DISABLE;
data |= KSZ886X_BMCR_DISABLE_TRANSMIT;
if (restart & PORT_LED_OFF)
data |= PHY_LED_DISABLE;
data |= KSZ886X_BMCR_DISABLE_LED;
break;
case PHY_REG_STATUS:
case MII_BMSR:
ksz_pread8(dev, p, regs[P_LINK_STATUS], &link);
data = PHY_100BTX_FD_CAPABLE |
PHY_100BTX_CAPABLE |
PHY_10BT_FD_CAPABLE |
PHY_10BT_CAPABLE |
PHY_AUTO_NEG_CAPABLE;
data = BMSR_100FULL |
BMSR_100HALF |
BMSR_10FULL |
BMSR_10HALF |
BMSR_ANEGCAPABLE;
if (link & PORT_AUTO_NEG_COMPLETE)
data |= PHY_AUTO_NEG_ACKNOWLEDGE;
data |= BMSR_ANEGCOMPLETE;
if (link & PORT_STAT_LINK_GOOD)
data |= PHY_LINK_STATUS;
data |= BMSR_LSTATUS;
break;
case PHY_REG_ID_1:
case MII_PHYSID1:
data = KSZ8795_ID_HI;
break;
case PHY_REG_ID_2:
case MII_PHYSID2:
if (ksz_is_ksz88x3(dev))
data = KSZ8863_ID_LO;
else
data = KSZ8795_ID_LO;
break;
case PHY_REG_AUTO_NEGOTIATION:
case MII_ADVERTISE:
ksz_pread8(dev, p, regs[P_LOCAL_CTRL], &ctrl);
data = PHY_AUTO_NEG_802_3;
data = ADVERTISE_CSMA;
if (ctrl & PORT_AUTO_NEG_SYM_PAUSE)
data |= PHY_AUTO_NEG_SYM_PAUSE;
data |= ADVERTISE_PAUSE_CAP;
if (ctrl & PORT_AUTO_NEG_100BTX_FD)
data |= PHY_AUTO_NEG_100BTX_FD;
data |= ADVERTISE_100FULL;
if (ctrl & PORT_AUTO_NEG_100BTX)
data |= PHY_AUTO_NEG_100BTX;
data |= ADVERTISE_100HALF;
if (ctrl & PORT_AUTO_NEG_10BT_FD)
data |= PHY_AUTO_NEG_10BT_FD;
data |= ADVERTISE_10FULL;
if (ctrl & PORT_AUTO_NEG_10BT)
data |= PHY_AUTO_NEG_10BT;
data |= ADVERTISE_10HALF;
break;
case PHY_REG_REMOTE_CAPABILITY:
case MII_LPA:
ksz_pread8(dev, p, regs[P_REMOTE_STATUS], &link);
data = PHY_AUTO_NEG_802_3;
data = LPA_SLCT;
if (link & PORT_REMOTE_SYM_PAUSE)
data |= PHY_AUTO_NEG_SYM_PAUSE;
data |= LPA_PAUSE_CAP;
if (link & PORT_REMOTE_100BTX_FD)
data |= PHY_AUTO_NEG_100BTX_FD;
data |= LPA_100FULL;
if (link & PORT_REMOTE_100BTX)
data |= PHY_AUTO_NEG_100BTX;
data |= LPA_100HALF;
if (link & PORT_REMOTE_10BT_FD)
data |= PHY_AUTO_NEG_10BT_FD;
data |= LPA_10FULL;
if (link & PORT_REMOTE_10BT)
data |= PHY_AUTO_NEG_10BT;
if (data & ~PHY_AUTO_NEG_802_3)
data |= PHY_REMOTE_ACKNOWLEDGE_NOT;
data |= LPA_10HALF;
if (data & ~LPA_SLCT)
data |= LPA_LPACK;
break;
case PHY_REG_LINK_MD:
ksz_pread8(dev, p, REG_PORT_LINK_MD_CTRL, &val1);
ksz_pread8(dev, p, REG_PORT_LINK_MD_RESULT, &val2);
if (val1 & PORT_START_CABLE_DIAG)
data |= PHY_START_CABLE_DIAG;

if (val1 & PORT_CABLE_10M_SHORT)
data |= PHY_CABLE_10M_SHORT;

data |= FIELD_PREP(PHY_CABLE_DIAG_RESULT_M,
FIELD_GET(PORT_CABLE_DIAG_RESULT_M, val1));

data |= FIELD_PREP(PHY_CABLE_FAULT_COUNTER_M,
(FIELD_GET(PORT_CABLE_FAULT_COUNTER_H, val1) << 8) |
FIELD_GET(PORT_CABLE_FAULT_COUNTER_L, val2));
break;
case PHY_REG_PHY_CTRL:
ksz_pread8(dev, p, regs[P_LINK_STATUS], &link);
if (link & PORT_MDIX_STATUS)
data |= KSZ886X_CTRL_MDIX_STAT;
break;
default:
processed = false;
Expand All @@ -830,14 +855,14 @@ static void ksz8_w_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 val)
u8 p = phy;

switch (reg) {
case PHY_REG_CTRL:
case MII_BMCR:

/* Do not support PHY reset function. */
if (val & PHY_RESET)
if (val & BMCR_RESET)
break;
ksz_pread8(dev, p, regs[P_SPEED_STATUS], &speed);
data = speed;
if (val & PHY_HP_MDIX)
if (val & KSZ886X_BMCR_HP_MDIX)
data |= PORT_HP_MDIX;
else
data &= ~PORT_HP_MDIX;
Expand All @@ -846,12 +871,12 @@ static void ksz8_w_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 val)
ksz_pread8(dev, p, regs[P_FORCE_CTRL], &ctrl);
data = ctrl;
if (ksz_is_ksz88x3(dev)) {
if ((val & PHY_AUTO_NEG_ENABLE))
if ((val & BMCR_ANENABLE))
data |= PORT_AUTO_NEG_ENABLE;
else
data &= ~PORT_AUTO_NEG_ENABLE;
} else {
if (!(val & PHY_AUTO_NEG_ENABLE))
if (!(val & BMCR_ANENABLE))
data |= PORT_AUTO_NEG_DISABLE;
else
data &= ~PORT_AUTO_NEG_DISABLE;
Expand All @@ -861,70 +886,74 @@ static void ksz8_w_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 val)
data |= PORT_AUTO_NEG_DISABLE;
}

if (val & PHY_SPEED_100MBIT)
if (val & BMCR_SPEED100)
data |= PORT_FORCE_100_MBIT;
else
data &= ~PORT_FORCE_100_MBIT;
if (val & PHY_FULL_DUPLEX)
if (val & BMCR_FULLDPLX)
data |= PORT_FORCE_FULL_DUPLEX;
else
data &= ~PORT_FORCE_FULL_DUPLEX;
if (data != ctrl)
ksz_pwrite8(dev, p, regs[P_FORCE_CTRL], data);
ksz_pread8(dev, p, regs[P_NEG_RESTART_CTRL], &restart);
data = restart;
if (val & PHY_LED_DISABLE)
if (val & KSZ886X_BMCR_DISABLE_LED)
data |= PORT_LED_OFF;
else
data &= ~PORT_LED_OFF;
if (val & PHY_TRANSMIT_DISABLE)
if (val & KSZ886X_BMCR_DISABLE_TRANSMIT)
data |= PORT_TX_DISABLE;
else
data &= ~PORT_TX_DISABLE;
if (val & PHY_AUTO_NEG_RESTART)
if (val & BMCR_ANRESTART)
data |= PORT_AUTO_NEG_RESTART;
else
data &= ~(PORT_AUTO_NEG_RESTART);
if (val & PHY_POWER_DOWN)
if (val & BMCR_PDOWN)
data |= PORT_POWER_DOWN;
else
data &= ~PORT_POWER_DOWN;
if (val & PHY_AUTO_MDIX_DISABLE)
if (val & KSZ886X_BMCR_DISABLE_AUTO_MDIX)
data |= PORT_AUTO_MDIX_DISABLE;
else
data &= ~PORT_AUTO_MDIX_DISABLE;
if (val & PHY_FORCE_MDIX)
if (val & KSZ886X_BMCR_FORCE_MDI)
data |= PORT_FORCE_MDIX;
else
data &= ~PORT_FORCE_MDIX;
if (val & PHY_LOOPBACK)
if (val & BMCR_LOOPBACK)
data |= PORT_PHY_LOOPBACK;
else
data &= ~PORT_PHY_LOOPBACK;
if (data != restart)
ksz_pwrite8(dev, p, regs[P_NEG_RESTART_CTRL], data);
break;
case PHY_REG_AUTO_NEGOTIATION:
case MII_ADVERTISE:
ksz_pread8(dev, p, regs[P_LOCAL_CTRL], &ctrl);
data = ctrl;
data &= ~(PORT_AUTO_NEG_SYM_PAUSE |
PORT_AUTO_NEG_100BTX_FD |
PORT_AUTO_NEG_100BTX |
PORT_AUTO_NEG_10BT_FD |
PORT_AUTO_NEG_10BT);
if (val & PHY_AUTO_NEG_SYM_PAUSE)
if (val & ADVERTISE_PAUSE_CAP)
data |= PORT_AUTO_NEG_SYM_PAUSE;
if (val & PHY_AUTO_NEG_100BTX_FD)
if (val & ADVERTISE_100FULL)
data |= PORT_AUTO_NEG_100BTX_FD;
if (val & PHY_AUTO_NEG_100BTX)
if (val & ADVERTISE_100HALF)
data |= PORT_AUTO_NEG_100BTX;
if (val & PHY_AUTO_NEG_10BT_FD)
if (val & ADVERTISE_10FULL)
data |= PORT_AUTO_NEG_10BT_FD;
if (val & PHY_AUTO_NEG_10BT)
if (val & ADVERTISE_10HALF)
data |= PORT_AUTO_NEG_10BT;
if (data != ctrl)
ksz_pwrite8(dev, p, regs[P_LOCAL_CTRL], data);
break;
case PHY_REG_LINK_MD:
if (val & PHY_START_CABLE_DIAG)
ksz_port_cfg(dev, p, REG_PORT_LINK_MD_CTRL, PORT_START_CABLE_DIAG, true);
break;
default:
break;
}
Expand All @@ -941,6 +970,18 @@ static enum dsa_tag_protocol ksz8_get_tag_protocol(struct dsa_switch *ds,
DSA_TAG_PROTO_KSZ9893 : DSA_TAG_PROTO_KSZ8795;
}

static u32 ksz8_sw_get_phy_flags(struct dsa_switch *ds, int port)
{
/* Silicon Errata Sheet (DS80000830A):
* Port 1 does not work with LinkMD Cable-Testing.
* Port 1 does not respond to received PAUSE control frames.
*/
if (!port)
return MICREL_KSZ8_P1_ERRATA;

return 0;
}

static void ksz8_get_strings(struct dsa_switch *ds, int port,
u32 stringset, uint8_t *buf)
{
Expand Down Expand Up @@ -1419,11 +1460,66 @@ static int ksz8_setup(struct dsa_switch *ds)
return 0;
}

static void ksz8_validate(struct dsa_switch *ds, int port,
unsigned long *supported,
struct phylink_link_state *state)
{
__ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = { 0, };
struct ksz_device *dev = ds->priv;

if (port == dev->cpu_port) {
if (state->interface != PHY_INTERFACE_MODE_RMII &&
state->interface != PHY_INTERFACE_MODE_MII &&
state->interface != PHY_INTERFACE_MODE_NA)
goto unsupported;
} else {
if (state->interface != PHY_INTERFACE_MODE_INTERNAL &&
state->interface != PHY_INTERFACE_MODE_NA)
goto unsupported;
}

/* Allow all the expected bits */
phylink_set_port_modes(mask);
phylink_set(mask, Autoneg);

/* Silicon Errata Sheet (DS80000830A):
* "Port 1 does not respond to received flow control PAUSE frames"
* So, disable Pause support on "Port 1" (port == 0) for all ksz88x3
* switches.
*/
if (!ksz_is_ksz88x3(dev) || port)
phylink_set(mask, Pause);

/* Asym pause is not supported on KSZ8863 and KSZ8873 */
if (!ksz_is_ksz88x3(dev))
phylink_set(mask, Asym_Pause);

/* 10M and 100M are only supported */
phylink_set(mask, 10baseT_Half);
phylink_set(mask, 10baseT_Full);
phylink_set(mask, 100baseT_Half);
phylink_set(mask, 100baseT_Full);

bitmap_and(supported, supported, mask,
__ETHTOOL_LINK_MODE_MASK_NBITS);
bitmap_and(state->advertising, state->advertising, mask,
__ETHTOOL_LINK_MODE_MASK_NBITS);

return;

unsupported:
bitmap_zero(supported, __ETHTOOL_LINK_MODE_MASK_NBITS);
dev_err(ds->dev, "Unsupported interface: %s, port: %d\n",
phy_modes(state->interface), port);
}

static const struct dsa_switch_ops ksz8_switch_ops = {
.get_tag_protocol = ksz8_get_tag_protocol,
.get_phy_flags = ksz8_sw_get_phy_flags,
.setup = ksz8_setup,
.phy_read = ksz_phy_read16,
.phy_write = ksz_phy_write16,
.phylink_validate = ksz8_validate,
.phylink_mac_link_down = ksz_mac_link_down,
.port_enable = ksz_enable_port,
.get_strings = ksz8_get_strings,
Expand Down
Loading

0 comments on commit 645a7fe

Please sign in to comment.