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