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 "sparx5_main_regs.h"
862306a36Sopenharmony_ci#include "sparx5_main.h"
962306a36Sopenharmony_ci#include "sparx5_port.h"
1062306a36Sopenharmony_ci#include "sparx5_tc.h"
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci/* The IFH bit position of the first VSTAX bit. This is because the
1362306a36Sopenharmony_ci * VSTAX bit positions in Data sheet is starting from zero.
1462306a36Sopenharmony_ci */
1562306a36Sopenharmony_ci#define VSTAX 73
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#define ifh_encode_bitfield(ifh, value, pos, _width)			\
1862306a36Sopenharmony_ci	({								\
1962306a36Sopenharmony_ci		u32 width = (_width);					\
2062306a36Sopenharmony_ci									\
2162306a36Sopenharmony_ci		/* Max width is 5 bytes - 40 bits. In worst case this will
2262306a36Sopenharmony_ci		 * spread over 6 bytes - 48 bits
2362306a36Sopenharmony_ci		 */							\
2462306a36Sopenharmony_ci		compiletime_assert(width <= 40,				\
2562306a36Sopenharmony_ci				   "Unsupported width, must be <= 40");	\
2662306a36Sopenharmony_ci		__ifh_encode_bitfield((ifh), (value), (pos), width);	\
2762306a36Sopenharmony_ci	})
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_cistatic void __ifh_encode_bitfield(void *ifh, u64 value, u32 pos, u32 width)
3062306a36Sopenharmony_ci{
3162306a36Sopenharmony_ci	u8 *ifh_hdr = ifh;
3262306a36Sopenharmony_ci	/* Calculate the Start IFH byte position of this IFH bit position */
3362306a36Sopenharmony_ci	u32 byte = (35 - (pos / 8));
3462306a36Sopenharmony_ci	/* Calculate the Start bit position in the Start IFH byte */
3562306a36Sopenharmony_ci	u32 bit  = (pos % 8);
3662306a36Sopenharmony_ci	u64 encode = GENMASK_ULL(bit + width - 1, bit) & (value << bit);
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci	/* The b0-b7 goes into the start IFH byte */
3962306a36Sopenharmony_ci	if (encode & 0xFF)
4062306a36Sopenharmony_ci		ifh_hdr[byte] |= (u8)((encode & 0xFF));
4162306a36Sopenharmony_ci	/* The b8-b15 goes into the next IFH byte */
4262306a36Sopenharmony_ci	if (encode & 0xFF00)
4362306a36Sopenharmony_ci		ifh_hdr[byte - 1] |= (u8)((encode & 0xFF00) >> 8);
4462306a36Sopenharmony_ci	/* The b16-b23 goes into the next IFH byte */
4562306a36Sopenharmony_ci	if (encode & 0xFF0000)
4662306a36Sopenharmony_ci		ifh_hdr[byte - 2] |= (u8)((encode & 0xFF0000) >> 16);
4762306a36Sopenharmony_ci	/* The b24-b31 goes into the next IFH byte */
4862306a36Sopenharmony_ci	if (encode & 0xFF000000)
4962306a36Sopenharmony_ci		ifh_hdr[byte - 3] |= (u8)((encode & 0xFF000000) >> 24);
5062306a36Sopenharmony_ci	/* The b32-b39 goes into the next IFH byte */
5162306a36Sopenharmony_ci	if (encode & 0xFF00000000)
5262306a36Sopenharmony_ci		ifh_hdr[byte - 4] |= (u8)((encode & 0xFF00000000) >> 32);
5362306a36Sopenharmony_ci	/* The b40-b47 goes into the next IFH byte */
5462306a36Sopenharmony_ci	if (encode & 0xFF0000000000)
5562306a36Sopenharmony_ci		ifh_hdr[byte - 5] |= (u8)((encode & 0xFF0000000000) >> 40);
5662306a36Sopenharmony_ci}
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_civoid sparx5_set_port_ifh(void *ifh_hdr, u16 portno)
5962306a36Sopenharmony_ci{
6062306a36Sopenharmony_ci	/* VSTAX.RSV = 1. MSBit must be 1 */
6162306a36Sopenharmony_ci	ifh_encode_bitfield(ifh_hdr, 1, VSTAX + 79,  1);
6262306a36Sopenharmony_ci	/* VSTAX.INGR_DROP_MODE = Enable. Don't make head-of-line blocking */
6362306a36Sopenharmony_ci	ifh_encode_bitfield(ifh_hdr, 1, VSTAX + 55,  1);
6462306a36Sopenharmony_ci	/* MISC.CPU_MASK/DPORT = Destination port */
6562306a36Sopenharmony_ci	ifh_encode_bitfield(ifh_hdr, portno,   29, 8);
6662306a36Sopenharmony_ci	/* MISC.PIPELINE_PT */
6762306a36Sopenharmony_ci	ifh_encode_bitfield(ifh_hdr, 16,       37, 5);
6862306a36Sopenharmony_ci	/* MISC.PIPELINE_ACT */
6962306a36Sopenharmony_ci	ifh_encode_bitfield(ifh_hdr, 1,        42, 3);
7062306a36Sopenharmony_ci	/* FWD.SRC_PORT = CPU */
7162306a36Sopenharmony_ci	ifh_encode_bitfield(ifh_hdr, SPX5_PORT_CPU, 46, 7);
7262306a36Sopenharmony_ci	/* FWD.SFLOW_ID (disable SFlow sampling) */
7362306a36Sopenharmony_ci	ifh_encode_bitfield(ifh_hdr, 124,      57, 7);
7462306a36Sopenharmony_ci	/* FWD.UPDATE_FCS = Enable. Enforce update of FCS. */
7562306a36Sopenharmony_ci	ifh_encode_bitfield(ifh_hdr, 1,        67, 1);
7662306a36Sopenharmony_ci}
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_civoid sparx5_set_port_ifh_rew_op(void *ifh_hdr, u32 rew_op)
7962306a36Sopenharmony_ci{
8062306a36Sopenharmony_ci	ifh_encode_bitfield(ifh_hdr, rew_op, VSTAX + 32,  10);
8162306a36Sopenharmony_ci}
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_civoid sparx5_set_port_ifh_pdu_type(void *ifh_hdr, u32 pdu_type)
8462306a36Sopenharmony_ci{
8562306a36Sopenharmony_ci	ifh_encode_bitfield(ifh_hdr, pdu_type, 191, 4);
8662306a36Sopenharmony_ci}
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_civoid sparx5_set_port_ifh_pdu_w16_offset(void *ifh_hdr, u32 pdu_w16_offset)
8962306a36Sopenharmony_ci{
9062306a36Sopenharmony_ci	ifh_encode_bitfield(ifh_hdr, pdu_w16_offset, 195, 6);
9162306a36Sopenharmony_ci}
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_civoid sparx5_set_port_ifh_timestamp(void *ifh_hdr, u64 timestamp)
9462306a36Sopenharmony_ci{
9562306a36Sopenharmony_ci	ifh_encode_bitfield(ifh_hdr, timestamp, 232,  40);
9662306a36Sopenharmony_ci}
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_cistatic int sparx5_port_open(struct net_device *ndev)
9962306a36Sopenharmony_ci{
10062306a36Sopenharmony_ci	struct sparx5_port *port = netdev_priv(ndev);
10162306a36Sopenharmony_ci	int err = 0;
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	sparx5_port_enable(port, true);
10462306a36Sopenharmony_ci	err = phylink_of_phy_connect(port->phylink, port->of_node, 0);
10562306a36Sopenharmony_ci	if (err) {
10662306a36Sopenharmony_ci		netdev_err(ndev, "Could not attach to PHY\n");
10762306a36Sopenharmony_ci		goto err_connect;
10862306a36Sopenharmony_ci	}
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	phylink_start(port->phylink);
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	if (!ndev->phydev) {
11362306a36Sopenharmony_ci		/* power up serdes */
11462306a36Sopenharmony_ci		port->conf.power_down = false;
11562306a36Sopenharmony_ci		if (port->conf.serdes_reset)
11662306a36Sopenharmony_ci			err = sparx5_serdes_set(port->sparx5, port, &port->conf);
11762306a36Sopenharmony_ci		else
11862306a36Sopenharmony_ci			err = phy_power_on(port->serdes);
11962306a36Sopenharmony_ci		if (err) {
12062306a36Sopenharmony_ci			netdev_err(ndev, "%s failed\n", __func__);
12162306a36Sopenharmony_ci			goto out_power;
12262306a36Sopenharmony_ci		}
12362306a36Sopenharmony_ci	}
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	return 0;
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ciout_power:
12862306a36Sopenharmony_ci	phylink_stop(port->phylink);
12962306a36Sopenharmony_ci	phylink_disconnect_phy(port->phylink);
13062306a36Sopenharmony_cierr_connect:
13162306a36Sopenharmony_ci	sparx5_port_enable(port, false);
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	return err;
13462306a36Sopenharmony_ci}
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_cistatic int sparx5_port_stop(struct net_device *ndev)
13762306a36Sopenharmony_ci{
13862306a36Sopenharmony_ci	struct sparx5_port *port = netdev_priv(ndev);
13962306a36Sopenharmony_ci	int err = 0;
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci	sparx5_port_enable(port, false);
14262306a36Sopenharmony_ci	phylink_stop(port->phylink);
14362306a36Sopenharmony_ci	phylink_disconnect_phy(port->phylink);
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	if (!ndev->phydev) {
14662306a36Sopenharmony_ci		/* power down serdes */
14762306a36Sopenharmony_ci		port->conf.power_down = true;
14862306a36Sopenharmony_ci		if (port->conf.serdes_reset)
14962306a36Sopenharmony_ci			err = sparx5_serdes_set(port->sparx5, port, &port->conf);
15062306a36Sopenharmony_ci		else
15162306a36Sopenharmony_ci			err = phy_power_off(port->serdes);
15262306a36Sopenharmony_ci		if (err)
15362306a36Sopenharmony_ci			netdev_err(ndev, "%s failed\n", __func__);
15462306a36Sopenharmony_ci	}
15562306a36Sopenharmony_ci	return 0;
15662306a36Sopenharmony_ci}
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_cistatic void sparx5_set_rx_mode(struct net_device *dev)
15962306a36Sopenharmony_ci{
16062306a36Sopenharmony_ci	struct sparx5_port *port = netdev_priv(dev);
16162306a36Sopenharmony_ci	struct sparx5 *sparx5 = port->sparx5;
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci	if (!test_bit(port->portno, sparx5->bridge_mask))
16462306a36Sopenharmony_ci		__dev_mc_sync(dev, sparx5_mc_sync, sparx5_mc_unsync);
16562306a36Sopenharmony_ci}
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_cistatic int sparx5_port_get_phys_port_name(struct net_device *dev,
16862306a36Sopenharmony_ci					  char *buf, size_t len)
16962306a36Sopenharmony_ci{
17062306a36Sopenharmony_ci	struct sparx5_port *port = netdev_priv(dev);
17162306a36Sopenharmony_ci	int ret;
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	ret = snprintf(buf, len, "p%d", port->portno);
17462306a36Sopenharmony_ci	if (ret >= len)
17562306a36Sopenharmony_ci		return -EINVAL;
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci	return 0;
17862306a36Sopenharmony_ci}
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_cistatic int sparx5_set_mac_address(struct net_device *dev, void *p)
18162306a36Sopenharmony_ci{
18262306a36Sopenharmony_ci	struct sparx5_port *port = netdev_priv(dev);
18362306a36Sopenharmony_ci	struct sparx5 *sparx5 = port->sparx5;
18462306a36Sopenharmony_ci	const struct sockaddr *addr = p;
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	if (!is_valid_ether_addr(addr->sa_data))
18762306a36Sopenharmony_ci		return -EADDRNOTAVAIL;
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	/* Remove current */
19062306a36Sopenharmony_ci	sparx5_mact_forget(sparx5, dev->dev_addr,  port->pvid);
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci	/* Add new */
19362306a36Sopenharmony_ci	sparx5_mact_learn(sparx5, PGID_CPU, addr->sa_data, port->pvid);
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	/* Record the address */
19662306a36Sopenharmony_ci	eth_hw_addr_set(dev, addr->sa_data);
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci	return 0;
19962306a36Sopenharmony_ci}
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_cistatic int sparx5_get_port_parent_id(struct net_device *dev,
20262306a36Sopenharmony_ci				     struct netdev_phys_item_id *ppid)
20362306a36Sopenharmony_ci{
20462306a36Sopenharmony_ci	struct sparx5_port *sparx5_port = netdev_priv(dev);
20562306a36Sopenharmony_ci	struct sparx5 *sparx5 = sparx5_port->sparx5;
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci	ppid->id_len = sizeof(sparx5->base_mac);
20862306a36Sopenharmony_ci	memcpy(&ppid->id, &sparx5->base_mac, ppid->id_len);
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci	return 0;
21162306a36Sopenharmony_ci}
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_cistatic int sparx5_port_hwtstamp_get(struct net_device *dev,
21462306a36Sopenharmony_ci				    struct kernel_hwtstamp_config *cfg)
21562306a36Sopenharmony_ci{
21662306a36Sopenharmony_ci	struct sparx5_port *sparx5_port = netdev_priv(dev);
21762306a36Sopenharmony_ci	struct sparx5 *sparx5 = sparx5_port->sparx5;
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci	if (!sparx5->ptp)
22062306a36Sopenharmony_ci		return -EOPNOTSUPP;
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	sparx5_ptp_hwtstamp_get(sparx5_port, cfg);
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci	return 0;
22562306a36Sopenharmony_ci}
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_cistatic int sparx5_port_hwtstamp_set(struct net_device *dev,
22862306a36Sopenharmony_ci				    struct kernel_hwtstamp_config *cfg,
22962306a36Sopenharmony_ci				    struct netlink_ext_ack *extack)
23062306a36Sopenharmony_ci{
23162306a36Sopenharmony_ci	struct sparx5_port *sparx5_port = netdev_priv(dev);
23262306a36Sopenharmony_ci	struct sparx5 *sparx5 = sparx5_port->sparx5;
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci	if (!sparx5->ptp)
23562306a36Sopenharmony_ci		return -EOPNOTSUPP;
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_ci	return sparx5_ptp_hwtstamp_set(sparx5_port, cfg, extack);
23862306a36Sopenharmony_ci}
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_cistatic const struct net_device_ops sparx5_port_netdev_ops = {
24162306a36Sopenharmony_ci	.ndo_open               = sparx5_port_open,
24262306a36Sopenharmony_ci	.ndo_stop               = sparx5_port_stop,
24362306a36Sopenharmony_ci	.ndo_start_xmit         = sparx5_port_xmit_impl,
24462306a36Sopenharmony_ci	.ndo_set_rx_mode        = sparx5_set_rx_mode,
24562306a36Sopenharmony_ci	.ndo_get_phys_port_name = sparx5_port_get_phys_port_name,
24662306a36Sopenharmony_ci	.ndo_set_mac_address    = sparx5_set_mac_address,
24762306a36Sopenharmony_ci	.ndo_validate_addr      = eth_validate_addr,
24862306a36Sopenharmony_ci	.ndo_get_stats64        = sparx5_get_stats64,
24962306a36Sopenharmony_ci	.ndo_get_port_parent_id = sparx5_get_port_parent_id,
25062306a36Sopenharmony_ci	.ndo_eth_ioctl          = phy_do_ioctl,
25162306a36Sopenharmony_ci	.ndo_setup_tc           = sparx5_port_setup_tc,
25262306a36Sopenharmony_ci	.ndo_hwtstamp_get       = sparx5_port_hwtstamp_get,
25362306a36Sopenharmony_ci	.ndo_hwtstamp_set       = sparx5_port_hwtstamp_set,
25462306a36Sopenharmony_ci};
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_cibool sparx5_netdevice_check(const struct net_device *dev)
25762306a36Sopenharmony_ci{
25862306a36Sopenharmony_ci	return dev && (dev->netdev_ops == &sparx5_port_netdev_ops);
25962306a36Sopenharmony_ci}
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_cistruct net_device *sparx5_create_netdev(struct sparx5 *sparx5, u32 portno)
26262306a36Sopenharmony_ci{
26362306a36Sopenharmony_ci	struct sparx5_port *spx5_port;
26462306a36Sopenharmony_ci	struct net_device *ndev;
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci	ndev = devm_alloc_etherdev_mqs(sparx5->dev, sizeof(struct sparx5_port),
26762306a36Sopenharmony_ci				       SPX5_PRIOS, 1);
26862306a36Sopenharmony_ci	if (!ndev)
26962306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_ci	ndev->hw_features |= NETIF_F_HW_TC;
27262306a36Sopenharmony_ci	ndev->features |= NETIF_F_HW_TC;
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_ci	SET_NETDEV_DEV(ndev, sparx5->dev);
27562306a36Sopenharmony_ci	spx5_port = netdev_priv(ndev);
27662306a36Sopenharmony_ci	spx5_port->ndev = ndev;
27762306a36Sopenharmony_ci	spx5_port->sparx5 = sparx5;
27862306a36Sopenharmony_ci	spx5_port->portno = portno;
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci	ndev->netdev_ops = &sparx5_port_netdev_ops;
28162306a36Sopenharmony_ci	ndev->ethtool_ops = &sparx5_ethtool_ops;
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci	eth_hw_addr_gen(ndev, sparx5->base_mac, portno + 1);
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_ci	return ndev;
28662306a36Sopenharmony_ci}
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_ciint sparx5_register_netdevs(struct sparx5 *sparx5)
28962306a36Sopenharmony_ci{
29062306a36Sopenharmony_ci	int portno;
29162306a36Sopenharmony_ci	int err;
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ci	for (portno = 0; portno < SPX5_PORTS; portno++)
29462306a36Sopenharmony_ci		if (sparx5->ports[portno]) {
29562306a36Sopenharmony_ci			err = register_netdev(sparx5->ports[portno]->ndev);
29662306a36Sopenharmony_ci			if (err) {
29762306a36Sopenharmony_ci				dev_err(sparx5->dev,
29862306a36Sopenharmony_ci					"port: %02u: netdev registration failed\n",
29962306a36Sopenharmony_ci					portno);
30062306a36Sopenharmony_ci				return err;
30162306a36Sopenharmony_ci			}
30262306a36Sopenharmony_ci			sparx5_port_inj_timer_setup(sparx5->ports[portno]);
30362306a36Sopenharmony_ci		}
30462306a36Sopenharmony_ci	return 0;
30562306a36Sopenharmony_ci}
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_civoid sparx5_destroy_netdevs(struct sparx5 *sparx5)
30862306a36Sopenharmony_ci{
30962306a36Sopenharmony_ci	struct sparx5_port *port;
31062306a36Sopenharmony_ci	int portno;
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci	for (portno = 0; portno < SPX5_PORTS; portno++) {
31362306a36Sopenharmony_ci		port = sparx5->ports[portno];
31462306a36Sopenharmony_ci		if (port && port->phylink) {
31562306a36Sopenharmony_ci			/* Disconnect the phy */
31662306a36Sopenharmony_ci			rtnl_lock();
31762306a36Sopenharmony_ci			sparx5_port_stop(port->ndev);
31862306a36Sopenharmony_ci			phylink_disconnect_phy(port->phylink);
31962306a36Sopenharmony_ci			rtnl_unlock();
32062306a36Sopenharmony_ci			phylink_destroy(port->phylink);
32162306a36Sopenharmony_ci			port->phylink = NULL;
32262306a36Sopenharmony_ci		}
32362306a36Sopenharmony_ci	}
32462306a36Sopenharmony_ci}
32562306a36Sopenharmony_ci
32662306a36Sopenharmony_civoid sparx5_unregister_netdevs(struct sparx5 *sparx5)
32762306a36Sopenharmony_ci{
32862306a36Sopenharmony_ci	int portno;
32962306a36Sopenharmony_ci
33062306a36Sopenharmony_ci	for (portno = 0; portno < SPX5_PORTS; portno++)
33162306a36Sopenharmony_ci		if (sparx5->ports[portno])
33262306a36Sopenharmony_ci			unregister_netdev(sparx5->ports[portno]->ndev);
33362306a36Sopenharmony_ci}
33462306a36Sopenharmony_ci
335