mirror of
https://github.com/torvalds/linux.git
synced 2026-01-12 00:42:35 +08:00
The fbnic driver is planning to make use of the XPCS driver to enable support for PCS and better integration with phylink. To do this though we will need to enable several workarounds since the PMD interface for fbnic is likely to be unique since it is a mix of two different vendor products with a unique wrapper around the IP. I have generated a PHY identifier based on IEEE 802.3-2022 22.2.4.3.1 using an OUI belonging to Meta Platforms and used with our NICs. Using this we will provide it as the PMD ID via the SW based MDIO interface so that the fbnic device can be identified and necessary workarounds enabled in the XPCS driver. As an initial workaround this change adds an exception so that soft_reset is not set when the driver is initially bound to the PCS. In addition I have added logic to integrate the PMD Rx signal detect state into the link state for the PCS. With this we can avoid the link coming up too soon on the FBNIC PMD and as a result of it being in the training state so we can avoid link flaps. Signed-off-by: Alexander Duyck <alexanderduyck@fb.com> Link: https://patch.msgid.link/176374321695.959489.6648161125012056619.stgit@ahduyck-xeon-server.home.arpa Signed-off-by: Paolo Abeni <pabeni@redhat.com>
1729 lines
42 KiB
C
1729 lines
42 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2020 Synopsys, Inc. and/or its affiliates.
|
|
* Synopsys DesignWare XPCS helpers
|
|
*
|
|
* Author: Jose Abreu <Jose.Abreu@synopsys.com>
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/pcs/pcs-xpcs.h>
|
|
#include <linux/mdio.h>
|
|
#include <linux/phy.h>
|
|
#include <linux/phylink.h>
|
|
#include <linux/property.h>
|
|
|
|
#include "pcs-xpcs.h"
|
|
|
|
#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,
|
|
ETHTOOL_LINK_MODE_Asym_Pause_BIT,
|
|
ETHTOOL_LINK_MODE_Autoneg_BIT,
|
|
ETHTOOL_LINK_MODE_1000baseKX_Full_BIT,
|
|
ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT,
|
|
ETHTOOL_LINK_MODE_10000baseKR_Full_BIT,
|
|
ETHTOOL_LINK_MODE_2500baseX_Full_BIT,
|
|
__ETHTOOL_LINK_MODE_MASK_NBITS,
|
|
};
|
|
|
|
static const int xpcs_10gkr_features[] = {
|
|
ETHTOOL_LINK_MODE_Pause_BIT,
|
|
ETHTOOL_LINK_MODE_Asym_Pause_BIT,
|
|
ETHTOOL_LINK_MODE_10000baseKR_Full_BIT,
|
|
__ETHTOOL_LINK_MODE_MASK_NBITS,
|
|
};
|
|
|
|
static const int xpcs_25gbaser_features[] = {
|
|
ETHTOOL_LINK_MODE_MII_BIT,
|
|
ETHTOOL_LINK_MODE_Pause_BIT,
|
|
ETHTOOL_LINK_MODE_Asym_Pause_BIT,
|
|
ETHTOOL_LINK_MODE_25000baseCR_Full_BIT,
|
|
ETHTOOL_LINK_MODE_25000baseKR_Full_BIT,
|
|
ETHTOOL_LINK_MODE_25000baseSR_Full_BIT,
|
|
__ETHTOOL_LINK_MODE_MASK_NBITS,
|
|
};
|
|
|
|
static const int xpcs_xlgmii_features[] = {
|
|
ETHTOOL_LINK_MODE_Pause_BIT,
|
|
ETHTOOL_LINK_MODE_Asym_Pause_BIT,
|
|
ETHTOOL_LINK_MODE_25000baseCR_Full_BIT,
|
|
ETHTOOL_LINK_MODE_25000baseKR_Full_BIT,
|
|
ETHTOOL_LINK_MODE_25000baseSR_Full_BIT,
|
|
ETHTOOL_LINK_MODE_40000baseKR4_Full_BIT,
|
|
ETHTOOL_LINK_MODE_40000baseCR4_Full_BIT,
|
|
ETHTOOL_LINK_MODE_40000baseSR4_Full_BIT,
|
|
ETHTOOL_LINK_MODE_40000baseLR4_Full_BIT,
|
|
ETHTOOL_LINK_MODE_50000baseCR2_Full_BIT,
|
|
ETHTOOL_LINK_MODE_50000baseKR2_Full_BIT,
|
|
ETHTOOL_LINK_MODE_50000baseSR2_Full_BIT,
|
|
ETHTOOL_LINK_MODE_50000baseKR_Full_BIT,
|
|
ETHTOOL_LINK_MODE_50000baseSR_Full_BIT,
|
|
ETHTOOL_LINK_MODE_50000baseCR_Full_BIT,
|
|
ETHTOOL_LINK_MODE_50000baseLR_ER_FR_Full_BIT,
|
|
ETHTOOL_LINK_MODE_50000baseDR_Full_BIT,
|
|
ETHTOOL_LINK_MODE_100000baseKR4_Full_BIT,
|
|
ETHTOOL_LINK_MODE_100000baseSR4_Full_BIT,
|
|
ETHTOOL_LINK_MODE_100000baseCR4_Full_BIT,
|
|
ETHTOOL_LINK_MODE_100000baseLR4_ER4_Full_BIT,
|
|
ETHTOOL_LINK_MODE_100000baseKR2_Full_BIT,
|
|
ETHTOOL_LINK_MODE_100000baseSR2_Full_BIT,
|
|
ETHTOOL_LINK_MODE_100000baseCR2_Full_BIT,
|
|
ETHTOOL_LINK_MODE_100000baseLR2_ER2_FR2_Full_BIT,
|
|
ETHTOOL_LINK_MODE_100000baseDR2_Full_BIT,
|
|
__ETHTOOL_LINK_MODE_MASK_NBITS,
|
|
};
|
|
|
|
static const int xpcs_50gbaser_features[] = {
|
|
ETHTOOL_LINK_MODE_MII_BIT,
|
|
ETHTOOL_LINK_MODE_Pause_BIT,
|
|
ETHTOOL_LINK_MODE_Asym_Pause_BIT,
|
|
ETHTOOL_LINK_MODE_50000baseKR_Full_BIT,
|
|
ETHTOOL_LINK_MODE_50000baseSR_Full_BIT,
|
|
ETHTOOL_LINK_MODE_50000baseCR_Full_BIT,
|
|
ETHTOOL_LINK_MODE_50000baseLR_ER_FR_Full_BIT,
|
|
ETHTOOL_LINK_MODE_50000baseDR_Full_BIT,
|
|
__ETHTOOL_LINK_MODE_MASK_NBITS,
|
|
};
|
|
|
|
static const int xpcs_50gbaser2_features[] = {
|
|
ETHTOOL_LINK_MODE_MII_BIT,
|
|
ETHTOOL_LINK_MODE_Pause_BIT,
|
|
ETHTOOL_LINK_MODE_Asym_Pause_BIT,
|
|
ETHTOOL_LINK_MODE_50000baseCR2_Full_BIT,
|
|
ETHTOOL_LINK_MODE_50000baseKR2_Full_BIT,
|
|
ETHTOOL_LINK_MODE_50000baseSR2_Full_BIT,
|
|
__ETHTOOL_LINK_MODE_MASK_NBITS,
|
|
};
|
|
|
|
static const int xpcs_100gbasep_features[] = {
|
|
ETHTOOL_LINK_MODE_MII_BIT,
|
|
ETHTOOL_LINK_MODE_Pause_BIT,
|
|
ETHTOOL_LINK_MODE_Asym_Pause_BIT,
|
|
ETHTOOL_LINK_MODE_100000baseKR2_Full_BIT,
|
|
ETHTOOL_LINK_MODE_100000baseSR2_Full_BIT,
|
|
ETHTOOL_LINK_MODE_100000baseCR2_Full_BIT,
|
|
ETHTOOL_LINK_MODE_100000baseLR2_ER2_FR2_Full_BIT,
|
|
ETHTOOL_LINK_MODE_100000baseDR2_Full_BIT,
|
|
__ETHTOOL_LINK_MODE_MASK_NBITS,
|
|
};
|
|
|
|
static const int xpcs_10gbaser_features[] = {
|
|
ETHTOOL_LINK_MODE_Pause_BIT,
|
|
ETHTOOL_LINK_MODE_Asym_Pause_BIT,
|
|
ETHTOOL_LINK_MODE_10000baseSR_Full_BIT,
|
|
ETHTOOL_LINK_MODE_10000baseLR_Full_BIT,
|
|
ETHTOOL_LINK_MODE_10000baseLRM_Full_BIT,
|
|
ETHTOOL_LINK_MODE_10000baseER_Full_BIT,
|
|
__ETHTOOL_LINK_MODE_MASK_NBITS,
|
|
};
|
|
|
|
static const int xpcs_sgmii_features[] = {
|
|
ETHTOOL_LINK_MODE_Pause_BIT,
|
|
ETHTOOL_LINK_MODE_Asym_Pause_BIT,
|
|
ETHTOOL_LINK_MODE_Autoneg_BIT,
|
|
ETHTOOL_LINK_MODE_10baseT_Half_BIT,
|
|
ETHTOOL_LINK_MODE_10baseT_Full_BIT,
|
|
ETHTOOL_LINK_MODE_100baseT_Half_BIT,
|
|
ETHTOOL_LINK_MODE_100baseT_Full_BIT,
|
|
ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
|
|
ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
|
|
__ETHTOOL_LINK_MODE_MASK_NBITS,
|
|
};
|
|
|
|
static const int xpcs_1000basex_features[] = {
|
|
ETHTOOL_LINK_MODE_Pause_BIT,
|
|
ETHTOOL_LINK_MODE_Asym_Pause_BIT,
|
|
ETHTOOL_LINK_MODE_Autoneg_BIT,
|
|
ETHTOOL_LINK_MODE_1000baseX_Full_BIT,
|
|
__ETHTOOL_LINK_MODE_MASK_NBITS,
|
|
};
|
|
|
|
static const int xpcs_2500basex_features[] = {
|
|
ETHTOOL_LINK_MODE_Pause_BIT,
|
|
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,
|
|
};
|
|
|
|
struct dw_xpcs_compat {
|
|
phy_interface_t interface;
|
|
const int *supported;
|
|
int an_mode;
|
|
int (*pma_config)(struct dw_xpcs *xpcs);
|
|
};
|
|
|
|
struct dw_xpcs_desc {
|
|
u32 id;
|
|
u32 mask;
|
|
const struct dw_xpcs_compat *compat;
|
|
};
|
|
|
|
static const struct dw_xpcs_compat *
|
|
xpcs_find_compat(struct dw_xpcs *xpcs, phy_interface_t interface)
|
|
{
|
|
const struct dw_xpcs_compat *compat;
|
|
|
|
for (compat = xpcs->desc->compat; compat->supported; compat++)
|
|
if (compat->interface == interface)
|
|
return compat;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct phylink_pcs *xpcs_to_phylink_pcs(struct dw_xpcs *xpcs)
|
|
{
|
|
return &xpcs->pcs;
|
|
}
|
|
EXPORT_SYMBOL_GPL(xpcs_to_phylink_pcs);
|
|
|
|
int xpcs_get_an_mode(struct dw_xpcs *xpcs, phy_interface_t interface)
|
|
{
|
|
const struct dw_xpcs_compat *compat;
|
|
|
|
compat = xpcs_find_compat(xpcs, interface);
|
|
if (!compat)
|
|
return -ENODEV;
|
|
|
|
return compat->an_mode;
|
|
}
|
|
EXPORT_SYMBOL_GPL(xpcs_get_an_mode);
|
|
|
|
static bool __xpcs_linkmode_supported(const struct dw_xpcs_compat *compat,
|
|
enum ethtool_link_mode_bit_indices linkmode)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; compat->supported[i] != __ETHTOOL_LINK_MODE_MASK_NBITS; i++)
|
|
if (compat->supported[i] == linkmode)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
#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)
|
|
{
|
|
return mdiodev_c45_read(xpcs->mdiodev, dev, reg);
|
|
}
|
|
|
|
int xpcs_write(struct dw_xpcs *xpcs, int dev, u32 reg, u16 val)
|
|
{
|
|
return mdiodev_c45_write(xpcs->mdiodev, dev, reg, val);
|
|
}
|
|
|
|
int xpcs_modify(struct dw_xpcs *xpcs, int dev, u32 reg, u16 mask, u16 set)
|
|
{
|
|
return mdiodev_c45_modify(xpcs->mdiodev, dev, reg, mask, set);
|
|
}
|
|
|
|
static int xpcs_modify_changed(struct dw_xpcs *xpcs, int dev, u32 reg,
|
|
u16 mask, u16 set)
|
|
{
|
|
return mdiodev_c45_modify_changed(xpcs->mdiodev, dev, reg, mask, set);
|
|
}
|
|
|
|
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 dw_xpcs *xpcs, int dev, int reg,
|
|
u16 val)
|
|
{
|
|
return xpcs_write(xpcs, dev, DW_VENDOR | reg, val);
|
|
}
|
|
|
|
static int xpcs_modify_vendor(struct dw_xpcs *xpcs, int dev, int reg, u16 mask,
|
|
u16 set)
|
|
{
|
|
return xpcs_modify(xpcs, dev, DW_VENDOR | reg, mask, set);
|
|
}
|
|
|
|
int xpcs_read_vpcs(struct dw_xpcs *xpcs, int reg)
|
|
{
|
|
return xpcs_read_vendor(xpcs, MDIO_MMD_PCS, reg);
|
|
}
|
|
|
|
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_modify_vpcs(struct dw_xpcs *xpcs, int reg, u16 mask, u16 val)
|
|
{
|
|
return xpcs_modify_vendor(xpcs, MDIO_MMD_PCS, reg, mask, val);
|
|
}
|
|
|
|
static int xpcs_poll_reset(struct dw_xpcs *xpcs, int dev)
|
|
{
|
|
int ret, val;
|
|
|
|
ret = read_poll_timeout(xpcs_read, val,
|
|
val < 0 || !(val & BMCR_RESET),
|
|
50000, 600000, true, xpcs, dev, MII_BMCR);
|
|
if (val < 0)
|
|
ret = val;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int xpcs_soft_reset(struct dw_xpcs *xpcs,
|
|
const struct dw_xpcs_compat *compat)
|
|
{
|
|
int ret, dev;
|
|
|
|
switch (compat->an_mode) {
|
|
case DW_AN_C73:
|
|
case DW_10GBASER:
|
|
dev = MDIO_MMD_PCS;
|
|
break;
|
|
case DW_AN_C37_SGMII:
|
|
case DW_2500BASEX:
|
|
case DW_AN_C37_1000BASEX:
|
|
dev = MDIO_MMD_VEND2;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = xpcs_write(xpcs, dev, MII_BMCR, BMCR_RESET);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return xpcs_poll_reset(xpcs, dev);
|
|
}
|
|
|
|
#define xpcs_warn(__xpcs, __state, __args...) \
|
|
({ \
|
|
if ((__state)->link) \
|
|
dev_warn(&(__xpcs)->mdiodev->dev, ##__args); \
|
|
})
|
|
|
|
static int xpcs_read_fault_c73(struct dw_xpcs *xpcs,
|
|
struct phylink_link_state *state,
|
|
u16 pcs_stat1)
|
|
{
|
|
int ret;
|
|
|
|
if (pcs_stat1 & MDIO_STAT1_FAULT) {
|
|
xpcs_warn(xpcs, state, "Link fault condition detected!\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
ret = xpcs_read(xpcs, MDIO_MMD_PCS, MDIO_STAT2);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (ret & MDIO_STAT2_RXFAULT)
|
|
xpcs_warn(xpcs, state, "Receiver fault detected!\n");
|
|
if (ret & MDIO_STAT2_TXFAULT)
|
|
xpcs_warn(xpcs, state, "Transmitter fault detected!\n");
|
|
|
|
ret = xpcs_read_vendor(xpcs, MDIO_MMD_PCS, DW_VR_XS_PCS_DIG_STS);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (ret & DW_RXFIFO_ERR) {
|
|
xpcs_warn(xpcs, state, "FIFO fault condition detected!\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
ret = xpcs_read(xpcs, MDIO_MMD_PCS, MDIO_PCS_10GBRT_STAT1);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (!(ret & MDIO_PCS_10GBRT_STAT1_BLKLK))
|
|
xpcs_warn(xpcs, state, "Link is not locked!\n");
|
|
|
|
ret = xpcs_read(xpcs, MDIO_MMD_PCS, MDIO_PCS_10GBRT_STAT2);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (ret & MDIO_PCS_10GBRT_STAT2_ERR) {
|
|
xpcs_warn(xpcs, state, "Link has errors!\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void xpcs_link_up_usxgmii(struct dw_xpcs *xpcs, int speed)
|
|
{
|
|
int ret, speed_sel;
|
|
|
|
switch (speed) {
|
|
case SPEED_10:
|
|
speed_sel = DW_USXGMII_10;
|
|
break;
|
|
case SPEED_100:
|
|
speed_sel = DW_USXGMII_100;
|
|
break;
|
|
case SPEED_1000:
|
|
speed_sel = DW_USXGMII_1000;
|
|
break;
|
|
case SPEED_2500:
|
|
speed_sel = DW_USXGMII_2500;
|
|
break;
|
|
case SPEED_5000:
|
|
speed_sel = DW_USXGMII_5000;
|
|
break;
|
|
case SPEED_10000:
|
|
speed_sel = DW_USXGMII_10000;
|
|
break;
|
|
default:
|
|
/* Nothing to do here */
|
|
return;
|
|
}
|
|
|
|
ret = xpcs_modify_vpcs(xpcs, MDIO_CTRL1, DW_USXGMII_EN, DW_USXGMII_EN);
|
|
if (ret < 0)
|
|
goto out;
|
|
|
|
ret = xpcs_modify(xpcs, MDIO_MMD_VEND2, MII_BMCR, DW_USXGMII_SS_MASK,
|
|
speed_sel | DW_USXGMII_FULL);
|
|
if (ret < 0)
|
|
goto out;
|
|
|
|
ret = xpcs_modify_vpcs(xpcs, MDIO_CTRL1, DW_USXGMII_RST,
|
|
DW_USXGMII_RST);
|
|
if (ret < 0)
|
|
goto out;
|
|
|
|
return;
|
|
|
|
out:
|
|
dev_err(&xpcs->mdiodev->dev, "%s: XPCS access returned %pe\n",
|
|
__func__, ERR_PTR(ret));
|
|
}
|
|
|
|
static int _xpcs_config_aneg_c73(struct dw_xpcs *xpcs,
|
|
const struct dw_xpcs_compat *compat)
|
|
{
|
|
int ret, adv;
|
|
|
|
/* By default, in USXGMII mode XPCS operates at 10G baud and
|
|
* replicates data to achieve lower speeds. Hereby, in this
|
|
* default configuration we need to advertise all supported
|
|
* modes and not only the ones we want to use.
|
|
*/
|
|
|
|
/* SR_AN_ADV3 */
|
|
adv = 0;
|
|
if (xpcs_linkmode_supported(compat, 2500baseX_Full))
|
|
adv |= DW_C73_2500KX;
|
|
|
|
/* TODO: 5000baseKR */
|
|
|
|
ret = xpcs_write(xpcs, MDIO_MMD_AN, DW_SR_AN_ADV3, adv);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* SR_AN_ADV2 */
|
|
adv = 0;
|
|
if (xpcs_linkmode_supported(compat, 1000baseKX_Full))
|
|
adv |= DW_C73_1000KX;
|
|
if (xpcs_linkmode_supported(compat, 10000baseKX4_Full))
|
|
adv |= DW_C73_10000KX4;
|
|
if (xpcs_linkmode_supported(compat, 10000baseKR_Full))
|
|
adv |= DW_C73_10000KR;
|
|
|
|
ret = xpcs_write(xpcs, MDIO_MMD_AN, DW_SR_AN_ADV2, adv);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* SR_AN_ADV1 */
|
|
adv = DW_C73_AN_ADV_SF;
|
|
if (xpcs_linkmode_supported(compat, Pause))
|
|
adv |= DW_C73_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 dw_xpcs *xpcs,
|
|
const struct dw_xpcs_compat *compat)
|
|
{
|
|
int ret;
|
|
|
|
ret = _xpcs_config_aneg_c73(xpcs, compat);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return xpcs_modify(xpcs, MDIO_MMD_AN, MDIO_CTRL1,
|
|
MDIO_AN_CTRL1_ENABLE | MDIO_AN_CTRL1_RESTART,
|
|
MDIO_AN_CTRL1_ENABLE | MDIO_AN_CTRL1_RESTART);
|
|
}
|
|
|
|
static int xpcs_aneg_done_c73(struct dw_xpcs *xpcs,
|
|
struct phylink_link_state *state,
|
|
const struct dw_xpcs_compat *compat, u16 an_stat1)
|
|
{
|
|
int ret;
|
|
|
|
if (an_stat1 & MDIO_AN_STAT1_COMPLETE) {
|
|
ret = xpcs_read(xpcs, MDIO_MMD_AN, MDIO_AN_LPA);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* Check if Aneg outcome is valid */
|
|
if (!(ret & DW_C73_AN_ADV_SF)) {
|
|
xpcs_config_aneg_c73(xpcs, compat);
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int xpcs_read_lpa_c73(struct dw_xpcs *xpcs,
|
|
struct phylink_link_state *state, u16 an_stat1)
|
|
{
|
|
u16 lpa[3];
|
|
int i, ret;
|
|
|
|
if (!(an_stat1 & MDIO_AN_STAT1_LPABLE)) {
|
|
phylink_clear(state->lp_advertising, Autoneg);
|
|
return 0;
|
|
}
|
|
|
|
phylink_set(state->lp_advertising, Autoneg);
|
|
|
|
/* Read Clause 73 link partner advertisement */
|
|
for (i = ARRAY_SIZE(lpa); --i >= 0; ) {
|
|
ret = xpcs_read(xpcs, MDIO_MMD_AN, MDIO_AN_LPA + i);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
lpa[i] = ret;
|
|
}
|
|
|
|
mii_c73_mod_linkmode(state->lp_advertising, lpa);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int xpcs_get_max_xlgmii_speed(struct dw_xpcs *xpcs,
|
|
struct phylink_link_state *state)
|
|
{
|
|
unsigned long *adv = state->advertising;
|
|
int speed = SPEED_UNKNOWN;
|
|
int bit;
|
|
|
|
for_each_set_bit(bit, adv, __ETHTOOL_LINK_MODE_MASK_NBITS) {
|
|
int new_speed = SPEED_UNKNOWN;
|
|
|
|
switch (bit) {
|
|
case ETHTOOL_LINK_MODE_25000baseCR_Full_BIT:
|
|
case ETHTOOL_LINK_MODE_25000baseKR_Full_BIT:
|
|
case ETHTOOL_LINK_MODE_25000baseSR_Full_BIT:
|
|
new_speed = SPEED_25000;
|
|
break;
|
|
case ETHTOOL_LINK_MODE_40000baseKR4_Full_BIT:
|
|
case ETHTOOL_LINK_MODE_40000baseCR4_Full_BIT:
|
|
case ETHTOOL_LINK_MODE_40000baseSR4_Full_BIT:
|
|
case ETHTOOL_LINK_MODE_40000baseLR4_Full_BIT:
|
|
new_speed = SPEED_40000;
|
|
break;
|
|
case ETHTOOL_LINK_MODE_50000baseCR2_Full_BIT:
|
|
case ETHTOOL_LINK_MODE_50000baseKR2_Full_BIT:
|
|
case ETHTOOL_LINK_MODE_50000baseSR2_Full_BIT:
|
|
case ETHTOOL_LINK_MODE_50000baseKR_Full_BIT:
|
|
case ETHTOOL_LINK_MODE_50000baseSR_Full_BIT:
|
|
case ETHTOOL_LINK_MODE_50000baseCR_Full_BIT:
|
|
case ETHTOOL_LINK_MODE_50000baseLR_ER_FR_Full_BIT:
|
|
case ETHTOOL_LINK_MODE_50000baseDR_Full_BIT:
|
|
new_speed = SPEED_50000;
|
|
break;
|
|
case ETHTOOL_LINK_MODE_100000baseKR4_Full_BIT:
|
|
case ETHTOOL_LINK_MODE_100000baseSR4_Full_BIT:
|
|
case ETHTOOL_LINK_MODE_100000baseCR4_Full_BIT:
|
|
case ETHTOOL_LINK_MODE_100000baseLR4_ER4_Full_BIT:
|
|
case ETHTOOL_LINK_MODE_100000baseKR2_Full_BIT:
|
|
case ETHTOOL_LINK_MODE_100000baseSR2_Full_BIT:
|
|
case ETHTOOL_LINK_MODE_100000baseCR2_Full_BIT:
|
|
case ETHTOOL_LINK_MODE_100000baseLR2_ER2_FR2_Full_BIT:
|
|
case ETHTOOL_LINK_MODE_100000baseDR2_Full_BIT:
|
|
new_speed = SPEED_100000;
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
|
|
if (new_speed > speed)
|
|
speed = new_speed;
|
|
}
|
|
|
|
return speed;
|
|
}
|
|
|
|
static int xpcs_c45_read_pcs_speed(struct dw_xpcs *xpcs,
|
|
struct phylink_link_state *state)
|
|
{
|
|
int pcs_ctrl1;
|
|
|
|
pcs_ctrl1 = xpcs_read(xpcs, MDIO_MMD_PCS, MDIO_CTRL1);
|
|
if (pcs_ctrl1 < 0)
|
|
return pcs_ctrl1;
|
|
|
|
switch (pcs_ctrl1 & MDIO_CTRL1_SPEEDSEL) {
|
|
case MDIO_PCS_CTRL1_SPEED25G:
|
|
state->speed = SPEED_25000;
|
|
break;
|
|
case MDIO_PCS_CTRL1_SPEED50G:
|
|
state->speed = SPEED_50000;
|
|
break;
|
|
case MDIO_PCS_CTRL1_SPEED100G:
|
|
state->speed = SPEED_100000;
|
|
break;
|
|
default:
|
|
state->speed = SPEED_UNKNOWN;
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int xpcs_resolve_pma(struct dw_xpcs *xpcs,
|
|
struct phylink_link_state *state)
|
|
{
|
|
int pmd_rxdet, err = 0;
|
|
|
|
/* The Meta Platforms FBNIC PMD will go into a training state for
|
|
* about 4 seconds when the link first comes up. During this time the
|
|
* PCS link will bounce. To avoid reporting link up too soon we include
|
|
* the PMD state provided by the driver.
|
|
*/
|
|
if (xpcs->info.pma == MP_FBNIC_XPCS_PMA_100G_ID) {
|
|
pmd_rxdet = xpcs_read(xpcs, MDIO_MMD_PMAPMD, MDIO_PMA_RXDET);
|
|
if (pmd_rxdet < 0) {
|
|
state->link = false;
|
|
return pmd_rxdet;
|
|
}
|
|
|
|
/* Verify Rx lanes are trained before reporting link up */
|
|
if (!(pmd_rxdet & MDIO_PMD_RXDET_GLOBAL)) {
|
|
state->link = false;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
state->pause = MLO_PAUSE_TX | MLO_PAUSE_RX;
|
|
state->duplex = DUPLEX_FULL;
|
|
|
|
switch (state->interface) {
|
|
case PHY_INTERFACE_MODE_10GKR:
|
|
state->speed = SPEED_10000;
|
|
break;
|
|
case PHY_INTERFACE_MODE_XLGMII:
|
|
state->speed = xpcs_get_max_xlgmii_speed(xpcs, state);
|
|
break;
|
|
case PHY_INTERFACE_MODE_100GBASEP:
|
|
case PHY_INTERFACE_MODE_LAUI:
|
|
case PHY_INTERFACE_MODE_50GBASER:
|
|
case PHY_INTERFACE_MODE_25GBASER:
|
|
err = xpcs_c45_read_pcs_speed(xpcs, state);
|
|
break;
|
|
default:
|
|
state->speed = SPEED_UNKNOWN;
|
|
break;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int xpcs_validate(struct phylink_pcs *pcs, unsigned long *supported,
|
|
const struct phylink_link_state *state)
|
|
{
|
|
__ETHTOOL_DECLARE_LINK_MODE_MASK(xpcs_supported) = { 0, };
|
|
const struct dw_xpcs_compat *compat;
|
|
struct dw_xpcs *xpcs;
|
|
int i;
|
|
|
|
xpcs = phylink_pcs_to_xpcs(pcs);
|
|
compat = xpcs_find_compat(xpcs, state->interface);
|
|
if (!compat)
|
|
return -EINVAL;
|
|
|
|
/* Populate the supported link modes for this PHY interface type.
|
|
* FIXME: what about the port modes and autoneg bit? This masks
|
|
* all those away.
|
|
*/
|
|
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);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static unsigned int xpcs_inband_caps(struct phylink_pcs *pcs,
|
|
phy_interface_t interface)
|
|
{
|
|
struct dw_xpcs *xpcs = phylink_pcs_to_xpcs(pcs);
|
|
const struct dw_xpcs_compat *compat;
|
|
|
|
compat = xpcs_find_compat(xpcs, interface);
|
|
if (!compat)
|
|
return 0;
|
|
|
|
switch (compat->an_mode) {
|
|
case DW_AN_C73:
|
|
return LINK_INBAND_ENABLE;
|
|
|
|
case DW_AN_C37_SGMII:
|
|
case DW_AN_C37_1000BASEX:
|
|
return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
|
|
|
|
case DW_10GBASER:
|
|
case DW_2500BASEX:
|
|
return LINK_INBAND_DISABLE;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static void xpcs_get_interfaces(struct dw_xpcs *xpcs, unsigned long *interfaces)
|
|
{
|
|
const struct dw_xpcs_compat *compat;
|
|
|
|
for (compat = xpcs->desc->compat; compat->supported; compat++)
|
|
__set_bit(compat->interface, interfaces);
|
|
}
|
|
|
|
static int xpcs_switch_interface_mode(struct dw_xpcs *xpcs,
|
|
phy_interface_t interface)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (xpcs->info.pma == WX_TXGBE_XPCS_PMA_10G_ID) {
|
|
ret = txgbe_xpcs_switch_mode(xpcs, interface);
|
|
} else if (xpcs->interface != interface) {
|
|
if (interface == PHY_INTERFACE_MODE_SGMII)
|
|
xpcs->need_reset = true;
|
|
xpcs->interface = interface;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void xpcs_pre_config(struct phylink_pcs *pcs, phy_interface_t interface)
|
|
{
|
|
struct dw_xpcs *xpcs = phylink_pcs_to_xpcs(pcs);
|
|
const struct dw_xpcs_compat *compat;
|
|
int ret;
|
|
|
|
ret = xpcs_switch_interface_mode(xpcs, interface);
|
|
if (ret)
|
|
dev_err(&xpcs->mdiodev->dev, "switch interface failed: %pe\n",
|
|
ERR_PTR(ret));
|
|
|
|
if (!xpcs->need_reset)
|
|
return;
|
|
|
|
compat = xpcs_find_compat(xpcs, interface);
|
|
if (!compat) {
|
|
dev_err(&xpcs->mdiodev->dev, "unsupported interface %s\n",
|
|
phy_modes(interface));
|
|
return;
|
|
}
|
|
|
|
ret = xpcs_soft_reset(xpcs, compat);
|
|
if (ret)
|
|
dev_err(&xpcs->mdiodev->dev, "soft reset failed: %pe\n",
|
|
ERR_PTR(ret));
|
|
|
|
xpcs->need_reset = false;
|
|
}
|
|
|
|
static int xpcs_config_aneg_c37_sgmii(struct dw_xpcs *xpcs,
|
|
unsigned int neg_mode)
|
|
{
|
|
int ret, mdio_ctrl, tx_conf;
|
|
u16 mask, val;
|
|
|
|
/* For AN for C37 SGMII mode, the settings are :-
|
|
* 1) VR_MII_MMD_CTRL Bit(12) [AN_ENABLE] = 0b (Disable SGMII AN in case
|
|
it is already enabled)
|
|
* 2) VR_MII_AN_CTRL Bit(2:1)[PCS_MODE] = 10b (SGMII AN)
|
|
* 3) VR_MII_AN_CTRL Bit(3) [TX_CONFIG] = 0b (MAC side SGMII)
|
|
* DW xPCS used with DW EQoS MAC is always MAC side SGMII.
|
|
* 4) VR_MII_DIG_CTRL1 Bit(9) [MAC_AUTO_SW] = 1b (Automatic
|
|
* speed/duplex mode change by HW after SGMII AN complete)
|
|
* 5) VR_MII_MMD_CTRL Bit(12) [AN_ENABLE] = 1b (Enable SGMII AN)
|
|
*
|
|
* Note that VR_MII_MMD_CTRL is MII_BMCR.
|
|
*
|
|
* Note: Since it is MAC side SGMII, there is no need to set
|
|
* SR_MII_AN_ADV. MAC side SGMII receives AN Tx Config from
|
|
* PHY about the link state change after C28 AN is completed
|
|
* between PHY and Link Partner. There is also no need to
|
|
* trigger AN restart for MAC-side SGMII.
|
|
*/
|
|
mdio_ctrl = xpcs_read(xpcs, MDIO_MMD_VEND2, MII_BMCR);
|
|
if (mdio_ctrl < 0)
|
|
return mdio_ctrl;
|
|
|
|
if (mdio_ctrl & BMCR_ANENABLE) {
|
|
ret = xpcs_write(xpcs, MDIO_MMD_VEND2, MII_BMCR,
|
|
mdio_ctrl & ~BMCR_ANENABLE);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
mask = DW_VR_MII_PCS_MODE_MASK | DW_VR_MII_TX_CONFIG_MASK;
|
|
val = FIELD_PREP(DW_VR_MII_PCS_MODE_MASK,
|
|
DW_VR_MII_PCS_MODE_C37_SGMII);
|
|
|
|
if (xpcs->info.pma == WX_TXGBE_XPCS_PMA_10G_ID) {
|
|
mask |= DW_VR_MII_AN_CTRL_8BIT;
|
|
val |= DW_VR_MII_AN_CTRL_8BIT;
|
|
/* Hardware requires it to be PHY side SGMII */
|
|
tx_conf = DW_VR_MII_TX_CONFIG_PHY_SIDE_SGMII;
|
|
} else {
|
|
tx_conf = DW_VR_MII_TX_CONFIG_MAC_SIDE_SGMII;
|
|
}
|
|
|
|
val |= FIELD_PREP(DW_VR_MII_TX_CONFIG_MASK, tx_conf);
|
|
|
|
ret = xpcs_modify(xpcs, MDIO_MMD_VEND2, DW_VR_MII_AN_CTRL, mask, val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
val = 0;
|
|
mask = DW_VR_MII_DIG_CTRL1_2G5_EN | DW_VR_MII_DIG_CTRL1_MAC_AUTO_SW;
|
|
|
|
if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED)
|
|
val = DW_VR_MII_DIG_CTRL1_MAC_AUTO_SW;
|
|
|
|
if (xpcs->info.pma == WX_TXGBE_XPCS_PMA_10G_ID) {
|
|
mask |= DW_VR_MII_DIG_CTRL1_PHY_MODE_CTRL;
|
|
val |= DW_VR_MII_DIG_CTRL1_PHY_MODE_CTRL;
|
|
}
|
|
|
|
ret = xpcs_modify(xpcs, MDIO_MMD_VEND2, DW_VR_MII_DIG_CTRL1, mask, val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED)
|
|
ret = xpcs_write(xpcs, MDIO_MMD_VEND2, MII_BMCR,
|
|
mdio_ctrl | BMCR_ANENABLE);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int xpcs_config_aneg_c37_1000basex(struct dw_xpcs *xpcs,
|
|
unsigned int neg_mode,
|
|
const unsigned long *advertising)
|
|
{
|
|
phy_interface_t interface = PHY_INTERFACE_MODE_1000BASEX;
|
|
int ret, mdio_ctrl, adv;
|
|
bool changed = 0;
|
|
u16 mask, val;
|
|
|
|
/* According to Chap 7.12, to set 1000BASE-X C37 AN, AN must
|
|
* be disabled first:-
|
|
* 1) VR_MII_MMD_CTRL Bit(12)[AN_ENABLE] = 0b
|
|
* 2) VR_MII_AN_CTRL Bit(2:1)[PCS_MODE] = 00b (1000BASE-X C37)
|
|
*
|
|
* Note that VR_MII_MMD_CTRL is MII_BMCR.
|
|
*/
|
|
mdio_ctrl = xpcs_read(xpcs, MDIO_MMD_VEND2, MII_BMCR);
|
|
if (mdio_ctrl < 0)
|
|
return mdio_ctrl;
|
|
|
|
if (mdio_ctrl & BMCR_ANENABLE) {
|
|
ret = xpcs_write(xpcs, MDIO_MMD_VEND2, MII_BMCR,
|
|
mdio_ctrl & ~BMCR_ANENABLE);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
mask = DW_VR_MII_PCS_MODE_MASK;
|
|
val = FIELD_PREP(DW_VR_MII_PCS_MODE_MASK,
|
|
DW_VR_MII_PCS_MODE_C37_1000BASEX);
|
|
|
|
if (!xpcs->pcs.poll) {
|
|
mask |= DW_VR_MII_AN_INTR_EN;
|
|
val |= DW_VR_MII_AN_INTR_EN;
|
|
}
|
|
|
|
ret = xpcs_modify(xpcs, MDIO_MMD_VEND2, DW_VR_MII_AN_CTRL, mask, val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* Check for advertising changes and update the C45 MII ADV
|
|
* register accordingly.
|
|
*/
|
|
adv = phylink_mii_c22_pcs_encode_advertisement(interface,
|
|
advertising);
|
|
if (adv >= 0) {
|
|
ret = xpcs_modify_changed(xpcs, MDIO_MMD_VEND2,
|
|
MII_ADVERTISE, 0xffff, adv);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
changed = ret;
|
|
}
|
|
|
|
/* Clear CL37 AN complete status */
|
|
ret = xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_AN_INTR_STS, 0);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) {
|
|
ret = xpcs_write(xpcs, MDIO_MMD_VEND2, MII_BMCR,
|
|
mdio_ctrl | BMCR_ANENABLE);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
static int xpcs_config_2500basex(struct dw_xpcs *xpcs)
|
|
{
|
|
int ret;
|
|
|
|
ret = xpcs_modify(xpcs, MDIO_MMD_VEND2, DW_VR_MII_DIG_CTRL1,
|
|
DW_VR_MII_DIG_CTRL1_2G5_EN |
|
|
DW_VR_MII_DIG_CTRL1_MAC_AUTO_SW,
|
|
DW_VR_MII_DIG_CTRL1_2G5_EN);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return xpcs_modify(xpcs, MDIO_MMD_VEND2, MII_BMCR,
|
|
BMCR_ANENABLE | BMCR_SPEED1000 | BMCR_SPEED100,
|
|
BMCR_SPEED1000);
|
|
}
|
|
|
|
static int xpcs_do_config(struct dw_xpcs *xpcs, phy_interface_t interface,
|
|
const unsigned long *advertising,
|
|
unsigned int neg_mode)
|
|
{
|
|
const struct dw_xpcs_compat *compat;
|
|
int ret;
|
|
|
|
compat = xpcs_find_compat(xpcs, interface);
|
|
if (!compat)
|
|
return -ENODEV;
|
|
|
|
if (xpcs->info.pma == WX_TXGBE_XPCS_PMA_10G_ID) {
|
|
/* Wangxun devices need backplane CL37 AN enabled for
|
|
* SGMII and 1000base-X
|
|
*/
|
|
if (interface == PHY_INTERFACE_MODE_SGMII ||
|
|
interface == PHY_INTERFACE_MODE_1000BASEX)
|
|
xpcs_write_vpcs(xpcs, DW_VR_XS_PCS_DIG_CTRL1,
|
|
DW_CL37_BP | DW_EN_VSMMD1);
|
|
}
|
|
|
|
switch (compat->an_mode) {
|
|
case DW_10GBASER:
|
|
break;
|
|
case DW_AN_C73:
|
|
if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) {
|
|
ret = xpcs_config_aneg_c73(xpcs, compat);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
break;
|
|
case DW_AN_C37_SGMII:
|
|
ret = xpcs_config_aneg_c37_sgmii(xpcs, neg_mode);
|
|
if (ret)
|
|
return ret;
|
|
break;
|
|
case DW_AN_C37_1000BASEX:
|
|
ret = xpcs_config_aneg_c37_1000basex(xpcs, neg_mode,
|
|
advertising);
|
|
if (ret)
|
|
return ret;
|
|
break;
|
|
case DW_2500BASEX:
|
|
ret = xpcs_config_2500basex(xpcs);
|
|
if (ret)
|
|
return ret;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (compat->pma_config) {
|
|
ret = compat->pma_config(xpcs);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int xpcs_config(struct phylink_pcs *pcs, unsigned int neg_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, advertising, neg_mode);
|
|
}
|
|
|
|
static int xpcs_get_state_c73(struct dw_xpcs *xpcs,
|
|
struct phylink_link_state *state,
|
|
const struct dw_xpcs_compat *compat)
|
|
{
|
|
bool an_enabled;
|
|
int pcs_stat1;
|
|
int an_stat1;
|
|
int ret;
|
|
|
|
/* The link status bit is latching-low, so it is important to
|
|
* avoid unnecessary re-reads of this register to avoid missing
|
|
* a link-down event.
|
|
*/
|
|
pcs_stat1 = xpcs_read(xpcs, MDIO_MMD_PCS, MDIO_STAT1);
|
|
if (pcs_stat1 < 0) {
|
|
state->link = false;
|
|
return pcs_stat1;
|
|
}
|
|
|
|
/* Link needs to be read first ... */
|
|
state->link = !!(pcs_stat1 & MDIO_STAT1_LSTATUS);
|
|
|
|
/* ... and then we check the faults. */
|
|
ret = xpcs_read_fault_c73(xpcs, state, pcs_stat1);
|
|
if (ret) {
|
|
ret = xpcs_soft_reset(xpcs, compat);
|
|
if (ret)
|
|
return ret;
|
|
|
|
state->link = 0;
|
|
|
|
return xpcs_do_config(xpcs, state->interface, NULL,
|
|
PHYLINK_PCS_NEG_INBAND_ENABLED);
|
|
}
|
|
|
|
/* There is no point doing anything else if the link is down. */
|
|
if (!state->link)
|
|
return 0;
|
|
|
|
an_enabled = linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
|
|
state->advertising);
|
|
if (an_enabled) {
|
|
/* The link status bit is latching-low, so it is important to
|
|
* avoid unnecessary re-reads of this register to avoid missing
|
|
* a link-down event.
|
|
*/
|
|
an_stat1 = xpcs_read(xpcs, MDIO_MMD_AN, MDIO_STAT1);
|
|
if (an_stat1 < 0) {
|
|
state->link = false;
|
|
return an_stat1;
|
|
}
|
|
|
|
state->an_complete = xpcs_aneg_done_c73(xpcs, state, compat,
|
|
an_stat1);
|
|
if (!state->an_complete) {
|
|
state->link = false;
|
|
return 0;
|
|
}
|
|
|
|
ret = xpcs_read_lpa_c73(xpcs, state, an_stat1);
|
|
if (ret < 0) {
|
|
state->link = false;
|
|
return ret;
|
|
}
|
|
|
|
phylink_resolve_c73(state);
|
|
} else {
|
|
ret = xpcs_resolve_pma(xpcs, state);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int xpcs_get_state_c37_sgmii(struct dw_xpcs *xpcs,
|
|
struct phylink_link_state *state)
|
|
{
|
|
int ret;
|
|
|
|
/* Reset link_state */
|
|
state->link = false;
|
|
state->speed = SPEED_UNKNOWN;
|
|
state->duplex = DUPLEX_UNKNOWN;
|
|
state->pause = 0;
|
|
|
|
/* For C37 SGMII mode, we check DW_VR_MII_AN_INTR_STS for link
|
|
* status, speed and duplex.
|
|
*/
|
|
ret = xpcs_read(xpcs, MDIO_MMD_VEND2, DW_VR_MII_AN_INTR_STS);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (ret & DW_VR_MII_C37_ANSGM_SP_LNKSTS) {
|
|
int speed_value;
|
|
|
|
state->link = true;
|
|
|
|
speed_value = FIELD_GET(DW_VR_MII_AN_STS_C37_ANSGM_SP, ret);
|
|
if (speed_value == DW_VR_MII_C37_ANSGM_SP_1000)
|
|
state->speed = SPEED_1000;
|
|
else if (speed_value == DW_VR_MII_C37_ANSGM_SP_100)
|
|
state->speed = SPEED_100;
|
|
else
|
|
state->speed = SPEED_10;
|
|
|
|
if (ret & DW_VR_MII_AN_STS_C37_ANSGM_FD)
|
|
state->duplex = DUPLEX_FULL;
|
|
else
|
|
state->duplex = DUPLEX_HALF;
|
|
} else if (ret == DW_VR_MII_AN_STS_C37_ANCMPLT_INTR) {
|
|
int speed, duplex;
|
|
|
|
state->link = true;
|
|
|
|
speed = xpcs_read(xpcs, MDIO_MMD_VEND2, MII_BMCR);
|
|
if (speed < 0)
|
|
return speed;
|
|
|
|
speed &= BMCR_SPEED100 | BMCR_SPEED1000;
|
|
if (speed == BMCR_SPEED1000)
|
|
state->speed = SPEED_1000;
|
|
else if (speed == BMCR_SPEED100)
|
|
state->speed = SPEED_100;
|
|
else if (speed == 0)
|
|
state->speed = SPEED_10;
|
|
|
|
duplex = xpcs_read(xpcs, MDIO_MMD_VEND2, MII_ADVERTISE);
|
|
if (duplex < 0)
|
|
return duplex;
|
|
|
|
if (duplex & ADVERTISE_1000XFULL)
|
|
state->duplex = DUPLEX_FULL;
|
|
else if (duplex & ADVERTISE_1000XHALF)
|
|
state->duplex = DUPLEX_HALF;
|
|
|
|
xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_AN_INTR_STS, 0);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int xpcs_get_state_c37_1000basex(struct dw_xpcs *xpcs,
|
|
unsigned int neg_mode,
|
|
struct phylink_link_state *state)
|
|
{
|
|
int lpa, bmsr;
|
|
|
|
if (linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
|
|
state->advertising)) {
|
|
/* Reset link state */
|
|
state->link = false;
|
|
|
|
lpa = xpcs_read(xpcs, MDIO_MMD_VEND2, MII_LPA);
|
|
if (lpa < 0 || lpa & LPA_RFAULT)
|
|
return lpa;
|
|
|
|
bmsr = xpcs_read(xpcs, MDIO_MMD_VEND2, MII_BMSR);
|
|
if (bmsr < 0)
|
|
return bmsr;
|
|
|
|
/* Clear AN complete interrupt */
|
|
if (!xpcs->pcs.poll) {
|
|
int an_intr;
|
|
|
|
an_intr = xpcs_read(xpcs, MDIO_MMD_VEND2, DW_VR_MII_AN_INTR_STS);
|
|
if (an_intr & DW_VR_MII_AN_STS_C37_ANCMPLT_INTR) {
|
|
an_intr &= ~DW_VR_MII_AN_STS_C37_ANCMPLT_INTR;
|
|
xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_AN_INTR_STS, an_intr);
|
|
}
|
|
}
|
|
|
|
phylink_mii_c22_pcs_decode_state(state, neg_mode, bmsr, lpa);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int xpcs_get_state_2500basex(struct dw_xpcs *xpcs,
|
|
struct phylink_link_state *state)
|
|
{
|
|
int ret;
|
|
|
|
ret = xpcs_read(xpcs, MDIO_MMD_VEND2, MII_BMSR);
|
|
if (ret < 0) {
|
|
state->link = 0;
|
|
return ret;
|
|
}
|
|
|
|
state->link = !!(ret & BMSR_LSTATUS);
|
|
if (!state->link)
|
|
return 0;
|
|
|
|
state->speed = SPEED_2500;
|
|
state->pause |= MLO_PAUSE_TX | MLO_PAUSE_RX;
|
|
state->duplex = DUPLEX_FULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void xpcs_get_state(struct phylink_pcs *pcs, unsigned int neg_mode,
|
|
struct phylink_link_state *state)
|
|
{
|
|
struct dw_xpcs *xpcs = phylink_pcs_to_xpcs(pcs);
|
|
const struct dw_xpcs_compat *compat;
|
|
int ret;
|
|
|
|
compat = xpcs_find_compat(xpcs, state->interface);
|
|
if (!compat)
|
|
return;
|
|
|
|
switch (compat->an_mode) {
|
|
case DW_10GBASER:
|
|
phylink_mii_c45_pcs_get_state(xpcs->mdiodev, state);
|
|
break;
|
|
case DW_AN_C73:
|
|
ret = xpcs_get_state_c73(xpcs, state, compat);
|
|
if (ret)
|
|
dev_err(&xpcs->mdiodev->dev, "%s returned %pe\n",
|
|
"xpcs_get_state_c73", ERR_PTR(ret));
|
|
break;
|
|
case DW_AN_C37_SGMII:
|
|
ret = xpcs_get_state_c37_sgmii(xpcs, state);
|
|
if (ret)
|
|
dev_err(&xpcs->mdiodev->dev, "%s returned %pe\n",
|
|
"xpcs_get_state_c37_sgmii", ERR_PTR(ret));
|
|
break;
|
|
case DW_AN_C37_1000BASEX:
|
|
ret = xpcs_get_state_c37_1000basex(xpcs, neg_mode, state);
|
|
if (ret)
|
|
dev_err(&xpcs->mdiodev->dev, "%s returned %pe\n",
|
|
"xpcs_get_state_c37_1000basex", ERR_PTR(ret));
|
|
break;
|
|
case DW_2500BASEX:
|
|
ret = xpcs_get_state_2500basex(xpcs, state);
|
|
if (ret)
|
|
dev_err(&xpcs->mdiodev->dev, "%s returned %pe\n",
|
|
"xpcs_get_state_2500basex", ERR_PTR(ret));
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void xpcs_link_up_sgmii_1000basex(struct dw_xpcs *xpcs,
|
|
unsigned int neg_mode,
|
|
phy_interface_t interface,
|
|
int speed, int duplex)
|
|
{
|
|
int ret;
|
|
|
|
if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED)
|
|
return;
|
|
|
|
if (interface == PHY_INTERFACE_MODE_1000BASEX) {
|
|
if (speed != SPEED_1000) {
|
|
dev_err(&xpcs->mdiodev->dev,
|
|
"%s: speed %dMbps not supported\n",
|
|
__func__, speed);
|
|
return;
|
|
}
|
|
|
|
if (duplex != DUPLEX_FULL)
|
|
dev_err(&xpcs->mdiodev->dev,
|
|
"%s: half duplex not supported\n",
|
|
__func__);
|
|
}
|
|
|
|
ret = xpcs_write(xpcs, MDIO_MMD_VEND2, MII_BMCR,
|
|
mii_bmcr_encode_fixed(speed, duplex));
|
|
if (ret)
|
|
dev_err(&xpcs->mdiodev->dev, "%s: xpcs_write returned %pe\n",
|
|
__func__, ERR_PTR(ret));
|
|
}
|
|
|
|
static void xpcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode,
|
|
phy_interface_t interface, int speed, int duplex)
|
|
{
|
|
struct dw_xpcs *xpcs = phylink_pcs_to_xpcs(pcs);
|
|
|
|
switch (interface) {
|
|
case PHY_INTERFACE_MODE_USXGMII:
|
|
xpcs_link_up_usxgmii(xpcs, speed);
|
|
break;
|
|
|
|
case PHY_INTERFACE_MODE_SGMII:
|
|
case PHY_INTERFACE_MODE_1000BASEX:
|
|
xpcs_link_up_sgmii_1000basex(xpcs, neg_mode, interface, speed,
|
|
duplex);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void xpcs_an_restart(struct phylink_pcs *pcs)
|
|
{
|
|
struct dw_xpcs *xpcs = phylink_pcs_to_xpcs(pcs);
|
|
|
|
xpcs_modify(xpcs, MDIO_MMD_VEND2, MII_BMCR, BMCR_ANRESTART,
|
|
BMCR_ANRESTART);
|
|
}
|
|
|
|
static int xpcs_config_eee(struct dw_xpcs *xpcs, bool enable)
|
|
{
|
|
u16 mask, val;
|
|
int ret;
|
|
|
|
mask = 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;
|
|
|
|
if (enable)
|
|
val = 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 |
|
|
FIELD_PREP(DW_VR_MII_EEE_MULT_FACT_100NS,
|
|
xpcs->eee_mult_fact);
|
|
else
|
|
val = 0;
|
|
|
|
ret = xpcs_modify(xpcs, MDIO_MMD_VEND2, DW_VR_MII_EEE_MCTRL0, mask,
|
|
val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return xpcs_modify(xpcs, MDIO_MMD_VEND2, DW_VR_MII_EEE_MCTRL1,
|
|
DW_VR_MII_EEE_TRN_LPI,
|
|
enable ? DW_VR_MII_EEE_TRN_LPI : 0);
|
|
}
|
|
|
|
static void xpcs_disable_eee(struct phylink_pcs *pcs)
|
|
{
|
|
struct dw_xpcs *xpcs = phylink_pcs_to_xpcs(pcs);
|
|
|
|
xpcs_config_eee(xpcs, false);
|
|
}
|
|
|
|
static void xpcs_enable_eee(struct phylink_pcs *pcs)
|
|
{
|
|
struct dw_xpcs *xpcs = phylink_pcs_to_xpcs(pcs);
|
|
|
|
xpcs_config_eee(xpcs, true);
|
|
}
|
|
|
|
/**
|
|
* xpcs_config_eee_mult_fact() - set the EEE clock multiplying factor
|
|
* @xpcs: pointer to a &struct dw_xpcs instance
|
|
* @mult_fact: the multiplying factor
|
|
*
|
|
* Configure the EEE clock multiplying factor. This value should be such that
|
|
* clk_eee_time_period * (mult_fact + 1) is within the range 80 to 120ns.
|
|
*/
|
|
void xpcs_config_eee_mult_fact(struct dw_xpcs *xpcs, u8 mult_fact)
|
|
{
|
|
xpcs->eee_mult_fact = mult_fact;
|
|
}
|
|
EXPORT_SYMBOL_GPL(xpcs_config_eee_mult_fact);
|
|
|
|
static int xpcs_read_ids(struct dw_xpcs *xpcs)
|
|
{
|
|
int ret;
|
|
u32 id;
|
|
|
|
/* First, search C73 PCS using PCS MMD 3. Return ENODEV if communication
|
|
* failed indicating that device couldn't be reached.
|
|
*/
|
|
ret = xpcs_read(xpcs, MDIO_MMD_PCS, MII_PHYSID1);
|
|
if (ret < 0)
|
|
return -ENODEV;
|
|
|
|
id = ret << 16;
|
|
|
|
ret = xpcs_read(xpcs, MDIO_MMD_PCS, MII_PHYSID2);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
id |= ret;
|
|
|
|
/* If Device IDs are not all zeros or ones, then 10GBase-X/R or C73
|
|
* KR/KX4 PCS found. Otherwise fallback to detecting 1000Base-X or C37
|
|
* PCS in MII MMD 31.
|
|
*/
|
|
if (!id || id == 0xffffffff) {
|
|
ret = xpcs_read(xpcs, MDIO_MMD_VEND2, MII_PHYSID1);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
id = ret << 16;
|
|
|
|
ret = xpcs_read(xpcs, MDIO_MMD_VEND2, MII_PHYSID2);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
id |= ret;
|
|
}
|
|
|
|
/* Set the PCS ID if it hasn't been pre-initialized */
|
|
if (xpcs->info.pcs == DW_XPCS_ID_NATIVE)
|
|
xpcs->info.pcs = id;
|
|
|
|
/* Find out PMA/PMD ID from MMD 1 device ID registers */
|
|
ret = xpcs_read(xpcs, MDIO_MMD_PMAPMD, MDIO_DEVID1);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
id = ret << 16;
|
|
|
|
ret = xpcs_read(xpcs, MDIO_MMD_PMAPMD, MDIO_DEVID2);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* For now we only record the OUI for the PMAPMD, we may want to
|
|
* add the model number at some point in the future.
|
|
*/
|
|
id |= ret & MDIO_DEVID2_OUI;
|
|
|
|
/* Set the PMA ID if it hasn't been pre-initialized */
|
|
if (xpcs->info.pma == DW_XPCS_PMA_ID_NATIVE)
|
|
xpcs->info.pma = id;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct dw_xpcs_compat synopsys_xpcs_compat[] = {
|
|
{
|
|
.interface = PHY_INTERFACE_MODE_USXGMII,
|
|
.supported = xpcs_usxgmii_features,
|
|
.an_mode = DW_AN_C73,
|
|
}, {
|
|
.interface = PHY_INTERFACE_MODE_10GKR,
|
|
.supported = xpcs_10gkr_features,
|
|
.an_mode = DW_AN_C73,
|
|
}, {
|
|
.interface = PHY_INTERFACE_MODE_25GBASER,
|
|
.supported = xpcs_25gbaser_features,
|
|
.an_mode = DW_AN_C73,
|
|
}, {
|
|
.interface = PHY_INTERFACE_MODE_XLGMII,
|
|
.supported = xpcs_xlgmii_features,
|
|
.an_mode = DW_AN_C73,
|
|
}, {
|
|
.interface = PHY_INTERFACE_MODE_50GBASER,
|
|
.supported = xpcs_50gbaser_features,
|
|
.an_mode = DW_AN_C73,
|
|
}, {
|
|
.interface = PHY_INTERFACE_MODE_LAUI,
|
|
.supported = xpcs_50gbaser2_features,
|
|
.an_mode = DW_AN_C73,
|
|
}, {
|
|
.interface = PHY_INTERFACE_MODE_100GBASEP,
|
|
.supported = xpcs_100gbasep_features,
|
|
.an_mode = DW_AN_C73,
|
|
}, {
|
|
.interface = PHY_INTERFACE_MODE_10GBASER,
|
|
.supported = xpcs_10gbaser_features,
|
|
.an_mode = DW_10GBASER,
|
|
}, {
|
|
.interface = PHY_INTERFACE_MODE_SGMII,
|
|
.supported = xpcs_sgmii_features,
|
|
.an_mode = DW_AN_C37_SGMII,
|
|
}, {
|
|
.interface = PHY_INTERFACE_MODE_1000BASEX,
|
|
.supported = xpcs_1000basex_features,
|
|
.an_mode = DW_AN_C37_1000BASEX,
|
|
}, {
|
|
.interface = PHY_INTERFACE_MODE_2500BASEX,
|
|
.supported = xpcs_2500basex_features,
|
|
.an_mode = DW_2500BASEX,
|
|
}, {
|
|
}
|
|
};
|
|
|
|
static const struct dw_xpcs_compat nxp_sja1105_xpcs_compat[] = {
|
|
{
|
|
.interface = PHY_INTERFACE_MODE_SGMII,
|
|
.supported = xpcs_sgmii_features,
|
|
.an_mode = DW_AN_C37_SGMII,
|
|
.pma_config = nxp_sja1105_sgmii_pma_config,
|
|
}, {
|
|
}
|
|
};
|
|
|
|
static const struct dw_xpcs_compat nxp_sja1110_xpcs_compat[] = {
|
|
{
|
|
.interface = PHY_INTERFACE_MODE_SGMII,
|
|
.supported = xpcs_sgmii_features,
|
|
.an_mode = DW_AN_C37_SGMII,
|
|
.pma_config = nxp_sja1110_sgmii_pma_config,
|
|
}, {
|
|
.interface = PHY_INTERFACE_MODE_2500BASEX,
|
|
.supported = xpcs_2500basex_features,
|
|
.an_mode = DW_2500BASEX,
|
|
.pma_config = nxp_sja1110_2500basex_pma_config,
|
|
}, {
|
|
}
|
|
};
|
|
|
|
static const struct dw_xpcs_desc xpcs_desc_list[] = {
|
|
{
|
|
.id = DW_XPCS_ID,
|
|
.mask = DW_XPCS_ID_MASK,
|
|
.compat = synopsys_xpcs_compat,
|
|
}, {
|
|
.id = NXP_SJA1105_XPCS_ID,
|
|
.mask = DW_XPCS_ID_MASK,
|
|
.compat = nxp_sja1105_xpcs_compat,
|
|
}, {
|
|
.id = NXP_SJA1110_XPCS_ID,
|
|
.mask = DW_XPCS_ID_MASK,
|
|
.compat = nxp_sja1110_xpcs_compat,
|
|
},
|
|
};
|
|
|
|
static const struct phylink_pcs_ops xpcs_phylink_ops = {
|
|
.pcs_validate = xpcs_validate,
|
|
.pcs_inband_caps = xpcs_inband_caps,
|
|
.pcs_pre_config = xpcs_pre_config,
|
|
.pcs_config = xpcs_config,
|
|
.pcs_get_state = xpcs_get_state,
|
|
.pcs_an_restart = xpcs_an_restart,
|
|
.pcs_link_up = xpcs_link_up,
|
|
.pcs_disable_eee = xpcs_disable_eee,
|
|
.pcs_enable_eee = xpcs_enable_eee,
|
|
};
|
|
|
|
static int xpcs_identify(struct dw_xpcs *xpcs)
|
|
{
|
|
int i, ret;
|
|
|
|
ret = xpcs_read_ids(xpcs);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(xpcs_desc_list); i++) {
|
|
const struct dw_xpcs_desc *entry = &xpcs_desc_list[i];
|
|
|
|
if ((xpcs->info.pcs & entry->mask) == entry->id) {
|
|
xpcs->desc = entry;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return -ENODEV;
|
|
}
|
|
|
|
static struct dw_xpcs *xpcs_create_data(struct mdio_device *mdiodev)
|
|
{
|
|
struct dw_xpcs *xpcs;
|
|
|
|
xpcs = kzalloc(sizeof(*xpcs), GFP_KERNEL);
|
|
if (!xpcs)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
mdio_device_get(mdiodev);
|
|
xpcs->mdiodev = mdiodev;
|
|
xpcs->pcs.ops = &xpcs_phylink_ops;
|
|
xpcs->pcs.poll = true;
|
|
|
|
return xpcs;
|
|
}
|
|
|
|
static void xpcs_free_data(struct dw_xpcs *xpcs)
|
|
{
|
|
mdio_device_put(xpcs->mdiodev);
|
|
kfree(xpcs);
|
|
}
|
|
|
|
static int xpcs_init_clks(struct dw_xpcs *xpcs)
|
|
{
|
|
static const char *ids[DW_XPCS_NUM_CLKS] = {
|
|
[DW_XPCS_CORE_CLK] = "core",
|
|
[DW_XPCS_PAD_CLK] = "pad",
|
|
};
|
|
struct device *dev = &xpcs->mdiodev->dev;
|
|
int ret, i;
|
|
|
|
for (i = 0; i < DW_XPCS_NUM_CLKS; ++i)
|
|
xpcs->clks[i].id = ids[i];
|
|
|
|
ret = clk_bulk_get_optional(dev, DW_XPCS_NUM_CLKS, xpcs->clks);
|
|
if (ret)
|
|
return dev_err_probe(dev, ret, "Failed to get clocks\n");
|
|
|
|
ret = clk_bulk_prepare_enable(DW_XPCS_NUM_CLKS, xpcs->clks);
|
|
if (ret)
|
|
return dev_err_probe(dev, ret, "Failed to enable clocks\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void xpcs_clear_clks(struct dw_xpcs *xpcs)
|
|
{
|
|
clk_bulk_disable_unprepare(DW_XPCS_NUM_CLKS, xpcs->clks);
|
|
|
|
clk_bulk_put(DW_XPCS_NUM_CLKS, xpcs->clks);
|
|
}
|
|
|
|
static int xpcs_init_id(struct dw_xpcs *xpcs)
|
|
{
|
|
const struct dw_xpcs_info *info;
|
|
|
|
info = dev_get_platdata(&xpcs->mdiodev->dev);
|
|
if (!info) {
|
|
xpcs->info.pcs = DW_XPCS_ID_NATIVE;
|
|
xpcs->info.pma = DW_XPCS_PMA_ID_NATIVE;
|
|
} else {
|
|
xpcs->info = *info;
|
|
}
|
|
|
|
return xpcs_identify(xpcs);
|
|
}
|
|
|
|
static struct dw_xpcs *xpcs_create(struct mdio_device *mdiodev)
|
|
{
|
|
struct dw_xpcs *xpcs;
|
|
int ret;
|
|
|
|
xpcs = xpcs_create_data(mdiodev);
|
|
if (IS_ERR(xpcs))
|
|
return xpcs;
|
|
|
|
ret = xpcs_init_clks(xpcs);
|
|
if (ret)
|
|
goto out_free_data;
|
|
|
|
ret = xpcs_init_id(xpcs);
|
|
if (ret)
|
|
goto out_clear_clks;
|
|
|
|
xpcs_get_interfaces(xpcs, xpcs->pcs.supported_interfaces);
|
|
|
|
if (xpcs->info.pma == WX_TXGBE_XPCS_PMA_10G_ID ||
|
|
xpcs->info.pma == MP_FBNIC_XPCS_PMA_100G_ID)
|
|
xpcs->pcs.poll = false;
|
|
else
|
|
xpcs->need_reset = true;
|
|
|
|
return xpcs;
|
|
|
|
out_clear_clks:
|
|
xpcs_clear_clks(xpcs);
|
|
|
|
out_free_data:
|
|
xpcs_free_data(xpcs);
|
|
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
/**
|
|
* xpcs_create_mdiodev() - create a DW xPCS instance with the MDIO @addr
|
|
* @bus: pointer to the MDIO-bus descriptor for the device to be looked at
|
|
* @addr: device MDIO-bus ID
|
|
*
|
|
* Return: a pointer to the DW XPCS handle if successful, otherwise -ENODEV if
|
|
* the PCS device couldn't be found on the bus and other negative errno related
|
|
* to the data allocation and MDIO-bus communications.
|
|
*/
|
|
struct dw_xpcs *xpcs_create_mdiodev(struct mii_bus *bus, int addr)
|
|
{
|
|
struct mdio_device *mdiodev;
|
|
struct dw_xpcs *xpcs;
|
|
|
|
mdiodev = mdio_device_create(bus, addr);
|
|
if (IS_ERR(mdiodev))
|
|
return ERR_CAST(mdiodev);
|
|
|
|
xpcs = xpcs_create(mdiodev);
|
|
|
|
/* xpcs_create() has taken a refcount on the mdiodev if it was
|
|
* successful. If xpcs_create() fails, this will free the mdio
|
|
* device here. In any case, we don't need to hold our reference
|
|
* anymore, and putting it here will allow mdio_device_put() in
|
|
* xpcs_destroy() to automatically free the mdio device.
|
|
*/
|
|
mdio_device_put(mdiodev);
|
|
|
|
return xpcs;
|
|
}
|
|
EXPORT_SYMBOL_GPL(xpcs_create_mdiodev);
|
|
|
|
struct phylink_pcs *xpcs_create_pcs_mdiodev(struct mii_bus *bus, int addr)
|
|
{
|
|
struct dw_xpcs *xpcs;
|
|
|
|
xpcs = xpcs_create_mdiodev(bus, addr);
|
|
if (IS_ERR(xpcs))
|
|
return ERR_CAST(xpcs);
|
|
|
|
return &xpcs->pcs;
|
|
}
|
|
EXPORT_SYMBOL_GPL(xpcs_create_pcs_mdiodev);
|
|
|
|
/**
|
|
* xpcs_create_fwnode() - Create a DW xPCS instance from @fwnode
|
|
* @fwnode: fwnode handle poining to the DW XPCS device
|
|
*
|
|
* Return: a pointer to the DW XPCS handle if successful, otherwise -ENODEV if
|
|
* the fwnode device is unavailable or the PCS device couldn't be found on the
|
|
* bus, -EPROBE_DEFER if the respective MDIO-device instance couldn't be found,
|
|
* other negative errno related to the data allocations and MDIO-bus
|
|
* communications.
|
|
*/
|
|
struct dw_xpcs *xpcs_create_fwnode(struct fwnode_handle *fwnode)
|
|
{
|
|
struct mdio_device *mdiodev;
|
|
struct dw_xpcs *xpcs;
|
|
|
|
if (!fwnode_device_is_available(fwnode))
|
|
return ERR_PTR(-ENODEV);
|
|
|
|
mdiodev = fwnode_mdio_find_device(fwnode);
|
|
if (!mdiodev)
|
|
return ERR_PTR(-EPROBE_DEFER);
|
|
|
|
xpcs = xpcs_create(mdiodev);
|
|
|
|
/* xpcs_create() has taken a refcount on the mdiodev if it was
|
|
* successful. If xpcs_create() fails, this will free the mdio
|
|
* device here. In any case, we don't need to hold our reference
|
|
* anymore, and putting it here will allow mdio_device_put() in
|
|
* xpcs_destroy() to automatically free the mdio device.
|
|
*/
|
|
mdio_device_put(mdiodev);
|
|
|
|
return xpcs;
|
|
}
|
|
EXPORT_SYMBOL_GPL(xpcs_create_fwnode);
|
|
|
|
void xpcs_destroy(struct dw_xpcs *xpcs)
|
|
{
|
|
if (!xpcs)
|
|
return;
|
|
|
|
xpcs_clear_clks(xpcs);
|
|
|
|
xpcs_free_data(xpcs);
|
|
}
|
|
EXPORT_SYMBOL_GPL(xpcs_destroy);
|
|
|
|
void xpcs_destroy_pcs(struct phylink_pcs *pcs)
|
|
{
|
|
xpcs_destroy(phylink_pcs_to_xpcs(pcs));
|
|
}
|
|
EXPORT_SYMBOL_GPL(xpcs_destroy_pcs);
|
|
|
|
MODULE_DESCRIPTION("Synopsys DesignWare XPCS library");
|
|
MODULE_LICENSE("GPL v2");
|