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

/* Debug logic for Mellanox Gigabit Ethernet driver
 *
 * This module only includes OOT driver debug code.  This code
 * is conditionally compiled with "#ifdef MLXBF_GIGE_INTERNAL".
 * This code should not be upstreamed and it should not be part
 * of the in-tree driver source.
 *
 * Copyright (C) 2020-2021 NVIDIA CORPORATION & AFFILIATES
 */

/* Standard method to enable kernel debug (e.g. dev_dbg) */
#define DEBUG

#include <linux/device.h>
#include <linux/platform_device.h>

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

#ifdef MLXBF_GIGE_INTERNAL
#define MLXBF_GIGE_MSG_FORMAT \
	"  %02x %02x %02x %02x %02x %02x %02x %02x" \
	" %02x %02x %02x %02x %02x %02x %02x %02x\n"

#define MLXBF_GIGE_MSG_ARGS(p) \
	*p, *(p + 1), *(p + 2), *(p + 3), \
	*(p + 4), *(p + 5), *(p + 6), *(p + 7), \
	*(p + 8), *(p + 9), *(p + 10), *(p + 11), \
	*(p + 12), *(p + 13), *(p + 14), *(p + 15)

void mlxbf_gige_plu_selftests(struct platform_device *pdev,
			      struct mlxbf_gige *priv)
{
#ifdef PLU_MMIO_TESTS
	u32 rd_data, exp_data;

	dev_dbg(&pdev->dev, "Running PLU MMIO tests\n");

	/* Read data should match reset value in register header file */
	rd_data = readl(priv->plu_base + 0x8);
	exp_data = 0x1ff;
	dev_dbg(&pdev->dev, "PLU 0x8 equals %x - %s\n",
		rd_data, (rd_data == exp_data) ? "OK" : "FAIL");

	/* Read data should match reset value in register header file */
	rd_data = readl(priv->plu_base + 0x140);
	exp_data = 0xe8001870;
	dev_dbg(&pdev->dev, "PLU 0x140 equals %x - %s\n",
		rd_data, (rd_data == exp_data) ? "OK" : "FAIL");

	/* Read data should match reset value in register header file */
	rd_data = readl(priv->plu_base + 0x610);
	exp_data = 0x31001;
	dev_dbg(&pdev->dev, "PLU 0x610 equals %x - %s\n",
		rd_data, (rd_data == exp_data) ? "OK" : "FAIL");

	/* Read data should match reset value in register header file */
	rd_data = readl(priv->plu_base + 0x618);
	exp_data = 0xb0000;
	dev_dbg(&pdev->dev, "PLU 0x618 equals %x - %s\n",
		rd_data, (rd_data == exp_data) ? "OK" : "FAIL");

	/* Read data should match reset value in register header file */
	rd_data = readl(priv->plu_base + 0x890);
	exp_data = 0x9;
	dev_dbg(&pdev->dev, "PLU 0x890 equals %x - %s\n",
		rd_data, (rd_data == exp_data) ? "OK" : "FAIL");

	/* Read data should match reset value in register header file */
	rd_data = readl(priv->plu_base + 0x894);
	exp_data = 0x1;
	dev_dbg(&pdev->dev, "PLU 0x894 equals %x - %s\n",
		rd_data, (rd_data == exp_data) ? "OK" : "FAIL");
#endif
}

void mlxbf_gige_llu_selftests(struct platform_device *pdev,
			      struct mlxbf_gige *priv)
{
#ifdef LLU_MMIO_TESTS
	u32 rd_data, exp_data;

	dev_dbg(&pdev->dev, "Running LLU MMIO tests\n");

	/* Read data should match reset value in register header file */
	rd_data = readl(priv->llu_base + 0x2200);
	exp_data = 0x91008808;
	dev_dbg(&pdev->dev, "LLU 0x2200 equals %x - %s\n",
		rd_data, (rd_data == exp_data) ? "OK" : "FAIL");

	/* Read data should match reset value in register header file */
	rd_data = readl(priv->llu_base + 0x2204);
	exp_data = 0x810088a8;
	dev_dbg(&pdev->dev, "LLU 0x2204 equals %x - %s\n",
		rd_data, (rd_data == exp_data) ? "OK" : "FAIL");

	/* Read data should match reset value in register header file */
	rd_data = readl(priv->llu_base + 0x2208);
	exp_data = 0x22e90000;
	dev_dbg(&pdev->dev, "LLU 0x2208 equals %x - %s\n",
		rd_data, (rd_data == exp_data) ? "OK" : "FAIL");

	/* Read data should match reset value in register header file */
	rd_data = readl(priv->llu_base + 0x220c);
	exp_data = 0x893f0000;
	dev_dbg(&pdev->dev, "LLU 0x220c equals %x - %s\n",
		rd_data, (rd_data == exp_data) ? "OK" : "FAIL");

	/* Read data should match reset value in register header file */
	rd_data = readl(priv->llu_base + 0x2260);
	exp_data = 0x8060806;
	dev_dbg(&pdev->dev, "LLU 0x2260 equals %x - %s\n",
		rd_data, (rd_data == exp_data) ? "OK" : "FAIL");

	/* Read data should match reset value in register header file */
	rd_data = readl(priv->llu_base + 0x2264);
	exp_data = 0x891422e7;
	dev_dbg(&pdev->dev, "LLU 0x2264 equals %x - %s\n",
		rd_data, (rd_data == exp_data) ? "OK" : "FAIL");
#endif
}

void mlxbf_gige_selftests(struct platform_device *pdev,
			  struct mlxbf_gige *priv)
{
#ifdef MLXBF_GIGE_MMIO_TESTS
	u64 rd_data, wr_data, exp_data;

	dev_dbg(&pdev->dev, "Running MLXBF_GIGE MMIO tests\n");

	/* Read data should match reset value in register header file */
	rd_data = readq(priv->base + MLXBF_GIGE_CONFIG);
	exp_data = (MLXBF_GIGE_CONFIG_MAX_PKT_SZ_RESET_VAL
		    << MLXBF_GIGE_CONFIG_MAX_PKT_SZ_SHIFT);
	dev_dbg(&pdev->dev, "MLXBF_GIGE_CONFIG equals %llx - %s\n",
		rd_data, (rd_data == exp_data) ? "OK" : "FAIL");

	/* Read data should match reset value in register header file */
	rd_data = readq(priv->base + MLXBF_GIGE_RX_WQE_SIZE_LOG2);
	exp_data = MLXBF_GIGE_RX_WQE_SIZE_LOG2_RESET_VAL;
	dev_dbg(&pdev->dev, "MLXBF_GIGE_RX_WQE_SIZE_LOG2 equals %llx - %s\n",
		rd_data, (rd_data == exp_data) ? "OK" : "FAIL");

	/* Read data should match reset value in register header file */
	rd_data = readq(priv->base + MLXBF_GIGE_TX_WQ_SIZE_LOG2);
	exp_data = MLXBF_GIGE_TX_WQ_SIZE_LOG2_RESET_VAL;
	dev_dbg(&pdev->dev, "MLXBF_GIGE_TX_WQ_SIZE_LOG2 equals %llx - %s\n",
		rd_data, (rd_data == exp_data) ? "OK" : "FAIL");

	/* Do some SCRATCHPAD testing */
	rd_data = readq(priv->base + MLXBF_GIGE_SCRATCHPAD);
	dev_dbg(&pdev->dev, "MLXBF_GIGE_SCRATCHPAD equals %llx\n", rd_data);

	wr_data = 0x1122334455667788;
	writeq(wr_data, priv->base + MLXBF_GIGE_SCRATCHPAD);
	dev_dbg(&pdev->dev, "Will write %llx to MLXBF_GIGE_SCRATCHPAD\n",
		wr_data);

	rd_data = readq(priv->base + MLXBF_GIGE_SCRATCHPAD);
	dev_dbg(&pdev->dev, "MLXBF_GIGE_SCRATCHPAD equals %llx - %s\n",
		rd_data, (rd_data == wr_data) ? "OK" : "FAIL");

	wr_data = 0xaabbccddeeff4321;
	writeq(wr_data, priv->base + MLXBF_GIGE_SCRATCHPAD);
	dev_dbg(&pdev->dev, "Will write %llx to MLXBF_GIGE_SCRATCHPAD\n",
		wr_data);

	rd_data = readq(priv->base + MLXBF_GIGE_SCRATCHPAD);
	dev_dbg(&pdev->dev, "MLXBF_GIGE_SCRATCHPAD equals %llx - %s\n",
		rd_data, (rd_data == wr_data) ? "OK" : "FAIL");
#endif /* MLXBF_GIGE_MMIO_TESTS */
}

#ifdef MLXBF_GIGE_MMIO_SYSFS
static ssize_t mmio_read_store(struct device *dev,
			       struct device_attribute *attr,
			       const char *buf, size_t len)
{
	struct mlxbf_gige *priv;
	u64 offset;
	int cnt;

	priv = dev_get_drvdata(dev);

	cnt = sscanf(buf, "%llx\n", &offset);

	if (cnt != 1) {
		dev_err(dev, "MMIO read: invalid arguments\n");
		return len;
	}

	/* Make sure offset is within MAC block and 8-byte aligned */
	if (offset <= MLXBF_GIGE_MAC_CFG &&
	    ((offset & 0x7) == 0)) {
		dev_err(dev,
			"MMIO read: offset=0x%llx data=0x%llx\n",
			offset, readq(priv->base + offset));
	} else {
		dev_err(dev,
			"MMIO read: invalid offset 0x%llx\n",
			offset);
	}

	return len;
}

static ssize_t mmio_write_store(struct device *dev,
				struct device_attribute *attr,
				const char *buf, size_t len)
{
	struct mlxbf_gige *priv;
	u64 offset, data;
	int cnt;

	priv = dev_get_drvdata(dev);

	cnt = sscanf(buf, "%llx %llx\n", &offset, &data);

	if (cnt != 2) {
		dev_err(dev, "MMIO write: invalid arguments\n");
		return len;
	}

	/* Make sure offset is within MAC block and 8-byte aligned */
	if (offset <= MLXBF_GIGE_MAC_CFG &&
	    ((offset & 0x7) == 0)) {
		dev_err(dev,
			"MMIO write: offset=0x%llx data=0x%llx\n",
			offset, data);
		writeq(data, priv->base + offset);
	} else {
		dev_err(dev,
			"MMIO write: invalid offset 0x%llx\n",
			offset);
	}

	return len;
}

static ssize_t llu_mmio_read_store(struct device *dev,
				   struct device_attribute *attr,
				   const char *buf, size_t len)
{
	struct mlxbf_gige *priv;
	u64 offset;
	int cnt;

	priv = dev_get_drvdata(dev);

	cnt = sscanf(buf, "%llx\n", &offset);

	if (cnt != 1) {
		dev_err(dev, "LLU MMIO read: invalid arguments\n");
		return len;
	}

	/* Make sure offset is within LLU and 4-byte aligned */
	if (offset <= MLXBF_GIGE_LLU_MAX_OFFSET &&
	    ((offset & 0x3) == 0)) {
		dev_err(dev,
			"LLU MMIO read: offset=0x%llx data=0x%x\n",
			offset, readl(priv->llu_base + offset));
	} else {
		dev_err(dev,
			"LLU MMIO read: invalid offset 0x%llx\n",
			offset);
	}

	return len;
}

static ssize_t llu_mmio_write_store(struct device *dev,
				    struct device_attribute *attr,
				    const char *buf, size_t len)
{
	struct mlxbf_gige *priv;
	u64 offset;
	u32 data;
	int cnt;

	priv = dev_get_drvdata(dev);

	cnt = sscanf(buf, "%llx %x\n", &offset, &data);

	if (cnt != 2) {
		dev_err(dev, "LLU MMIO write: invalid arguments\n");
		return len;
	}

	/* Make sure offset is within LLU and 4-byte aligned */
	if (offset <= MLXBF_GIGE_LLU_MAX_OFFSET &&
	    ((offset & 0x3) == 0)) {
		dev_err(dev,
			"LLU MMIO write: offset=0x%llx data=0x%x\n",
			offset, data);
		writel(data, priv->llu_base + offset);
	} else {
		dev_err(dev,
			"LLU MMIO write: invalid offset 0x%llx\n",
			offset);
	}

	return len;
}

static ssize_t plu_mmio_read_store(struct device *dev,
				   struct device_attribute *attr,
				   const char *buf, size_t len)
{
	struct mlxbf_gige *priv;
	u64 offset;
	int cnt;

	priv = dev_get_drvdata(dev);

	cnt = sscanf(buf, "%llx\n", &offset);

	if (cnt != 1) {
		dev_err(dev, "PLU MMIO read: invalid arguments\n");
		return len;
	}

	/* Make sure offset is within PLU and 4-byte aligned */
	if (offset <= MLXBF_GIGE_PLU_MAX_OFFSET &&
	    ((offset & 0x3) == 0)) {
		dev_err(dev,
			"PLU MMIO read: offset=0x%llx data=0x%x\n",
			offset, readl(priv->plu_base + offset));
	} else {
		dev_err(dev,
			"PLU MMIO read: invalid offset 0x%llx\n",
			offset);
	}

	return len;
}

static ssize_t plu_mmio_write_store(struct device *dev,
				    struct device_attribute *attr,
				    const char *buf, size_t len)
{
	struct mlxbf_gige *priv;
	u64 offset;
	u32 data;
	int cnt;

	priv = dev_get_drvdata(dev);

	cnt = sscanf(buf, "%llx %x\n", &offset, &data);

	if (cnt != 2) {
		dev_err(dev, "PLU MMIO write: invalid arguments\n");
		return len;
	}

	/* Make sure offset is within PLU and 4-byte aligned */
	if (offset <= MLXBF_GIGE_PLU_MAX_OFFSET &&
	    ((offset & 0x3) == 0)) {
		dev_err(dev,
			"PLU MMIO write: offset=0x%llx data=0x%x\n",
			offset, data);
		writel(data, priv->plu_base + offset);
	} else {
		dev_err(dev,
			"PLU MMIO write: invalid offset 0x%llx\n",
			offset);
	}

	return len;
}

DEVICE_ATTR_WO(mmio_read);
DEVICE_ATTR_WO(mmio_write);
DEVICE_ATTR_WO(llu_mmio_read);
DEVICE_ATTR_WO(llu_mmio_write);
DEVICE_ATTR_WO(plu_mmio_read);
DEVICE_ATTR_WO(plu_mmio_write);
#endif /* MLXBF_GIGE_MMIO_SYSFS */

static void oob_dump_tx_wqe(struct device *dev, u64 *tx_wqe_addr)
{
	u64 word1, word2;

	/* Sanity check the TX WQE address */
	if (!tx_wqe_addr)
		return;

	word1 = *tx_wqe_addr;
	word2 = *(tx_wqe_addr + 1);

	/* If TX WQE is empty (i.e. both words are 0)
	 * then don't bother displaying WQE details
	 */
	if (word1 == (u64)0 && word2 == (u64)0) {
		dev_dbg(dev, "%s(%llx)=%llx %llx", __func__,
			(u64)tx_wqe_addr, word1, word2);
	} else {
		dev_dbg(dev, "%s(%llx)", __func__,
			(u64)tx_wqe_addr);

		dev_dbg(dev, "  buffer addr:  %llx\n", word1);

		dev_dbg(dev, "  pkt_len:      %llx\n",
			((word2 & MLXBF_GIGE_TX_WQE_PKT_LEN_MASK)
			 >> MLXBF_GIGE_TX_WQE_PKT_LEN_SHIFT));

		dev_dbg(dev, "  update:       %llx\n",
			((word2 & MLXBF_GIGE_TX_WQE_UPDATE_MASK)
			 >> MLXBF_GIGE_TX_WQE_UPDATE_SHIFT));

		dev_dbg(dev, "  cksum_len:    %llx\n",
			((word2 & MLXBF_GIGE_TX_WQE_CHKSUM_LEN_MASK)
			 >> MLXBF_GIGE_TX_WQE_CHKSUM_LEN_SHIFT));

		dev_dbg(dev, "  cksum_start:  %llx\n",
			((word2 & MLXBF_GIGE_TX_WQE_CHKSUM_START_MASK)
			 >> MLXBF_GIGE_TX_WQE_CHKSUM_START_SHIFT));

		dev_dbg(dev, "  cksum_offset: %llx\n",
			((word2 & MLXBF_GIGE_TX_WQE_CHKSUM_OFFSET_MASK)
			 >> MLXBF_GIGE_TX_WQE_CHKSUM_OFFSET_SHIFT));
	}
}

/* Handler for sysfs entry 'dump_tx' found at
 *   /sys/devices/platform/MLNXBF17:00/
 * Issue 'cat dump_tx' to invoke this routine
 */
static ssize_t dump_tx_show(struct device *dev,
			    struct device_attribute *attr, char *buf)
{
	struct mlxbf_gige *priv;
	u64 data;
	int i;

	priv = dev_get_drvdata(dev);

	dev_dbg(dev, "============================================\n");

	dev_dbg(dev, "%s: dev_get_drvdata=%llx\n",
		__func__, (u64)priv);

	/* Loop through 'n' TX WQE entries */
	for (i = 0;
	     (priv->netdev->flags & IFF_UP) && (i < priv->tx_q_entries);
	     i++) {
		u8 *p;

		oob_dump_tx_wqe(dev,
				(u64 *)((u64)priv->tx_wqe_base +
				(i * MLXBF_GIGE_TX_WQE_SZ)));

		if (priv->tx_skb[i]) {
			p = priv->tx_skb[i]->data;
			dev_dbg(dev, "  tx_skb[%d]=%llx head=%llx data=%llx\n",
				i, (u64)priv->tx_skb[i],
				(u64)priv->tx_skb[i]->head,
				(u64)priv->tx_skb[i]->data);
		}
		else
			p = NULL;


		if (p) {
			int j;

			for (j = 0;
			     j < (MLXBF_GIGE_NUM_BYTES_IN_PKT_DUMP / 16);
			     j++, p += 16) {
				dev_dbg(dev, MLXBF_GIGE_MSG_FORMAT,
					MLXBF_GIGE_MSG_ARGS(p));
			}
		}
	}

	if (priv->netdev->flags & IFF_UP) {
		dev_dbg(dev, "tx_cc=%llx *tx_cc=%llx\n",
			(u64)priv->tx_cc, *(u64 *)priv->tx_cc);
	}

	/* Display TX producer index */
	data = readq(priv->base + MLXBF_GIGE_TX_PRODUCER_INDEX);
	dev_dbg(dev, "tx_producer_index=%llx\n", (u64)data);

	/* Display TX consumer index */
	data = readq(priv->base + MLXBF_GIGE_TX_CONSUMER_INDEX);
	dev_dbg(dev, "tx_consumer_index=%llx\n", (u64)data);

	/* Display TX status */
	data = readq(priv->base + MLXBF_GIGE_TX_STATUS);
	dev_dbg(dev, "tx_status=%llx\n", (u64)data);

	/* Display TX FIFO status */
	data = readq(priv->base + MLXBF_GIGE_TX_FIFOS_STATUS);
	dev_dbg(dev, "tx_fifos_status=%llx\n", (u64)data);

	return strlen(buf);
}

DEVICE_ATTR_RO(dump_tx);

static void oob_dump_rx_wqe(struct device *dev, u64 *rx_wqe_addr)
{
	/* Sanity check the RX WQE address */
	if (!rx_wqe_addr)
		return;

	dev_dbg(dev, "%s(%llx)=%llx\n", __func__,
		(u64)rx_wqe_addr, *rx_wqe_addr);
}

static void oob_dump_rx_cqe(struct device *dev, u64 *rx_cqe_addr)
{
	u64 rx_cqe;

	/* Sanity check the RX CQE address */
	if (!rx_cqe_addr)
		return;

	rx_cqe = *rx_cqe_addr;

	/* If RX CQE is empty (i.e. value is 0) then
	 * don't bother displaying CQE details
	 */
	if (rx_cqe == (u64)0) {
		dev_dbg(dev, "%s(%llx)=%llx", __func__,
			(u64)rx_cqe_addr, rx_cqe);
	} else {
		dev_dbg(dev, "%s(%llx)", __func__,
			(u64)rx_cqe_addr);

		dev_dbg(dev, "  pkt_len:    %llx\n",
			(rx_cqe & MLXBF_GIGE_RX_CQE_PKT_LEN_MASK));

		dev_dbg(dev, "  valid:      %llx\n",
			((rx_cqe & MLXBF_GIGE_RX_CQE_VALID_MASK)
			 >> MLXBF_GIGE_RX_CQE_VALID_SHIFT));

		dev_dbg(dev, "  pkt_status: %llx\n",
			((rx_cqe & MLXBF_GIGE_RX_CQE_PKT_STATUS_MASK)
			 >> MLXBF_GIGE_RX_CQE_PKT_STATUS_SHIFT));

		dev_dbg(dev, "  chksum:     %llx\n",
			((rx_cqe & MLXBF_GIGE_RX_CQE_CHKSUM_MASK)
			 >> MLXBF_GIGE_RX_CQE_CHKSUM_SHIFT));
	}
}

/* Handler for sysfs entry 'dump_rx' found at
 *   /sys/devices/platform/MLNXBF17:00/
 * Issue 'cat dump_rx' to invoke this routine
 */
static ssize_t dump_rx_show(struct device *dev,
			    struct device_attribute *attr, char *buf)
{
	struct mlxbf_gige *priv;
	u64 data;
	int i;

	priv = dev_get_drvdata(dev);

	dev_dbg(dev, "============================================\n");
	dev_dbg(dev, "%s: dev_get_drvdata=%llx\n", __func__, (u64)priv);

	/* Loop through 'n' RX WQE entries */
	for (i = 0;
	     (priv->netdev->flags & IFF_UP) && (i < priv->rx_q_entries);
	     i++) {
		u8 *p;

		oob_dump_rx_wqe(dev, priv->rx_wqe_base + i);

		p = priv->rx_skb[i]->data;
		dev_dbg(dev, "  rx_skb[%d]=%llx head=%llx data=%llx\n",
			i, (u64)priv->rx_skb[i], (u64)priv->rx_skb[i]->head,
			(u64)priv->rx_skb[i]->data);

		/* Only display RX buffer contents if not in initial state */
		if (p && (*p != MLXBF_GIGE_INIT_BYTE_RX_BUF)) {
			int j;

			for (j = 0;
			     j < (MLXBF_GIGE_NUM_BYTES_IN_PKT_DUMP / 16);
			     j++, p += 16) {
				dev_dbg(dev, MLXBF_GIGE_MSG_FORMAT,
					MLXBF_GIGE_MSG_ARGS(p));
			}
		}
	}

	/* Loop through 'n' RX CQE entries */
	for (i = 0;
	     (priv->netdev->flags & IFF_UP) && (i < priv->rx_q_entries);
	     i++) {
		oob_dump_rx_cqe(dev, priv->rx_cqe_base + i);
	}

	/* Display RX WQE producer index */
	data = readq(priv->base + MLXBF_GIGE_RX_WQE_PI);
	dev_dbg(dev, "rx_wqe_pi=%llx\n", (u64)data);

	/* Display INT_STATUS */
	data = readq(priv->base + MLXBF_GIGE_INT_STATUS);
	dev_dbg(dev, "int_status=%llx\n", (u64)data);

	/* Then, clear INT_STATUS */
	data = 0x1FF;
	writeq(data, priv->base + MLXBF_GIGE_INT_STATUS);

	/* Display RX_DIN_DROP_COUNTER */
	data = readq(priv->base + MLXBF_GIGE_RX_DIN_DROP_COUNTER);
	dev_dbg(dev, "rx_din_drop_counter=%llx\n", (u64)data);

	/* Display INT_STATUS_EXP */
	data = readq(priv->base + MLXBF_GIGE_INT_STATUS_EXP);
	dev_dbg(dev, "int_status_exp=%llx\n", (u64)data);

	/* Then, clear INT_STATUS_EXP */
	data = 0;
	writeq(data, priv->base + MLXBF_GIGE_INT_STATUS_EXP);

	/* Display RX_MAC_FILTER_PASS_COUNTER_ALL */
	data = readq(priv->base + MLXBF_GIGE_RX_PASS_COUNTER_ALL);
	dev_dbg(dev, "rx_mac_filter_pass_counter_all=%llx\n", (u64)data);

	/* Display RX_MAC_FILTER_DISC_COUNTER_ALL */
	data = readq(priv->base + MLXBF_GIGE_RX_DISC_COUNTER_ALL);
	dev_dbg(dev, "rx_mac_filter_disc_counter_all=%llx\n", (u64)data);

	/* Display first word of RX_MAC_FILTER */
	data = readq(priv->base + MLXBF_GIGE_RX_MAC_FILTER);
	dev_dbg(dev, "rx_mac_filter0=%llx\n", (u64)data);

	/* Display second word of RX_MAC_FILTER */
	data = readq(priv->base + MLXBF_GIGE_RX_MAC_FILTER + 0x8);
	dev_dbg(dev, "rx_mac_filter1=%llx\n", (u64)data);

	/* Display third word of RX_MAC_FILTER */
	data = readq(priv->base + MLXBF_GIGE_RX_MAC_FILTER + 0x10);
	dev_dbg(dev, "rx_mac_filter2=%llx\n", (u64)data);

	/* Display fourth word of RX_MAC_FILTER */
	data = readq(priv->base + MLXBF_GIGE_RX_MAC_FILTER + 0x18);
	dev_dbg(dev, "rx_mac_filter3=%llx\n", (u64)data);

	/* Display MLXBF_GIGE_RX_CQE_PACKET_CI */
	data = readq(priv->base + MLXBF_GIGE_RX_CQE_PACKET_CI);
	dev_dbg(dev, "MLXBF_GIGE_RX_CQE_PACKET_CI=%llx\n", (u64)data);

	dev_dbg(dev, "error_intr_count=%llx\n", priv->error_intr_count);
	dev_dbg(dev, "rx_intr_count=%llx\n", priv->rx_intr_count);
	dev_dbg(dev, "llu_plu_intr_count=%llx\n", priv->llu_plu_intr_count);

	/* Display INT_EN */
	data = readq(priv->base + MLXBF_GIGE_INT_EN);
	dev_dbg(dev, "int_en=%llx\n", (u64)data);

	/* Display INT_MASK */
	data = readq(priv->base + MLXBF_GIGE_INT_MASK);
	dev_dbg(dev, "int_mask=%llx\n", (u64)data);

	return strlen(buf);
}

DEVICE_ATTR_RO(dump_rx);

/* Handler for sysfs entry 'start_tx' found at
 *   /sys/devices/platform/MLNXBF17:00/
 * Issue 'echo <N> > start_tx' to invoke this routine
 * which will send <N> dummy IP packets to GigE port
 */
static ssize_t start_tx_store(struct device *dev,
			      struct device_attribute *attr,
			      const char *buf, size_t len)
{
	struct mlxbf_gige *priv;
	struct sk_buff *tx_skb;
	dma_addr_t tx_buf_dma;
	u8 oob_tx_data_seed;
	int oob_tx_pkt_size;
	long num_pkts = 1;
	u64 *tx_wqe_addr;
	u8 *tx_buf;
	u16 data16;
	u64 word2;
	int i, j;
	int ret;

	priv = dev_get_drvdata(dev);

	oob_tx_pkt_size = MLXBF_GIGE_DEFAULT_TX_PKT_SIZE;

	if (buf) {
		ret = kstrtol(buf, 10, &num_pkts);

		if (ret == 0) {
			dev_dbg(dev, "%s: num_pkts %d\n",
				__func__, (int)num_pkts);
		}
	}

	for (i = 0; i < num_pkts; i++) {
		/* The data seed is used to populate the packet with
		 * fake, but predictable, data.  The value of seed
		 * is stored in the first byte after the L2 header,
		 * then (seed+1) is stored in the second byte, etc.
		 */
		oob_tx_data_seed = priv->tx_data_seed;
		priv->tx_data_seed += 4;

		/* To limit output only perform debug logging if number
		 * of packets to send is less than some maximum value.
		 */
		if (num_pkts < MLXBF_GIGE_MAX_TX_PKTS_VERBOSE) {
			dev_dbg(dev, "%s: size=%x seed=%x\n",
				__func__, oob_tx_pkt_size,
				oob_tx_data_seed);
		}

		tx_skb = mlxbf_gige_alloc_skb(priv, oob_tx_pkt_size,
					      &tx_buf_dma, DMA_TO_DEVICE);
		if (!tx_skb) {
			dev_dbg(dev, "%s: could not alloc TX SKB\n", __func__);
			return -ENOMEM;
		}

		tx_buf = tx_skb->data;

		if (num_pkts < MLXBF_GIGE_MAX_TX_PKTS_VERBOSE) {
			dev_dbg(dev, "%s: tx_buf %llx %llx\n",
				__func__, (u64)tx_buf, (u64)tx_buf_dma);
		}

		if (num_pkts < MLXBF_GIGE_MAX_TX_PKTS_VERBOSE) {
			dev_dbg(dev, "%s: pkt num %llx buffer index %x\n",
				__func__, (u64)priv->tx_pi,
				(priv->tx_pi % priv->tx_q_entries));
		}

		priv->tx_skb[priv->tx_pi % priv->tx_q_entries] = tx_skb;

		/* Put in four bytes of fake destination MAC, but use real
		 * value of 'tx_pi' in order to track TX producer index
		 * in the actual packet contents.
		 */
		*tx_buf++ = MLXBF_GIGE_FAKE_DMAC_BYTE;
		*tx_buf++ = MLXBF_GIGE_FAKE_DMAC_BYTE;
		*tx_buf++ = MLXBF_GIGE_FAKE_DMAC_BYTE;
		*tx_buf++ = MLXBF_GIGE_FAKE_DMAC_BYTE;
		*tx_buf++ = (priv->tx_pi & 0xFF00) >> 8;
		*tx_buf++ = (priv->tx_pi & 0xFF);

		/* Put in fake source MAC */
		*tx_buf++ = MLXBF_GIGE_FAKE_SMAC_BYTE;
		*tx_buf++ = MLXBF_GIGE_FAKE_SMAC_BYTE;
		*tx_buf++ = MLXBF_GIGE_FAKE_SMAC_BYTE;
		*tx_buf++ = MLXBF_GIGE_FAKE_SMAC_BYTE;
		*tx_buf++ = MLXBF_GIGE_FAKE_SMAC_BYTE;
		*tx_buf++ = MLXBF_GIGE_FAKE_SMAC_BYTE;

		/* Set ethertype for IP (0x0800) */
		*tx_buf++ = 0x08;
		*tx_buf++ = 0x00;

		/* Put in fake packet payload */
		for (j = 0; j < (oob_tx_pkt_size - ETH_HLEN); j++)
			*tx_buf++ = (u8)(j + oob_tx_data_seed);

		/* TODO - should really reorganize all low-level TX   */
		/* logic and call it here and in 'xmit' function also */

		/* Get address of TX WQE */
		tx_wqe_addr = priv->tx_wqe_next;

		if (num_pkts < MLXBF_GIGE_MAX_TX_PKTS_VERBOSE) {
			dev_dbg(dev, "%s: tx_wqe_addr=%llx\n",
				__func__, (u64)tx_wqe_addr);
		}

		mlxbf_gige_update_tx_wqe_next(priv);

		if (num_pkts < MLXBF_GIGE_MAX_TX_PKTS_VERBOSE) {
			dev_dbg(dev, "%s: tx_wqe_next=%llx\n",
				__func__, (u64)priv->tx_wqe_next);
		}

		/* Put PA of buffer address into first 64-bit word of TX WQE */
		*tx_wqe_addr = tx_buf_dma;

		/* Set TX WQE pkt_len appropriately */
		word2 = oob_tx_pkt_size & MLXBF_GIGE_TX_WQE_PKT_LEN_MASK;

		if (num_pkts < MLXBF_GIGE_MAX_TX_PKTS_VERBOSE) {
			dev_dbg(dev, "%s: word2=%llx\n",
				__func__, (u64)word2);
		}

		/* Write entire 2nd word of TX WQE */
		*(tx_wqe_addr + 1) = word2;

		/* Create memory barrier before write to TX PI */
		wmb();

		priv->tx_pi++;

		writeq(priv->tx_pi, priv->base + MLXBF_GIGE_TX_PRODUCER_INDEX);

		if (priv->tx_pi >= 0x20) {
			data16 = readq(priv->base + MLXBF_GIGE_RX_WQE_PI) + 1;
			writeq(data16, priv->base + MLXBF_GIGE_RX_WQE_PI);
		}
	} /* end - i loop */

	return len;
}

DEVICE_ATTR_WO(start_tx);

void mlxbf_gige_create_sysfs(struct device *dev)
{
#ifdef MLXBF_GIGE_MMIO_SYSFS
	if (device_create_file(dev, &dev_attr_mmio_read))
		dev_info(dev, "failed to create mmio_read sysfs entry\n");
	if (device_create_file(dev, &dev_attr_mmio_write))
		dev_info(dev, "failed to create mmio_write sysfs entry\n");
	if (device_create_file(dev, &dev_attr_llu_mmio_read))
		dev_info(dev, "failed to create llu_mmio_read sysfs entry\n");
	if (device_create_file(dev, &dev_attr_llu_mmio_write))
		dev_info(dev, "failed to create llu_mmio_write sysfs entry\n");
	if (device_create_file(dev, &dev_attr_plu_mmio_read))
		dev_info(dev, "failed to create plu_mmio_read sysfs entry\n");
	if (device_create_file(dev, &dev_attr_plu_mmio_write))
		dev_info(dev, "failed to create plu_mmio_write sysfs entry\n");
#endif

	if (device_create_file(dev, &dev_attr_dump_rx))
		dev_info(dev, "failed to create dump_rx sysfs entry\n");
	if (device_create_file(dev, &dev_attr_dump_tx))
		dev_info(dev, "failed to create dump_tx sysfs entry\n");
	if (device_create_file(dev, &dev_attr_start_tx))
		dev_info(dev, "failed to create start_tx sysfs entry\n");
}

void mlxbf_gige_remove_sysfs(struct device *dev)
{
#ifdef MLXBF_GIGE_MMIO_SYSFS
	device_remove_file(dev, &dev_attr_mmio_read);
	device_remove_file(dev, &dev_attr_mmio_write);
	device_remove_file(dev, &dev_attr_llu_mmio_read);
	device_remove_file(dev, &dev_attr_llu_mmio_write);
	device_remove_file(dev, &dev_attr_plu_mmio_read);
	device_remove_file(dev, &dev_attr_plu_mmio_write);
#endif

	device_remove_file(dev, &dev_attr_dump_rx);
	device_remove_file(dev, &dev_attr_dump_tx);
	device_remove_file(dev, &dev_attr_start_tx);
}
#endif /* MLXBF_GIGE_INTERNAL */

