// SPDX-License-Identifier: GPL-2.0-only OR BSD-3-Clause

/* Ethtool support for Mellanox Gigabit Ethernet driver
 *
 * Copyright (C) 2020-2021 NVIDIA CORPORATION & AFFILIATES
 */

#include <linux/phy.h>

#include "mlxbf_gige.h"
#include "mlxbf_gige_regs.h"

/* Start of struct ethtool_ops functions */
static int mlxbf_gige_get_regs_len(struct net_device *netdev)
{
	return MLXBF_GIGE_MMIO_REG_SZ;
}

static void mlxbf_gige_get_regs(struct net_device *netdev,
				struct ethtool_regs *regs, void *p)
{
	struct mlxbf_gige *priv = netdev_priv(netdev);

	regs->version = MLXBF_GIGE_REGS_VERSION;

	/* Read entire MMIO register space and store results
	 * into the provided buffer. By design, a read to an
	 * offset without an existing register will be
	 * acknowledged and return zero.
	 */
	memcpy_fromio(p, priv->base, MLXBF_GIGE_MMIO_REG_SZ);
}

static void mlxbf_gige_get_ringparam(struct net_device *netdev,
				     struct ethtool_ringparam *ering)
{
	struct mlxbf_gige *priv = netdev_priv(netdev);

	ering->rx_max_pending = MLXBF_GIGE_MAX_RXQ_SZ;
	ering->tx_max_pending = MLXBF_GIGE_MAX_TXQ_SZ;
	ering->rx_pending = priv->rx_q_entries;
	ering->tx_pending = priv->tx_q_entries;
}

static int mlxbf_gige_set_ringparam(struct net_device *netdev,
				    struct ethtool_ringparam *ering)
{
	const struct net_device_ops *ops = netdev->netdev_ops;
	struct mlxbf_gige *priv = netdev_priv(netdev);
	int new_rx_q_entries, new_tx_q_entries;

	/* Device does not have separate queues for small/large frames */
	if (ering->rx_mini_pending || ering->rx_jumbo_pending)
		return -EINVAL;

	/* Round up to supported values */
	new_rx_q_entries = roundup_pow_of_two(ering->rx_pending);
	new_tx_q_entries = roundup_pow_of_two(ering->tx_pending);

	/* Check against min values, core checks against max values */
	if (new_tx_q_entries < MLXBF_GIGE_MIN_TXQ_SZ ||
	    new_rx_q_entries < MLXBF_GIGE_MIN_RXQ_SZ)
		return -EINVAL;

	/* If queue sizes did not change, exit now */
	if (new_rx_q_entries == priv->rx_q_entries &&
	    new_tx_q_entries == priv->tx_q_entries)
		return 0;

#ifdef MLXBF_GIGE_INTERNAL
	netdev_printk(KERN_DEBUG, netdev,
		      "set_ringparam(): new_tx=%x new_rx=%x\n",
		      new_tx_q_entries, new_rx_q_entries);
#endif

	if (netif_running(netdev))
		ops->ndo_stop(netdev);

	priv->rx_q_entries = new_rx_q_entries;
	priv->tx_q_entries = new_tx_q_entries;

	if (netif_running(netdev))
		ops->ndo_open(netdev);

	return 0;
}

static const struct {
	const char string[ETH_GSTRING_LEN];
} mlxbf_gige_ethtool_stats_keys[] = {
	{ "hw_access_errors" },
	{ "tx_invalid_checksums" },
	{ "tx_small_frames" },
	{ "tx_index_errors" },
	{ "sw_config_errors" },
	{ "sw_access_errors" },
	{ "rx_truncate_errors" },
	{ "rx_mac_errors" },
	{ "rx_din_dropped_pkts" },
	{ "tx_fifo_full" },
	{ "rx_filter_passed_pkts" },
	{ "rx_filter_discard_pkts" },
	{ "mac_intr_count" },
	{ "rx_intr_count" },
	{ "llu_plu_intr_count" },
};

static int mlxbf_gige_get_sset_count(struct net_device *netdev, int stringset)
{
	if (stringset != ETH_SS_STATS)
		return -EOPNOTSUPP;
	return ARRAY_SIZE(mlxbf_gige_ethtool_stats_keys);
}

static void mlxbf_gige_get_strings(struct net_device *netdev, u32 stringset,
				   u8 *buf)
{
	if (stringset != ETH_SS_STATS)
		return;
	memcpy(buf, &mlxbf_gige_ethtool_stats_keys,
	       sizeof(mlxbf_gige_ethtool_stats_keys));
}

static void mlxbf_gige_get_ethtool_stats(struct net_device *netdev,
					 struct ethtool_stats *estats,
					 u64 *data)
{
	struct mlxbf_gige *priv = netdev_priv(netdev);

	/* Fill data array with interface statistics
	 *
	 * NOTE: the data writes must be in
	 *       sync with the strings shown in
	 *       the mlxbf_gige_ethtool_stats_keys[] array
	 *
	 * NOTE2: certain statistics below are zeroed upon
	 *        port disable, so the calculation below
	 *        must include the "cached" value of the stat
	 *        plus the value read directly from hardware.
	 *        Cached statistics are currently:
	 *          rx_din_dropped_pkts
	 *          rx_filter_passed_pkts
	 *          rx_filter_discard_pkts
	 */
	*data++ = priv->stats.hw_access_errors;
	*data++ = priv->stats.tx_invalid_checksums;
	*data++ = priv->stats.tx_small_frames;
	*data++ = priv->stats.tx_index_errors;
	*data++ = priv->stats.sw_config_errors;
	*data++ = priv->stats.sw_access_errors;
	*data++ = priv->stats.rx_truncate_errors;
	*data++ = priv->stats.rx_mac_errors;
	*data++ = (priv->stats.rx_din_dropped_pkts +
		   readq(priv->base + MLXBF_GIGE_RX_DIN_DROP_COUNTER));
	*data++ = priv->stats.tx_fifo_full;
	*data++ = (priv->stats.rx_filter_passed_pkts +
		   readq(priv->base + MLXBF_GIGE_RX_PASS_COUNTER_ALL));
	*data++ = (priv->stats.rx_filter_discard_pkts +
		   readq(priv->base + MLXBF_GIGE_RX_DISC_COUNTER_ALL));
	*data++ = priv->error_intr_count;
	*data++ = priv->rx_intr_count;
	*data++ = priv->llu_plu_intr_count;
}

static void mlxbf_gige_get_pauseparam(struct net_device *netdev,
				      struct ethtool_pauseparam *pause)
{
	pause->autoneg = AUTONEG_DISABLE;
	pause->rx_pause = 1;
	pause->tx_pause = 1;
}

const struct ethtool_ops mlxbf_gige_ethtool_ops = {
	.get_link		= ethtool_op_get_link,
	.get_ringparam		= mlxbf_gige_get_ringparam,
	.set_ringparam		= mlxbf_gige_set_ringparam,
	.get_regs_len           = mlxbf_gige_get_regs_len,
	.get_regs               = mlxbf_gige_get_regs,
	.get_strings            = mlxbf_gige_get_strings,
	.get_sset_count         = mlxbf_gige_get_sset_count,
	.get_ethtool_stats      = mlxbf_gige_get_ethtool_stats,
	.nway_reset		= phy_ethtool_nway_reset,
	.get_pauseparam		= mlxbf_gige_get_pauseparam,
	.get_link_ksettings	= phy_ethtool_get_link_ksettings,
};
