| // SPDX-License-Identifier: GPL-2.0+ |
| /* Microchip Sparx5 Switch driver |
| * |
| * Copyright (c) 2021 Microchip Technology Inc. and its subsidiaries. |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/phylink.h> |
| #include <linux/device.h> |
| #include <linux/netdevice.h> |
| #include <linux/sfp.h> |
| |
| #include "sparx5_main_regs.h" |
| #include "sparx5_main.h" |
| #include "sparx5_port.h" |
| |
| static bool port_conf_has_changed(struct sparx5_port_config *a, struct sparx5_port_config *b) |
| { |
| if (a->speed != b->speed || |
| a->portmode != b->portmode || |
| a->autoneg != b->autoneg || |
| a->pause_adv != b->pause_adv || |
| a->power_down != b->power_down || |
| a->media != b->media) |
| return true; |
| return false; |
| } |
| |
| static struct phylink_pcs * |
| sparx5_phylink_mac_select_pcs(struct phylink_config *config, |
| phy_interface_t interface) |
| { |
| struct sparx5_port *port = netdev_priv(to_net_dev(config->dev)); |
| |
| return &port->phylink_pcs; |
| } |
| |
| static void sparx5_phylink_mac_config(struct phylink_config *config, |
| unsigned int mode, |
| const struct phylink_link_state *state) |
| { |
| /* Currently not used */ |
| } |
| |
| static void sparx5_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 sparx5_port *port = netdev_priv(to_net_dev(config->dev)); |
| struct sparx5_port_config conf; |
| int err; |
| |
| conf = port->conf; |
| conf.duplex = duplex; |
| conf.pause = 0; |
| conf.pause |= tx_pause ? MLO_PAUSE_TX : 0; |
| conf.pause |= rx_pause ? MLO_PAUSE_RX : 0; |
| conf.speed = speed; |
| /* Configure the port to speed/duplex/pause */ |
| err = sparx5_port_config(port->sparx5, port, &conf); |
| if (err) |
| netdev_err(port->ndev, "port config failed: %d\n", err); |
| } |
| |
| static void sparx5_phylink_mac_link_down(struct phylink_config *config, |
| unsigned int mode, |
| phy_interface_t interface) |
| { |
| /* Currently not used */ |
| } |
| |
| static struct sparx5_port *sparx5_pcs_to_port(struct phylink_pcs *pcs) |
| { |
| return container_of(pcs, struct sparx5_port, phylink_pcs); |
| } |
| |
| static void sparx5_pcs_get_state(struct phylink_pcs *pcs, |
| struct phylink_link_state *state) |
| { |
| struct sparx5_port *port = sparx5_pcs_to_port(pcs); |
| struct sparx5_port_status status; |
| |
| sparx5_get_port_status(port->sparx5, port, &status); |
| state->link = status.link && !status.link_down; |
| state->an_complete = status.an_complete; |
| state->speed = status.speed; |
| state->duplex = status.duplex; |
| state->pause = status.pause; |
| } |
| |
| static int sparx5_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode, |
| phy_interface_t interface, |
| const unsigned long *advertising, |
| bool permit_pause_to_mac) |
| { |
| struct sparx5_port *port = sparx5_pcs_to_port(pcs); |
| struct sparx5_port_config conf; |
| int ret = 0; |
| |
| conf = port->conf; |
| conf.power_down = false; |
| conf.portmode = interface; |
| conf.inband = neg_mode == PHYLINK_PCS_NEG_INBAND_DISABLED || |
| neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED; |
| conf.autoneg = neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED; |
| conf.pause_adv = 0; |
| if (phylink_test(advertising, Pause)) |
| conf.pause_adv |= ADVERTISE_1000XPAUSE; |
| if (phylink_test(advertising, Asym_Pause)) |
| conf.pause_adv |= ADVERTISE_1000XPSE_ASYM; |
| if (sparx5_is_baser(interface)) { |
| if (phylink_test(advertising, FIBRE)) |
| conf.media = PHY_MEDIA_SR; |
| else |
| conf.media = PHY_MEDIA_DAC; |
| } |
| if (!port_conf_has_changed(&port->conf, &conf)) |
| return ret; |
| /* Enable the PCS matching this interface type */ |
| ret = sparx5_port_pcs_set(port->sparx5, port, &conf); |
| if (ret) |
| netdev_err(port->ndev, "port PCS config failed: %d\n", ret); |
| return ret; |
| } |
| |
| static void sparx5_pcs_aneg_restart(struct phylink_pcs *pcs) |
| { |
| /* Currently not used */ |
| } |
| |
| const struct phylink_pcs_ops sparx5_phylink_pcs_ops = { |
| .pcs_get_state = sparx5_pcs_get_state, |
| .pcs_config = sparx5_pcs_config, |
| .pcs_an_restart = sparx5_pcs_aneg_restart, |
| }; |
| |
| const struct phylink_mac_ops sparx5_phylink_mac_ops = { |
| .mac_select_pcs = sparx5_phylink_mac_select_pcs, |
| .mac_config = sparx5_phylink_mac_config, |
| .mac_link_down = sparx5_phylink_mac_link_down, |
| .mac_link_up = sparx5_phylink_mac_link_up, |
| }; |