#include <linux/pcs/pcs-xpcs.h>
#include <linux/phy.h>
#include <linux/phylink.h>
#include "fbnic.h"
#include "fbnic_mac.h"
#include "fbnic_netdev.h"
static phy_interface_t fbnic_phylink_select_interface(u8 aui)
{
switch (aui) {
case FBNIC_AUI_100GAUI2:
return PHY_INTERFACE_MODE_100GBASEP;
case FBNIC_AUI_50GAUI1:
return PHY_INTERFACE_MODE_50GBASER;
case FBNIC_AUI_LAUI2:
return PHY_INTERFACE_MODE_LAUI;
case FBNIC_AUI_25GAUI:
return PHY_INTERFACE_MODE_25GBASER;
}
return PHY_INTERFACE_MODE_NA;
}
void fbnic_phylink_get_pauseparam(struct net_device *netdev,
struct ethtool_pauseparam *pause)
{
struct fbnic_net *fbn = netdev_priv(netdev);
phylink_ethtool_get_pauseparam(fbn->phylink, pause);
}
int fbnic_phylink_set_pauseparam(struct net_device *netdev,
struct ethtool_pauseparam *pause)
{
struct fbnic_net *fbn = netdev_priv(netdev);
return phylink_ethtool_set_pauseparam(fbn->phylink, pause);
}
static void
fbnic_phylink_get_supported_fec_modes(unsigned long *supported)
{
if (phylink_test(supported, 100000baseCR2_Full) ||
phylink_test(supported, 50000baseCR_Full))
phylink_set(supported, FEC_RS);
if (phylink_test(supported, 50000baseCR2_Full) ||
phylink_test(supported, 25000baseCR_Full)) {
phylink_set(supported, FEC_BASER);
phylink_set(supported, FEC_NONE);
phylink_set(supported, FEC_RS);
}
}
int fbnic_phylink_ethtool_ksettings_get(struct net_device *netdev,
struct ethtool_link_ksettings *cmd)
{
struct fbnic_net *fbn = netdev_priv(netdev);
int err;
err = phylink_ethtool_ksettings_get(fbn->phylink, cmd);
if (!err) {
unsigned long *supp = cmd->link_modes.supported;
cmd->base.port = PORT_DA;
cmd->lanes = (fbn->aui & FBNIC_AUI_MODE_R2) ? 2 : 1;
fbnic_phylink_get_supported_fec_modes(supp);
}
return err;
}
int fbnic_phylink_get_fecparam(struct net_device *netdev,
struct ethtool_fecparam *fecparam)
{
struct fbnic_net *fbn = netdev_priv(netdev);
if (fbn->fec & FBNIC_FEC_RS) {
fecparam->active_fec = ETHTOOL_FEC_RS;
fecparam->fec = ETHTOOL_FEC_RS;
} else if (fbn->fec & FBNIC_FEC_BASER) {
fecparam->active_fec = ETHTOOL_FEC_BASER;
fecparam->fec = ETHTOOL_FEC_BASER;
} else {
fecparam->active_fec = ETHTOOL_FEC_OFF;
fecparam->fec = ETHTOOL_FEC_OFF;
}
if (fbn->aui & FBNIC_AUI_MODE_PAM4)
fecparam->fec |= ETHTOOL_FEC_AUTO;
return 0;
}
static struct phylink_pcs *
fbnic_phylink_mac_select_pcs(struct phylink_config *config,
phy_interface_t interface)
{
struct net_device *netdev = to_net_dev(config->dev);
struct fbnic_net *fbn = netdev_priv(netdev);
return fbn->pcs;
}
static int
fbnic_phylink_mac_prepare(struct phylink_config *config, unsigned int mode,
phy_interface_t iface)
{
struct net_device *netdev = to_net_dev(config->dev);
struct fbnic_net *fbn = netdev_priv(netdev);
struct fbnic_dev *fbd = fbn->fbd;
fbd->mac->prepare(fbd, fbn->aui, fbn->fec);
return 0;
}
static void
fbnic_phylink_mac_config(struct phylink_config *config, unsigned int mode,
const struct phylink_link_state *state)
{
}
static int
fbnic_phylink_mac_finish(struct phylink_config *config, unsigned int mode,
phy_interface_t iface)
{
struct net_device *netdev = to_net_dev(config->dev);
struct fbnic_net *fbn = netdev_priv(netdev);
struct fbnic_dev *fbd = fbn->fbd;
fbd->mac->get_link(fbd, fbn->aui, fbn->fec);
return 0;
}
static void
fbnic_phylink_mac_link_down(struct phylink_config *config, unsigned int mode,
phy_interface_t interface)
{
struct net_device *netdev = to_net_dev(config->dev);
struct fbnic_net *fbn = netdev_priv(netdev);
struct fbnic_dev *fbd = fbn->fbd;
fbd->mac->link_down(fbd);
fbn->link_down_events++;
}
static void
fbnic_phylink_mac_link_up(struct phylink_config *config,
struct phy_device *phy, unsigned int mode,
phy_interface_t interface, int speed, int duplex,
bool tx_pause, bool rx_pause)
{
struct net_device *netdev = to_net_dev(config->dev);
struct fbnic_net *fbn = netdev_priv(netdev);
struct fbnic_dev *fbd = fbn->fbd;
fbn->tx_pause = tx_pause;
fbnic_config_drop_mode(fbn, tx_pause);
fbd->mac->link_up(fbd, tx_pause, rx_pause);
}
static const struct phylink_mac_ops fbnic_phylink_mac_ops = {
.mac_select_pcs = fbnic_phylink_mac_select_pcs,
.mac_prepare = fbnic_phylink_mac_prepare,
.mac_config = fbnic_phylink_mac_config,
.mac_finish = fbnic_phylink_mac_finish,
.mac_link_down = fbnic_phylink_mac_link_down,
.mac_link_up = fbnic_phylink_mac_link_up,
};
int fbnic_phylink_create(struct net_device *netdev)
{
struct fbnic_net *fbn = netdev_priv(netdev);
struct fbnic_dev *fbd = fbn->fbd;
struct phylink_pcs *pcs;
struct phylink *phylink;
int err;
pcs = xpcs_create_pcs_mdiodev(fbd->mdio_bus, 0);
if (IS_ERR(pcs)) {
err = PTR_ERR(pcs);
dev_err(fbd->dev, "Failed to create PCS device: %d\n", err);
return err;
}
fbn->pcs = pcs;
fbn->phylink_config.dev = &netdev->dev;
fbn->phylink_config.type = PHYLINK_NETDEV;
fbn->phylink_config.mac_capabilities = MAC_SYM_PAUSE | MAC_ASYM_PAUSE |
MAC_25000FD | MAC_50000FD |
MAC_100000FD;
fbn->phylink_config.default_an_inband = true;
__set_bit(PHY_INTERFACE_MODE_100GBASEP,
fbn->phylink_config.supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_50GBASER,
fbn->phylink_config.supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_LAUI,
fbn->phylink_config.supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_25GBASER,
fbn->phylink_config.supported_interfaces);
fbnic_mac_get_fw_settings(fbd, &fbn->aui, &fbn->fec);
phylink = phylink_create(&fbn->phylink_config, NULL,
fbnic_phylink_select_interface(fbn->aui),
&fbnic_phylink_mac_ops);
if (IS_ERR(phylink)) {
err = PTR_ERR(phylink);
dev_err(netdev->dev.parent,
"Failed to create Phylink interface, err: %d\n", err);
xpcs_destroy_pcs(pcs);
return err;
}
fbn->phylink = phylink;
return 0;
}
void fbnic_phylink_destroy(struct net_device *netdev)
{
struct fbnic_net *fbn = netdev_priv(netdev);
if (fbn->phylink)
phylink_destroy(fbn->phylink);
if (fbn->pcs)
xpcs_destroy_pcs(fbn->pcs);
}
void fbnic_phylink_pmd_training_complete_notify(struct net_device *netdev)
{
struct fbnic_net *fbn = netdev_priv(netdev);
struct fbnic_dev *fbd = fbn->fbd;
if (fbd->pmd_state != FBNIC_PMD_TRAINING)
return;
smp_rmb();
if (!time_before(READ_ONCE(fbd->end_of_pmd_training), jiffies))
return;
if (cmpxchg(&fbd->pmd_state, FBNIC_PMD_TRAINING,
FBNIC_PMD_LINK_READY) != FBNIC_PMD_TRAINING)
return;
if (!time_before(READ_ONCE(fbd->end_of_pmd_training), jiffies))
return;
if (cmpxchg(&fbd->pmd_state, FBNIC_PMD_LINK_READY,
FBNIC_PMD_SEND_DATA) != FBNIC_PMD_LINK_READY)
return;
phylink_pcs_change(fbn->pcs, false);
}