162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci/* Microchip Sparx5 Switch driver 362306a36Sopenharmony_ci * 462306a36Sopenharmony_ci * Copyright (c) 2021 Microchip Technology Inc. and its subsidiaries. 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#include <linux/module.h> 862306a36Sopenharmony_ci#include <linux/phylink.h> 962306a36Sopenharmony_ci#include <linux/device.h> 1062306a36Sopenharmony_ci#include <linux/netdevice.h> 1162306a36Sopenharmony_ci#include <linux/sfp.h> 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci#include "sparx5_main_regs.h" 1462306a36Sopenharmony_ci#include "sparx5_main.h" 1562306a36Sopenharmony_ci#include "sparx5_port.h" 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_cistatic bool port_conf_has_changed(struct sparx5_port_config *a, struct sparx5_port_config *b) 1862306a36Sopenharmony_ci{ 1962306a36Sopenharmony_ci if (a->speed != b->speed || 2062306a36Sopenharmony_ci a->portmode != b->portmode || 2162306a36Sopenharmony_ci a->autoneg != b->autoneg || 2262306a36Sopenharmony_ci a->pause_adv != b->pause_adv || 2362306a36Sopenharmony_ci a->power_down != b->power_down || 2462306a36Sopenharmony_ci a->media != b->media) 2562306a36Sopenharmony_ci return true; 2662306a36Sopenharmony_ci return false; 2762306a36Sopenharmony_ci} 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_cistatic struct phylink_pcs * 3062306a36Sopenharmony_cisparx5_phylink_mac_select_pcs(struct phylink_config *config, 3162306a36Sopenharmony_ci phy_interface_t interface) 3262306a36Sopenharmony_ci{ 3362306a36Sopenharmony_ci struct sparx5_port *port = netdev_priv(to_net_dev(config->dev)); 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci return &port->phylink_pcs; 3662306a36Sopenharmony_ci} 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_cistatic void sparx5_phylink_mac_config(struct phylink_config *config, 3962306a36Sopenharmony_ci unsigned int mode, 4062306a36Sopenharmony_ci const struct phylink_link_state *state) 4162306a36Sopenharmony_ci{ 4262306a36Sopenharmony_ci /* Currently not used */ 4362306a36Sopenharmony_ci} 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_cistatic void sparx5_phylink_mac_link_up(struct phylink_config *config, 4662306a36Sopenharmony_ci struct phy_device *phy, 4762306a36Sopenharmony_ci unsigned int mode, 4862306a36Sopenharmony_ci phy_interface_t interface, 4962306a36Sopenharmony_ci int speed, int duplex, 5062306a36Sopenharmony_ci bool tx_pause, bool rx_pause) 5162306a36Sopenharmony_ci{ 5262306a36Sopenharmony_ci struct sparx5_port *port = netdev_priv(to_net_dev(config->dev)); 5362306a36Sopenharmony_ci struct sparx5_port_config conf; 5462306a36Sopenharmony_ci int err; 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci conf = port->conf; 5762306a36Sopenharmony_ci conf.duplex = duplex; 5862306a36Sopenharmony_ci conf.pause = 0; 5962306a36Sopenharmony_ci conf.pause |= tx_pause ? MLO_PAUSE_TX : 0; 6062306a36Sopenharmony_ci conf.pause |= rx_pause ? MLO_PAUSE_RX : 0; 6162306a36Sopenharmony_ci conf.speed = speed; 6262306a36Sopenharmony_ci /* Configure the port to speed/duplex/pause */ 6362306a36Sopenharmony_ci err = sparx5_port_config(port->sparx5, port, &conf); 6462306a36Sopenharmony_ci if (err) 6562306a36Sopenharmony_ci netdev_err(port->ndev, "port config failed: %d\n", err); 6662306a36Sopenharmony_ci} 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_cistatic void sparx5_phylink_mac_link_down(struct phylink_config *config, 6962306a36Sopenharmony_ci unsigned int mode, 7062306a36Sopenharmony_ci phy_interface_t interface) 7162306a36Sopenharmony_ci{ 7262306a36Sopenharmony_ci /* Currently not used */ 7362306a36Sopenharmony_ci} 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_cistatic struct sparx5_port *sparx5_pcs_to_port(struct phylink_pcs *pcs) 7662306a36Sopenharmony_ci{ 7762306a36Sopenharmony_ci return container_of(pcs, struct sparx5_port, phylink_pcs); 7862306a36Sopenharmony_ci} 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_cistatic void sparx5_pcs_get_state(struct phylink_pcs *pcs, 8162306a36Sopenharmony_ci struct phylink_link_state *state) 8262306a36Sopenharmony_ci{ 8362306a36Sopenharmony_ci struct sparx5_port *port = sparx5_pcs_to_port(pcs); 8462306a36Sopenharmony_ci struct sparx5_port_status status; 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci sparx5_get_port_status(port->sparx5, port, &status); 8762306a36Sopenharmony_ci state->link = status.link && !status.link_down; 8862306a36Sopenharmony_ci state->an_complete = status.an_complete; 8962306a36Sopenharmony_ci state->speed = status.speed; 9062306a36Sopenharmony_ci state->duplex = status.duplex; 9162306a36Sopenharmony_ci state->pause = status.pause; 9262306a36Sopenharmony_ci} 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_cistatic int sparx5_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode, 9562306a36Sopenharmony_ci phy_interface_t interface, 9662306a36Sopenharmony_ci const unsigned long *advertising, 9762306a36Sopenharmony_ci bool permit_pause_to_mac) 9862306a36Sopenharmony_ci{ 9962306a36Sopenharmony_ci struct sparx5_port *port = sparx5_pcs_to_port(pcs); 10062306a36Sopenharmony_ci struct sparx5_port_config conf; 10162306a36Sopenharmony_ci int ret = 0; 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci conf = port->conf; 10462306a36Sopenharmony_ci conf.power_down = false; 10562306a36Sopenharmony_ci conf.portmode = interface; 10662306a36Sopenharmony_ci conf.inband = neg_mode == PHYLINK_PCS_NEG_INBAND_DISABLED || 10762306a36Sopenharmony_ci neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED; 10862306a36Sopenharmony_ci conf.autoneg = neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED; 10962306a36Sopenharmony_ci conf.pause_adv = 0; 11062306a36Sopenharmony_ci if (phylink_test(advertising, Pause)) 11162306a36Sopenharmony_ci conf.pause_adv |= ADVERTISE_1000XPAUSE; 11262306a36Sopenharmony_ci if (phylink_test(advertising, Asym_Pause)) 11362306a36Sopenharmony_ci conf.pause_adv |= ADVERTISE_1000XPSE_ASYM; 11462306a36Sopenharmony_ci if (sparx5_is_baser(interface)) { 11562306a36Sopenharmony_ci if (phylink_test(advertising, FIBRE)) 11662306a36Sopenharmony_ci conf.media = PHY_MEDIA_SR; 11762306a36Sopenharmony_ci else 11862306a36Sopenharmony_ci conf.media = PHY_MEDIA_DAC; 11962306a36Sopenharmony_ci } 12062306a36Sopenharmony_ci if (!port_conf_has_changed(&port->conf, &conf)) 12162306a36Sopenharmony_ci return ret; 12262306a36Sopenharmony_ci /* Enable the PCS matching this interface type */ 12362306a36Sopenharmony_ci ret = sparx5_port_pcs_set(port->sparx5, port, &conf); 12462306a36Sopenharmony_ci if (ret) 12562306a36Sopenharmony_ci netdev_err(port->ndev, "port PCS config failed: %d\n", ret); 12662306a36Sopenharmony_ci return ret; 12762306a36Sopenharmony_ci} 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_cistatic void sparx5_pcs_aneg_restart(struct phylink_pcs *pcs) 13062306a36Sopenharmony_ci{ 13162306a36Sopenharmony_ci /* Currently not used */ 13262306a36Sopenharmony_ci} 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ciconst struct phylink_pcs_ops sparx5_phylink_pcs_ops = { 13562306a36Sopenharmony_ci .pcs_get_state = sparx5_pcs_get_state, 13662306a36Sopenharmony_ci .pcs_config = sparx5_pcs_config, 13762306a36Sopenharmony_ci .pcs_an_restart = sparx5_pcs_aneg_restart, 13862306a36Sopenharmony_ci}; 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ciconst struct phylink_mac_ops sparx5_phylink_mac_ops = { 14162306a36Sopenharmony_ci .mac_select_pcs = sparx5_phylink_mac_select_pcs, 14262306a36Sopenharmony_ci .mac_config = sparx5_phylink_mac_config, 14362306a36Sopenharmony_ci .mac_link_down = sparx5_phylink_mac_link_down, 14462306a36Sopenharmony_ci .mac_link_up = sparx5_phylink_mac_link_up, 14562306a36Sopenharmony_ci}; 146