| // SPDX-License-Identifier: GPL-2.0 |
| /* Copyright (c) Meta Platforms, Inc. and affiliates. */ |
| |
| #include <linux/phy.h> |
| #include <linux/phylink.h> |
| |
| #include "fbnic.h" |
| #include "fbnic_mac.h" |
| #include "fbnic_netdev.h" |
| |
| static struct fbnic_net * |
| fbnic_pcs_to_net(struct phylink_pcs *pcs) |
| { |
| return container_of(pcs, struct fbnic_net, phylink_pcs); |
| } |
| |
| static void |
| fbnic_phylink_pcs_get_state(struct phylink_pcs *pcs, |
| struct phylink_link_state *state) |
| { |
| struct fbnic_net *fbn = fbnic_pcs_to_net(pcs); |
| struct fbnic_dev *fbd = fbn->fbd; |
| |
| /* For now we use hard-coded defaults and FW config to determine |
| * the current values. In future patches we will add support for |
| * reconfiguring these values and changing link settings. |
| */ |
| switch (fbd->fw_cap.link_speed) { |
| case FBNIC_FW_LINK_SPEED_25R1: |
| state->speed = SPEED_25000; |
| break; |
| case FBNIC_FW_LINK_SPEED_50R2: |
| state->speed = SPEED_50000; |
| break; |
| case FBNIC_FW_LINK_SPEED_100R2: |
| state->speed = SPEED_100000; |
| break; |
| default: |
| state->speed = SPEED_UNKNOWN; |
| break; |
| } |
| |
| state->duplex = DUPLEX_FULL; |
| |
| state->link = fbd->mac->pcs_get_link(fbd); |
| } |
| |
| static int |
| fbnic_phylink_pcs_enable(struct phylink_pcs *pcs) |
| { |
| struct fbnic_net *fbn = fbnic_pcs_to_net(pcs); |
| struct fbnic_dev *fbd = fbn->fbd; |
| |
| return fbd->mac->pcs_enable(fbd); |
| } |
| |
| static void |
| fbnic_phylink_pcs_disable(struct phylink_pcs *pcs) |
| { |
| struct fbnic_net *fbn = fbnic_pcs_to_net(pcs); |
| struct fbnic_dev *fbd = fbn->fbd; |
| |
| return fbd->mac->pcs_disable(fbd); |
| } |
| |
| static int |
| fbnic_phylink_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode, |
| phy_interface_t interface, |
| const unsigned long *advertising, |
| bool permit_pause_to_mac) |
| { |
| return 0; |
| } |
| |
| static const struct phylink_pcs_ops fbnic_phylink_pcs_ops = { |
| .pcs_config = fbnic_phylink_pcs_config, |
| .pcs_enable = fbnic_phylink_pcs_enable, |
| .pcs_disable = fbnic_phylink_pcs_disable, |
| .pcs_get_state = fbnic_phylink_pcs_get_state, |
| }; |
| |
| 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->phylink_pcs; |
| } |
| |
| static void |
| fbnic_phylink_mac_config(struct phylink_config *config, unsigned int mode, |
| const struct phylink_link_state *state) |
| { |
| } |
| |
| 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; |
| |
| 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_config = fbnic_phylink_mac_config, |
| .mac_link_down = fbnic_phylink_mac_link_down, |
| .mac_link_up = fbnic_phylink_mac_link_up, |
| }; |
| |
| int fbnic_phylink_init(struct net_device *netdev) |
| { |
| struct fbnic_net *fbn = netdev_priv(netdev); |
| struct phylink *phylink; |
| |
| fbn->phylink_pcs.neg_mode = true; |
| fbn->phylink_pcs.ops = &fbnic_phylink_pcs_ops; |
| |
| fbn->phylink_config.dev = &netdev->dev; |
| fbn->phylink_config.type = PHYLINK_NETDEV; |
| fbn->phylink_config.mac_capabilities = MAC_SYM_PAUSE | MAC_ASYM_PAUSE | |
| MAC_10000FD | MAC_25000FD | |
| MAC_40000FD | MAC_50000FD | |
| MAC_100000FD; |
| fbn->phylink_config.default_an_inband = true; |
| |
| __set_bit(PHY_INTERFACE_MODE_XGMII, |
| fbn->phylink_config.supported_interfaces); |
| __set_bit(PHY_INTERFACE_MODE_XLGMII, |
| fbn->phylink_config.supported_interfaces); |
| |
| phylink = phylink_create(&fbn->phylink_config, NULL, |
| PHY_INTERFACE_MODE_XLGMII, |
| &fbnic_phylink_mac_ops); |
| if (IS_ERR(phylink)) |
| return PTR_ERR(phylink); |
| |
| fbn->phylink = phylink; |
| |
| return 0; |
| } |