diff options
Diffstat (limited to 'drivers/net/pcs/pcs-xpcs.c')
-rw-r--r-- | drivers/net/pcs/pcs-xpcs.c | 659 |
1 files changed, 439 insertions, 220 deletions
diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c index 944ba105cac1..63fda3fc40aa 100644 --- a/drivers/net/pcs/pcs-xpcs.c +++ b/drivers/net/pcs/pcs-xpcs.c @@ -11,80 +11,10 @@ #include <linux/mdio.h> #include <linux/phylink.h> #include <linux/workqueue.h> +#include "pcs-xpcs.h" -#define SYNOPSYS_XPCS_USXGMII_ID 0x7996ced0 -#define SYNOPSYS_XPCS_10GKR_ID 0x7996ced0 -#define SYNOPSYS_XPCS_XLGMII_ID 0x7996ced0 -#define SYNOPSYS_XPCS_SGMII_ID 0x7996ced0 -#define SYNOPSYS_XPCS_MASK 0xffffffff - -/* Vendor regs access */ -#define DW_VENDOR BIT(15) - -/* VR_XS_PCS */ -#define DW_USXGMII_RST BIT(10) -#define DW_USXGMII_EN BIT(9) -#define DW_VR_XS_PCS_DIG_STS 0x0010 -#define DW_RXFIFO_ERR GENMASK(6, 5) - -/* SR_MII */ -#define DW_USXGMII_FULL BIT(8) -#define DW_USXGMII_SS_MASK (BIT(13) | BIT(6) | BIT(5)) -#define DW_USXGMII_10000 (BIT(13) | BIT(6)) -#define DW_USXGMII_5000 (BIT(13) | BIT(5)) -#define DW_USXGMII_2500 (BIT(5)) -#define DW_USXGMII_1000 (BIT(6)) -#define DW_USXGMII_100 (BIT(13)) -#define DW_USXGMII_10 (0) - -/* SR_AN */ -#define DW_SR_AN_ADV1 0x10 -#define DW_SR_AN_ADV2 0x11 -#define DW_SR_AN_ADV3 0x12 -#define DW_SR_AN_LP_ABL1 0x13 -#define DW_SR_AN_LP_ABL2 0x14 -#define DW_SR_AN_LP_ABL3 0x15 - -/* Clause 73 Defines */ -/* AN_LP_ABL1 */ -#define DW_C73_PAUSE BIT(10) -#define DW_C73_ASYM_PAUSE BIT(11) -#define DW_C73_AN_ADV_SF 0x1 -/* AN_LP_ABL2 */ -#define DW_C73_1000KX BIT(5) -#define DW_C73_10000KX4 BIT(6) -#define DW_C73_10000KR BIT(7) -/* AN_LP_ABL3 */ -#define DW_C73_2500KX BIT(0) -#define DW_C73_5000KR BIT(1) - -/* Clause 37 Defines */ -/* VR MII MMD registers offsets */ -#define DW_VR_MII_DIG_CTRL1 0x8000 -#define DW_VR_MII_AN_CTRL 0x8001 -#define DW_VR_MII_AN_INTR_STS 0x8002 - -/* VR_MII_DIG_CTRL1 */ -#define DW_VR_MII_DIG_CTRL1_MAC_AUTO_SW BIT(9) - -/* VR_MII_AN_CTRL */ -#define DW_VR_MII_AN_CTRL_TX_CONFIG_SHIFT 3 -#define DW_VR_MII_TX_CONFIG_MASK BIT(3) -#define DW_VR_MII_TX_CONFIG_PHY_SIDE_SGMII 0x1 -#define DW_VR_MII_TX_CONFIG_MAC_SIDE_SGMII 0x0 -#define DW_VR_MII_AN_CTRL_PCS_MODE_SHIFT 1 -#define DW_VR_MII_PCS_MODE_MASK GENMASK(2, 1) -#define DW_VR_MII_PCS_MODE_C37_1000BASEX 0x0 -#define DW_VR_MII_PCS_MODE_C37_SGMII 0x2 - -/* VR_MII_AN_INTR_STS */ -#define DW_VR_MII_AN_STS_C37_ANSGM_FD BIT(1) -#define DW_VR_MII_AN_STS_C37_ANSGM_SP_SHIFT 2 -#define DW_VR_MII_AN_STS_C37_ANSGM_SP GENMASK(3, 2) -#define DW_VR_MII_C37_ANSGM_SP_10 0x0 -#define DW_VR_MII_C37_ANSGM_SP_100 0x1 -#define DW_VR_MII_C37_ANSGM_SP_1000 0x2 -#define DW_VR_MII_C37_ANSGM_SP_LNKSTS BIT(4) +#define phylink_pcs_to_xpcs(pl_pcs) \ + container_of((pl_pcs), struct dw_xpcs, pcs) static const int xpcs_usxgmii_features[] = { ETHTOOL_LINK_MODE_Pause_BIT, @@ -144,96 +74,141 @@ static const int xpcs_sgmii_features[] = { __ETHTOOL_LINK_MODE_MASK_NBITS, }; +static const int xpcs_2500basex_features[] = { + ETHTOOL_LINK_MODE_Asym_Pause_BIT, + ETHTOOL_LINK_MODE_Autoneg_BIT, + ETHTOOL_LINK_MODE_2500baseX_Full_BIT, + ETHTOOL_LINK_MODE_2500baseT_Full_BIT, + __ETHTOOL_LINK_MODE_MASK_NBITS, +}; + static const phy_interface_t xpcs_usxgmii_interfaces[] = { PHY_INTERFACE_MODE_USXGMII, - PHY_INTERFACE_MODE_MAX, }; static const phy_interface_t xpcs_10gkr_interfaces[] = { PHY_INTERFACE_MODE_10GKR, - PHY_INTERFACE_MODE_MAX, }; static const phy_interface_t xpcs_xlgmii_interfaces[] = { PHY_INTERFACE_MODE_XLGMII, - PHY_INTERFACE_MODE_MAX, }; static const phy_interface_t xpcs_sgmii_interfaces[] = { PHY_INTERFACE_MODE_SGMII, +}; + +static const phy_interface_t xpcs_2500basex_interfaces[] = { + PHY_INTERFACE_MODE_2500BASEX, PHY_INTERFACE_MODE_MAX, }; -static struct xpcs_id { - u32 id; - u32 mask; +enum { + DW_XPCS_USXGMII, + DW_XPCS_10GKR, + DW_XPCS_XLGMII, + DW_XPCS_SGMII, + DW_XPCS_2500BASEX, + DW_XPCS_INTERFACE_MAX, +}; + +struct xpcs_compat { const int *supported; const phy_interface_t *interface; + int num_interfaces; int an_mode; -} xpcs_id_list[] = { - { - .id = SYNOPSYS_XPCS_USXGMII_ID, - .mask = SYNOPSYS_XPCS_MASK, - .supported = xpcs_usxgmii_features, - .interface = xpcs_usxgmii_interfaces, - .an_mode = DW_AN_C73, - }, { - .id = SYNOPSYS_XPCS_10GKR_ID, - .mask = SYNOPSYS_XPCS_MASK, - .supported = xpcs_10gkr_features, - .interface = xpcs_10gkr_interfaces, - .an_mode = DW_AN_C73, - }, { - .id = SYNOPSYS_XPCS_XLGMII_ID, - .mask = SYNOPSYS_XPCS_MASK, - .supported = xpcs_xlgmii_features, - .interface = xpcs_xlgmii_interfaces, - .an_mode = DW_AN_C73, - }, { - .id = SYNOPSYS_XPCS_SGMII_ID, - .mask = SYNOPSYS_XPCS_MASK, - .supported = xpcs_sgmii_features, - .interface = xpcs_sgmii_interfaces, - .an_mode = DW_AN_C37_SGMII, - }, + int (*pma_config)(struct dw_xpcs *xpcs); }; -static int xpcs_read(struct mdio_xpcs_args *xpcs, int dev, u32 reg) +struct xpcs_id { + u32 id; + u32 mask; + const struct xpcs_compat *compat; +}; + +static const struct xpcs_compat *xpcs_find_compat(const struct xpcs_id *id, + phy_interface_t interface) +{ + int i, j; + + for (i = 0; i < DW_XPCS_INTERFACE_MAX; i++) { + const struct xpcs_compat *compat = &id->compat[i]; + + for (j = 0; j < compat->num_interfaces; j++) + if (compat->interface[j] == interface) + return compat; + } + + return NULL; +} + +int xpcs_get_an_mode(struct dw_xpcs *xpcs, phy_interface_t interface) +{ + const struct xpcs_compat *compat; + + compat = xpcs_find_compat(xpcs->id, interface); + if (!compat) + return -ENODEV; + + return compat->an_mode; +} +EXPORT_SYMBOL_GPL(xpcs_get_an_mode); + +static bool __xpcs_linkmode_supported(const struct xpcs_compat *compat, + enum ethtool_link_mode_bit_indices linkmode) { - u32 reg_addr = MII_ADDR_C45 | dev << 16 | reg; + int i; - return mdiobus_read(xpcs->bus, xpcs->addr, reg_addr); + for (i = 0; compat->supported[i] != __ETHTOOL_LINK_MODE_MASK_NBITS; i++) + if (compat->supported[i] == linkmode) + return true; + + return false; } -static int xpcs_write(struct mdio_xpcs_args *xpcs, int dev, u32 reg, u16 val) +#define xpcs_linkmode_supported(compat, mode) \ + __xpcs_linkmode_supported(compat, ETHTOOL_LINK_MODE_ ## mode ## _BIT) + +int xpcs_read(struct dw_xpcs *xpcs, int dev, u32 reg) { - u32 reg_addr = MII_ADDR_C45 | dev << 16 | reg; + u32 reg_addr = mdiobus_c45_addr(dev, reg); + struct mii_bus *bus = xpcs->mdiodev->bus; + int addr = xpcs->mdiodev->addr; - return mdiobus_write(xpcs->bus, xpcs->addr, reg_addr, val); + return mdiobus_read(bus, addr, reg_addr); } -static int xpcs_read_vendor(struct mdio_xpcs_args *xpcs, int dev, u32 reg) +int xpcs_write(struct dw_xpcs *xpcs, int dev, u32 reg, u16 val) +{ + u32 reg_addr = mdiobus_c45_addr(dev, reg); + struct mii_bus *bus = xpcs->mdiodev->bus; + int addr = xpcs->mdiodev->addr; + + return mdiobus_write(bus, addr, reg_addr, val); +} + +static int xpcs_read_vendor(struct dw_xpcs *xpcs, int dev, u32 reg) { return xpcs_read(xpcs, dev, DW_VENDOR | reg); } -static int xpcs_write_vendor(struct mdio_xpcs_args *xpcs, int dev, int reg, +static int xpcs_write_vendor(struct dw_xpcs *xpcs, int dev, int reg, u16 val) { return xpcs_write(xpcs, dev, DW_VENDOR | reg, val); } -static int xpcs_read_vpcs(struct mdio_xpcs_args *xpcs, int reg) +static int xpcs_read_vpcs(struct dw_xpcs *xpcs, int reg) { return xpcs_read_vendor(xpcs, MDIO_MMD_PCS, reg); } -static int xpcs_write_vpcs(struct mdio_xpcs_args *xpcs, int reg, u16 val) +static int xpcs_write_vpcs(struct dw_xpcs *xpcs, int reg, u16 val) { return xpcs_write_vendor(xpcs, MDIO_MMD_PCS, reg, val); } -static int xpcs_poll_reset(struct mdio_xpcs_args *xpcs, int dev) +static int xpcs_poll_reset(struct dw_xpcs *xpcs, int dev) { /* Poll until the reset bit clears (50ms per retry == 0.6 sec) */ unsigned int retries = 12; @@ -249,15 +224,17 @@ static int xpcs_poll_reset(struct mdio_xpcs_args *xpcs, int dev) return (ret & MDIO_CTRL1_RESET) ? -ETIMEDOUT : 0; } -static int xpcs_soft_reset(struct mdio_xpcs_args *xpcs) +static int xpcs_soft_reset(struct dw_xpcs *xpcs, + const struct xpcs_compat *compat) { int ret, dev; - switch (xpcs->an_mode) { + switch (compat->an_mode) { case DW_AN_C73: dev = MDIO_MMD_PCS; break; case DW_AN_C37_SGMII: + case DW_2500BASEX: dev = MDIO_MMD_VEND2; break; default: @@ -274,10 +251,10 @@ static int xpcs_soft_reset(struct mdio_xpcs_args *xpcs) #define xpcs_warn(__xpcs, __state, __args...) \ ({ \ if ((__state)->link) \ - dev_warn(&(__xpcs)->bus->dev, ##__args); \ + dev_warn(&(__xpcs)->mdiodev->dev, ##__args); \ }) -static int xpcs_read_fault_c73(struct mdio_xpcs_args *xpcs, +static int xpcs_read_fault_c73(struct dw_xpcs *xpcs, struct phylink_link_state *state) { int ret; @@ -328,7 +305,7 @@ static int xpcs_read_fault_c73(struct mdio_xpcs_args *xpcs, return 0; } -static int xpcs_read_link_c73(struct mdio_xpcs_args *xpcs, bool an) +static int xpcs_read_link_c73(struct dw_xpcs *xpcs, bool an) { bool link = true; int ret; @@ -368,7 +345,7 @@ static int xpcs_get_max_usxgmii_speed(const unsigned long *supported) return max; } -static int xpcs_config_usxgmii(struct mdio_xpcs_args *xpcs, int speed) +static void xpcs_config_usxgmii(struct dw_xpcs *xpcs, int speed) { int ret, speed_sel; @@ -393,36 +370,44 @@ static int xpcs_config_usxgmii(struct mdio_xpcs_args *xpcs, int speed) break; default: /* Nothing to do here */ - return -EINVAL; + return; } ret = xpcs_read_vpcs(xpcs, MDIO_CTRL1); if (ret < 0) - return ret; + goto out; ret = xpcs_write_vpcs(xpcs, MDIO_CTRL1, ret | DW_USXGMII_EN); if (ret < 0) - return ret; + goto out; ret = xpcs_read(xpcs, MDIO_MMD_VEND2, MDIO_CTRL1); if (ret < 0) - return ret; + goto out; ret &= ~DW_USXGMII_SS_MASK; ret |= speed_sel | DW_USXGMII_FULL; ret = xpcs_write(xpcs, MDIO_MMD_VEND2, MDIO_CTRL1, ret); if (ret < 0) - return ret; + goto out; ret = xpcs_read_vpcs(xpcs, MDIO_CTRL1); if (ret < 0) - return ret; + goto out; - return xpcs_write_vpcs(xpcs, MDIO_CTRL1, ret | DW_USXGMII_RST); + ret = xpcs_write_vpcs(xpcs, MDIO_CTRL1, ret | DW_USXGMII_RST); + if (ret < 0) + goto out; + + return; + +out: + pr_err("%s: XPCS access returned %pe\n", __func__, ERR_PTR(ret)); } -static int _xpcs_config_aneg_c73(struct mdio_xpcs_args *xpcs) +static int _xpcs_config_aneg_c73(struct dw_xpcs *xpcs, + const struct xpcs_compat *compat) { int ret, adv; @@ -434,7 +419,7 @@ static int _xpcs_config_aneg_c73(struct mdio_xpcs_args *xpcs) /* SR_AN_ADV3 */ adv = 0; - if (phylink_test(xpcs->supported, 2500baseX_Full)) + if (xpcs_linkmode_supported(compat, 2500baseX_Full)) adv |= DW_C73_2500KX; /* TODO: 5000baseKR */ @@ -445,11 +430,11 @@ static int _xpcs_config_aneg_c73(struct mdio_xpcs_args *xpcs) /* SR_AN_ADV2 */ adv = 0; - if (phylink_test(xpcs->supported, 1000baseKX_Full)) + if (xpcs_linkmode_supported(compat, 1000baseKX_Full)) adv |= DW_C73_1000KX; - if (phylink_test(xpcs->supported, 10000baseKX4_Full)) + if (xpcs_linkmode_supported(compat, 10000baseKX4_Full)) adv |= DW_C73_10000KX4; - if (phylink_test(xpcs->supported, 10000baseKR_Full)) + if (xpcs_linkmode_supported(compat, 10000baseKR_Full)) adv |= DW_C73_10000KR; ret = xpcs_write(xpcs, MDIO_MMD_AN, DW_SR_AN_ADV2, adv); @@ -458,19 +443,20 @@ static int _xpcs_config_aneg_c73(struct mdio_xpcs_args *xpcs) /* SR_AN_ADV1 */ adv = DW_C73_AN_ADV_SF; - if (phylink_test(xpcs->supported, Pause)) + if (xpcs_linkmode_supported(compat, Pause)) adv |= DW_C73_PAUSE; - if (phylink_test(xpcs->supported, Asym_Pause)) + if (xpcs_linkmode_supported(compat, Asym_Pause)) adv |= DW_C73_ASYM_PAUSE; return xpcs_write(xpcs, MDIO_MMD_AN, DW_SR_AN_ADV1, adv); } -static int xpcs_config_aneg_c73(struct mdio_xpcs_args *xpcs) +static int xpcs_config_aneg_c73(struct dw_xpcs *xpcs, + const struct xpcs_compat *compat) { int ret; - ret = _xpcs_config_aneg_c73(xpcs); + ret = _xpcs_config_aneg_c73(xpcs, compat); if (ret < 0) return ret; @@ -483,8 +469,9 @@ static int xpcs_config_aneg_c73(struct mdio_xpcs_args *xpcs) return xpcs_write(xpcs, MDIO_MMD_AN, MDIO_CTRL1, ret); } -static int xpcs_aneg_done_c73(struct mdio_xpcs_args *xpcs, - struct phylink_link_state *state) +static int xpcs_aneg_done_c73(struct dw_xpcs *xpcs, + struct phylink_link_state *state, + const struct xpcs_compat *compat) { int ret; @@ -499,7 +486,7 @@ static int xpcs_aneg_done_c73(struct mdio_xpcs_args *xpcs, /* Check if Aneg outcome is valid */ if (!(ret & DW_C73_AN_ADV_SF)) { - xpcs_config_aneg_c73(xpcs); + xpcs_config_aneg_c73(xpcs, compat); return 0; } @@ -509,7 +496,7 @@ static int xpcs_aneg_done_c73(struct mdio_xpcs_args *xpcs, return 0; } -static int xpcs_read_lpa_c73(struct mdio_xpcs_args *xpcs, +static int xpcs_read_lpa_c73(struct dw_xpcs *xpcs, struct phylink_link_state *state) { int ret; @@ -558,7 +545,7 @@ static int xpcs_read_lpa_c73(struct mdio_xpcs_args *xpcs, return 0; } -static void xpcs_resolve_lpa_c73(struct mdio_xpcs_args *xpcs, +static void xpcs_resolve_lpa_c73(struct dw_xpcs *xpcs, struct phylink_link_state *state) { int max_speed = xpcs_get_max_usxgmii_speed(state->lp_advertising); @@ -568,7 +555,7 @@ static void xpcs_resolve_lpa_c73(struct mdio_xpcs_args *xpcs, state->duplex = DUPLEX_FULL; } -static int xpcs_get_max_xlgmii_speed(struct mdio_xpcs_args *xpcs, +static int xpcs_get_max_xlgmii_speed(struct dw_xpcs *xpcs, struct phylink_link_state *state) { unsigned long *adv = state->advertising; @@ -622,7 +609,7 @@ static int xpcs_get_max_xlgmii_speed(struct mdio_xpcs_args *xpcs, return speed; } -static void xpcs_resolve_pma(struct mdio_xpcs_args *xpcs, +static void xpcs_resolve_pma(struct dw_xpcs *xpcs, struct phylink_link_state *state) { state->pause = MLO_PAUSE_TX | MLO_PAUSE_RX; @@ -641,16 +628,70 @@ static void xpcs_resolve_pma(struct mdio_xpcs_args *xpcs, } } -static int xpcs_validate(struct mdio_xpcs_args *xpcs, - unsigned long *supported, - struct phylink_link_state *state) +void xpcs_validate(struct dw_xpcs *xpcs, unsigned long *supported, + struct phylink_link_state *state) { - linkmode_and(supported, supported, xpcs->supported); - linkmode_and(state->advertising, state->advertising, xpcs->supported); - return 0; + __ETHTOOL_DECLARE_LINK_MODE_MASK(xpcs_supported); + const struct xpcs_compat *compat; + int i; + + /* phylink expects us to report all supported modes with + * PHY_INTERFACE_MODE_NA, just don't limit the supported and + * advertising masks and exit. + */ + if (state->interface == PHY_INTERFACE_MODE_NA) + return; + + bitmap_zero(xpcs_supported, __ETHTOOL_LINK_MODE_MASK_NBITS); + + compat = xpcs_find_compat(xpcs->id, state->interface); + + /* Populate the supported link modes for this + * PHY interface type + */ + if (compat) + for (i = 0; compat->supported[i] != __ETHTOOL_LINK_MODE_MASK_NBITS; i++) + set_bit(compat->supported[i], xpcs_supported); + + linkmode_and(supported, supported, xpcs_supported); + linkmode_and(state->advertising, state->advertising, xpcs_supported); } +EXPORT_SYMBOL_GPL(xpcs_validate); -static int xpcs_config_aneg_c37_sgmii(struct mdio_xpcs_args *xpcs) +int xpcs_config_eee(struct dw_xpcs *xpcs, int mult_fact_100ns, int enable) +{ + int ret; + + if (enable) { + /* Enable EEE */ + ret = DW_VR_MII_EEE_LTX_EN | DW_VR_MII_EEE_LRX_EN | + DW_VR_MII_EEE_TX_QUIET_EN | DW_VR_MII_EEE_RX_QUIET_EN | + DW_VR_MII_EEE_TX_EN_CTRL | DW_VR_MII_EEE_RX_EN_CTRL | + mult_fact_100ns << DW_VR_MII_EEE_MULT_FACT_100NS_SHIFT; + } else { + ret = xpcs_read(xpcs, MDIO_MMD_VEND2, DW_VR_MII_EEE_MCTRL0); + if (ret < 0) + return ret; + ret &= ~(DW_VR_MII_EEE_LTX_EN | DW_VR_MII_EEE_LRX_EN | + DW_VR_MII_EEE_TX_QUIET_EN | DW_VR_MII_EEE_RX_QUIET_EN | + DW_VR_MII_EEE_TX_EN_CTRL | DW_VR_MII_EEE_RX_EN_CTRL | + DW_VR_MII_EEE_MULT_FACT_100NS); + } + + ret = xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_EEE_MCTRL0, ret); + if (ret < 0) + return ret; + + ret = xpcs_read(xpcs, MDIO_MMD_VEND2, DW_VR_MII_EEE_MCTRL1); + if (ret < 0) + return ret; + + ret |= DW_VR_MII_EEE_TRN_LPI; + return xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_EEE_MCTRL1, ret); +} +EXPORT_SYMBOL_GPL(xpcs_config_eee); + +static int xpcs_config_aneg_c37_sgmii(struct dw_xpcs *xpcs, unsigned int mode) { int ret; @@ -686,26 +727,61 @@ static int xpcs_config_aneg_c37_sgmii(struct mdio_xpcs_args *xpcs) if (ret < 0) return ret; - ret |= DW_VR_MII_DIG_CTRL1_MAC_AUTO_SW; + if (phylink_autoneg_inband(mode)) + ret |= DW_VR_MII_DIG_CTRL1_MAC_AUTO_SW; + else + ret &= ~DW_VR_MII_DIG_CTRL1_MAC_AUTO_SW; return xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_DIG_CTRL1, ret); } -static int xpcs_config(struct mdio_xpcs_args *xpcs, - const struct phylink_link_state *state) +static int xpcs_config_2500basex(struct dw_xpcs *xpcs) +{ + int ret; + + ret = xpcs_read(xpcs, MDIO_MMD_VEND2, DW_VR_MII_DIG_CTRL1); + if (ret < 0) + return ret; + ret |= DW_VR_MII_DIG_CTRL1_2G5_EN; + ret &= ~DW_VR_MII_DIG_CTRL1_MAC_AUTO_SW; + ret = xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_DIG_CTRL1, ret); + if (ret < 0) + return ret; + + ret = xpcs_read(xpcs, MDIO_MMD_VEND2, DW_VR_MII_MMD_CTRL); + if (ret < 0) + return ret; + ret &= ~AN_CL37_EN; + ret |= SGMII_SPEED_SS6; + ret &= ~SGMII_SPEED_SS13; + return xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_MMD_CTRL, ret); +} + +int xpcs_do_config(struct dw_xpcs *xpcs, phy_interface_t interface, + unsigned int mode) { + const struct xpcs_compat *compat; int ret; - switch (xpcs->an_mode) { + compat = xpcs_find_compat(xpcs->id, interface); + if (!compat) + return -ENODEV; + + switch (compat->an_mode) { case DW_AN_C73: - if (state->an_enabled) { - ret = xpcs_config_aneg_c73(xpcs); + if (phylink_autoneg_inband(mode)) { + ret = xpcs_config_aneg_c73(xpcs, compat); if (ret) return ret; } break; case DW_AN_C37_SGMII: - ret = xpcs_config_aneg_c37_sgmii(xpcs); + ret = xpcs_config_aneg_c37_sgmii(xpcs, mode); + if (ret) + return ret; + break; + case DW_2500BASEX: + ret = xpcs_config_2500basex(xpcs); if (ret) return ret; break; @@ -713,11 +789,29 @@ static int xpcs_config(struct mdio_xpcs_args *xpcs, return -1; } + if (compat->pma_config) { + ret = compat->pma_config(xpcs); + if (ret) + return ret; + } + return 0; } +EXPORT_SYMBOL_GPL(xpcs_do_config); + +static int xpcs_config(struct phylink_pcs *pcs, unsigned int mode, + phy_interface_t interface, + const unsigned long *advertising, + bool permit_pause_to_mac) +{ + struct dw_xpcs *xpcs = phylink_pcs_to_xpcs(pcs); + + return xpcs_do_config(xpcs, interface, mode); +} -static int xpcs_get_state_c73(struct mdio_xpcs_args *xpcs, - struct phylink_link_state *state) +static int xpcs_get_state_c73(struct dw_xpcs *xpcs, + struct phylink_link_state *state, + const struct xpcs_compat *compat) { int ret; @@ -727,16 +821,16 @@ static int xpcs_get_state_c73(struct mdio_xpcs_args *xpcs, /* ... and then we check the faults. */ ret = xpcs_read_fault_c73(xpcs, state); if (ret) { - ret = xpcs_soft_reset(xpcs); + ret = xpcs_soft_reset(xpcs, compat); if (ret) return ret; state->link = 0; - return xpcs_config(xpcs, state); + return xpcs_do_config(xpcs, state->interface, MLO_AN_INBAND); } - if (state->an_enabled && xpcs_aneg_done_c73(xpcs, state)) { + if (state->an_enabled && xpcs_aneg_done_c73(xpcs, state, compat)) { state->an_complete = true; xpcs_read_lpa_c73(xpcs, state); xpcs_resolve_lpa_c73(xpcs, state); @@ -749,7 +843,7 @@ static int xpcs_get_state_c73(struct mdio_xpcs_args *xpcs, return 0; } -static int xpcs_get_state_c37_sgmii(struct mdio_xpcs_args *xpcs, +static int xpcs_get_state_c37_sgmii(struct dw_xpcs *xpcs, struct phylink_link_state *state) { int ret; @@ -790,39 +884,81 @@ static int xpcs_get_state_c37_sgmii(struct mdio_xpcs_args *xpcs, return 0; } -static int xpcs_get_state(struct mdio_xpcs_args *xpcs, - struct phylink_link_state *state) +static void xpcs_get_state(struct phylink_pcs *pcs, + struct phylink_link_state *state) { + struct dw_xpcs *xpcs = phylink_pcs_to_xpcs(pcs); + const struct xpcs_compat *compat; int ret; - switch (xpcs->an_mode) { + compat = xpcs_find_compat(xpcs->id, state->interface); + if (!compat) + return; + + switch (compat->an_mode) { case DW_AN_C73: - ret = xpcs_get_state_c73(xpcs, state); - if (ret) - return ret; + ret = xpcs_get_state_c73(xpcs, state, compat); + if (ret) { + pr_err("xpcs_get_state_c73 returned %pe\n", + ERR_PTR(ret)); + return; + } break; case DW_AN_C37_SGMII: ret = xpcs_get_state_c37_sgmii(xpcs, state); - if (ret) - return ret; + if (ret) { + pr_err("xpcs_get_state_c37_sgmii returned %pe\n", + ERR_PTR(ret)); + } break; default: - return -1; + return; } +} - return 0; +static void xpcs_link_up_sgmii(struct dw_xpcs *xpcs, unsigned int mode, + int speed, int duplex) +{ + int val, ret; + + if (phylink_autoneg_inband(mode)) + return; + + switch (speed) { + case SPEED_1000: + val = BMCR_SPEED1000; + break; + case SPEED_100: + val = BMCR_SPEED100; + break; + case SPEED_10: + val = BMCR_SPEED10; + break; + default: + return; + } + + if (duplex == DUPLEX_FULL) + val |= BMCR_FULLDPLX; + + ret = xpcs_write(xpcs, MDIO_MMD_VEND2, MDIO_CTRL1, val); + if (ret) + pr_err("%s: xpcs_write returned %pe\n", __func__, ERR_PTR(ret)); } -static int xpcs_link_up(struct mdio_xpcs_args *xpcs, int speed, - phy_interface_t interface) +void xpcs_link_up(struct phylink_pcs *pcs, unsigned int mode, + phy_interface_t interface, int speed, int duplex) { + struct dw_xpcs *xpcs = phylink_pcs_to_xpcs(pcs); + if (interface == PHY_INTERFACE_MODE_USXGMII) return xpcs_config_usxgmii(xpcs, speed); - - return 0; + if (interface == PHY_INTERFACE_MODE_SGMII) + return xpcs_link_up_sgmii(xpcs, mode, speed, duplex); } +EXPORT_SYMBOL_GPL(xpcs_link_up); -static u32 xpcs_get_id(struct mdio_xpcs_args *xpcs) +static u32 xpcs_get_id(struct dw_xpcs *xpcs) { int ret; u32 id; @@ -838,8 +974,10 @@ static u32 xpcs_get_id(struct mdio_xpcs_args *xpcs) if (ret < 0) return 0xffffffff; - /* If Device IDs are not all zeros, we found C73 AN-type device */ - if (id | ret) + /* If Device IDs are not all zeros or all ones, + * we found C73 AN-type device + */ + if ((id | ret) && (id | ret) != 0xffffffff) return id | ret; /* Next, search C37 PCS using Vendor-Specific MII MMD */ @@ -860,60 +998,141 @@ static u32 xpcs_get_id(struct mdio_xpcs_args *xpcs) return 0xffffffff; } -static bool xpcs_check_features(struct mdio_xpcs_args *xpcs, - struct xpcs_id *match, - phy_interface_t interface) -{ - int i; - - for (i = 0; match->interface[i] != PHY_INTERFACE_MODE_MAX; i++) { - if (match->interface[i] == interface) - break; - } +static const struct xpcs_compat synopsys_xpcs_compat[DW_XPCS_INTERFACE_MAX] = { + [DW_XPCS_USXGMII] = { + .supported = xpcs_usxgmii_features, + .interface = xpcs_usxgmii_interfaces, + .num_interfaces = ARRAY_SIZE(xpcs_usxgmii_interfaces), + .an_mode = DW_AN_C73, + }, + [DW_XPCS_10GKR] = { + .supported = xpcs_10gkr_features, + .interface = xpcs_10gkr_interfaces, + .num_interfaces = ARRAY_SIZE(xpcs_10gkr_interfaces), + .an_mode = DW_AN_C73, + }, + [DW_XPCS_XLGMII] = { + .supported = xpcs_xlgmii_features, + .interface = xpcs_xlgmii_interfaces, + .num_interfaces = ARRAY_SIZE(xpcs_xlgmii_interfaces), + .an_mode = DW_AN_C73, + }, + [DW_XPCS_SGMII] = { + .supported = xpcs_sgmii_features, + .interface = xpcs_sgmii_interfaces, + .num_interfaces = ARRAY_SIZE(xpcs_sgmii_interfaces), + .an_mode = DW_AN_C37_SGMII, + }, + [DW_XPCS_2500BASEX] = { + .supported = xpcs_2500basex_features, + .interface = xpcs_2500basex_interfaces, + .num_interfaces = ARRAY_SIZE(xpcs_2500basex_features), + .an_mode = DW_2500BASEX, + }, +}; - if (match->interface[i] == PHY_INTERFACE_MODE_MAX) - return false; +static const struct xpcs_compat nxp_sja1105_xpcs_compat[DW_XPCS_INTERFACE_MAX] = { + [DW_XPCS_SGMII] = { + .supported = xpcs_sgmii_features, + .interface = xpcs_sgmii_interfaces, + .num_interfaces = ARRAY_SIZE(xpcs_sgmii_interfaces), + .an_mode = DW_AN_C37_SGMII, + .pma_config = nxp_sja1105_sgmii_pma_config, + }, +}; - for (i = 0; match->supported[i] != __ETHTOOL_LINK_MODE_MASK_NBITS; i++) - set_bit(match->supported[i], xpcs->supported); +static const struct xpcs_compat nxp_sja1110_xpcs_compat[DW_XPCS_INTERFACE_MAX] = { + [DW_XPCS_SGMII] = { + .supported = xpcs_sgmii_features, + .interface = xpcs_sgmii_interfaces, + .num_interfaces = ARRAY_SIZE(xpcs_sgmii_interfaces), + .an_mode = DW_AN_C37_SGMII, + .pma_config = nxp_sja1110_sgmii_pma_config, + }, + [DW_XPCS_2500BASEX] = { + .supported = xpcs_2500basex_features, + .interface = xpcs_2500basex_interfaces, + .num_interfaces = ARRAY_SIZE(xpcs_2500basex_interfaces), + .an_mode = DW_2500BASEX, + .pma_config = nxp_sja1110_2500basex_pma_config, + }, +}; - xpcs->an_mode = match->an_mode; +static const struct xpcs_id xpcs_id_list[] = { + { + .id = SYNOPSYS_XPCS_ID, + .mask = SYNOPSYS_XPCS_MASK, + .compat = synopsys_xpcs_compat, + }, { + .id = NXP_SJA1105_XPCS_ID, + .mask = SYNOPSYS_XPCS_MASK, + .compat = nxp_sja1105_xpcs_compat, + }, { + .id = NXP_SJA1110_XPCS_ID, + .mask = SYNOPSYS_XPCS_MASK, + .compat = nxp_sja1110_xpcs_compat, + }, +}; - return true; -} +static const struct phylink_pcs_ops xpcs_phylink_ops = { + .pcs_config = xpcs_config, + .pcs_get_state = xpcs_get_state, + .pcs_link_up = xpcs_link_up, +}; -static int xpcs_probe(struct mdio_xpcs_args *xpcs, phy_interface_t interface) +struct dw_xpcs *xpcs_create(struct mdio_device *mdiodev, + phy_interface_t interface) { - u32 xpcs_id = xpcs_get_id(xpcs); - struct xpcs_id *match = NULL; - int i; + struct dw_xpcs *xpcs; + u32 xpcs_id; + int i, ret; + + xpcs = kzalloc(sizeof(*xpcs), GFP_KERNEL); + if (!xpcs) + return NULL; + + xpcs->mdiodev = mdiodev; + + xpcs_id = xpcs_get_id(xpcs); for (i = 0; i < ARRAY_SIZE(xpcs_id_list); i++) { - struct xpcs_id *entry = &xpcs_id_list[i]; + const struct xpcs_id *entry = &xpcs_id_list[i]; + const struct xpcs_compat *compat; + + if ((xpcs_id & entry->mask) != entry->id) + continue; - if ((xpcs_id & entry->mask) == entry->id) { - match = entry; + xpcs->id = entry; - if (xpcs_check_features(xpcs, match, interface)) - return xpcs_soft_reset(xpcs); + compat = xpcs_find_compat(entry, interface); + if (!compat) { + ret = -ENODEV; + goto out; } + + xpcs->pcs.ops = &xpcs_phylink_ops; + xpcs->pcs.poll = true; + + ret = xpcs_soft_reset(xpcs, compat); + if (ret) + goto out; + + return xpcs; } - return -ENODEV; -} + ret = -ENODEV; -static struct mdio_xpcs_ops xpcs_ops = { - .validate = xpcs_validate, - .config = xpcs_config, - .get_state = xpcs_get_state, - .link_up = xpcs_link_up, - .probe = xpcs_probe, -}; +out: + kfree(xpcs); + + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(xpcs_create); -struct mdio_xpcs_ops *mdio_xpcs_get_ops(void) +void xpcs_destroy(struct dw_xpcs *xpcs) { - return &xpcs_ops; + kfree(xpcs); } -EXPORT_SYMBOL_GPL(mdio_xpcs_get_ops); +EXPORT_SYMBOL_GPL(xpcs_destroy); MODULE_LICENSE("GPL v2"); |