diff options
author | David Thompson <davthompson@nvidia.com> | 2021-06-24 21:11:46 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2021-06-25 11:20:23 -0700 |
commit | f92e1869d74e1acc6551256eb084a1c14a054e19 (patch) | |
tree | e978b9338c55ccc16f8389c00958d7e35bebcb7f | |
parent | 19938bafa7ae8fc0a4a2c1c1430abb1a04668da1 (diff) | |
download | linux-f92e1869d74e1acc6551256eb084a1c14a054e19.tar.gz linux-f92e1869d74e1acc6551256eb084a1c14a054e19.tar.bz2 linux-f92e1869d74e1acc6551256eb084a1c14a054e19.zip |
Add Mellanox BlueField Gigabit Ethernet driver
This patch adds build and driver logic for the "mlxbf_gige"
Ethernet driver from Mellanox Technologies. The second
generation BlueField SoC from Mellanox supports an
out-of-band GigaBit Ethernet management port to the Arm
subsystem. This driver supports TCP/IP network connectivity
for that port, and provides back-end routines to handle
basic ethtool requests.
The driver interfaces to the Gigabit Ethernet block of
BlueField SoC via MMIO accesses to registers, which contain
control information or pointers describing transmit and
receive resources. There is a single transmit queue, and
the port supports transmit ring sizes of 4 to 256 entries.
There is a single receive queue, and the port supports
receive ring sizes of 32 to 32K entries. The transmit and
receive rings are allocated from DMA coherent memory. There
is a 16-bit producer and consumer index per ring to denote
software ownership and hardware ownership, respectively.
The main driver logic such as probe(), remove(), and netdev
ops are in "mlxbf_gige_main.c". Logic in "mlxbf_gige_rx.c"
and "mlxbf_gige_tx.c" handles the packet processing for
receive and transmit respectively.
The logic in "mlxbf_gige_ethtool.c" supports the handling
of some basic ethtool requests: get driver info, get ring
parameters, get registers, and get statistics.
The logic in "mlxbf_gige_mdio.c" is the driver controlling
the Mellanox BlueField hardware that interacts with a PHY
device via MDIO/MDC pins. This driver does the following:
- At driver probe time, it configures several BlueField MDIO
parameters such as sample rate, full drive, voltage and MDC
- It defines functions to read and write MDIO registers and
registers the MDIO bus.
- It defines the phy interrupt handler reporting a
link up/down status change
- This driver's probe is invoked from the main driver logic
while the phy interrupt handler is registered in ndo_open.
Driver limitations
- Only supports 1Gbps speed
- Only supports GMII protocol
- Supports maximum packet size of 2KB
- Does not support scatter-gather buffering
Testing
- Successful build of kernel for ARM64, ARM32, X86_64
- Tested ARM64 build on FastModels & Palladium
- Tested ARM64 build on several Mellanox boards that are built with
the BlueField-2 SoC. The testing includes coverage in the areas
of networking (e.g. ping, iperf, ifconfig, route), file transfers
(e.g. SCP), and various ethtool options relevant to this driver.
Signed-off-by: David Thompson <davthompson@nvidia.com>
Signed-off-by: Asmaa Mnebhi <asmaa@nvidia.com>
Reviewed-by: Liming Sun <limings@nvidia.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | drivers/net/ethernet/mellanox/Kconfig | 1 | ||||
-rw-r--r-- | drivers/net/ethernet/mellanox/Makefile | 1 | ||||
-rw-r--r-- | drivers/net/ethernet/mellanox/mlxbf_gige/Kconfig | 13 | ||||
-rw-r--r-- | drivers/net/ethernet/mellanox/mlxbf_gige/Makefile | 11 | ||||
-rw-r--r-- | drivers/net/ethernet/mellanox/mlxbf_gige/mlxbf_gige.h | 190 | ||||
-rw-r--r-- | drivers/net/ethernet/mellanox/mlxbf_gige/mlxbf_gige_ethtool.c | 137 | ||||
-rw-r--r-- | drivers/net/ethernet/mellanox/mlxbf_gige/mlxbf_gige_gpio.c | 212 | ||||
-rw-r--r-- | drivers/net/ethernet/mellanox/mlxbf_gige/mlxbf_gige_intr.c | 142 | ||||
-rw-r--r-- | drivers/net/ethernet/mellanox/mlxbf_gige/mlxbf_gige_main.c | 452 | ||||
-rw-r--r-- | drivers/net/ethernet/mellanox/mlxbf_gige/mlxbf_gige_mdio.c | 187 | ||||
-rw-r--r-- | drivers/net/ethernet/mellanox/mlxbf_gige/mlxbf_gige_regs.h | 78 | ||||
-rw-r--r-- | drivers/net/ethernet/mellanox/mlxbf_gige/mlxbf_gige_rx.c | 320 | ||||
-rw-r--r-- | drivers/net/ethernet/mellanox/mlxbf_gige/mlxbf_gige_tx.c | 284 |
13 files changed, 2028 insertions, 0 deletions
diff --git a/drivers/net/ethernet/mellanox/Kconfig b/drivers/net/ethernet/mellanox/Kconfig index ff6613a5cdd3..b4f66eb9ddb9 100644 --- a/drivers/net/ethernet/mellanox/Kconfig +++ b/drivers/net/ethernet/mellanox/Kconfig @@ -22,5 +22,6 @@ source "drivers/net/ethernet/mellanox/mlx4/Kconfig" source "drivers/net/ethernet/mellanox/mlx5/core/Kconfig" source "drivers/net/ethernet/mellanox/mlxsw/Kconfig" source "drivers/net/ethernet/mellanox/mlxfw/Kconfig" +source "drivers/net/ethernet/mellanox/mlxbf_gige/Kconfig" endif # NET_VENDOR_MELLANOX diff --git a/drivers/net/ethernet/mellanox/Makefile b/drivers/net/ethernet/mellanox/Makefile index 79773ac331ee..d4b5f547a727 100644 --- a/drivers/net/ethernet/mellanox/Makefile +++ b/drivers/net/ethernet/mellanox/Makefile @@ -7,3 +7,4 @@ obj-$(CONFIG_MLX4_CORE) += mlx4/ obj-$(CONFIG_MLX5_CORE) += mlx5/core/ obj-$(CONFIG_MLXSW_CORE) += mlxsw/ obj-$(CONFIG_MLXFW) += mlxfw/ +obj-$(CONFIG_MLXBF_GIGE) += mlxbf_gige/ diff --git a/drivers/net/ethernet/mellanox/mlxbf_gige/Kconfig b/drivers/net/ethernet/mellanox/mlxbf_gige/Kconfig new file mode 100644 index 000000000000..4cdebafaf222 --- /dev/null +++ b/drivers/net/ethernet/mellanox/mlxbf_gige/Kconfig @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-3-Clause +# +# Mellanox GigE driver configuration +# + +config MLXBF_GIGE + tristate "Mellanox Technologies BlueField Gigabit Ethernet support" + depends on (ARM64 && ACPI) || COMPILE_TEST + select PHYLIB + help + The second generation BlueField SoC from Mellanox Technologies + supports an out-of-band Gigabit Ethernet management port to the + Arm subsystem. diff --git a/drivers/net/ethernet/mellanox/mlxbf_gige/Makefile b/drivers/net/ethernet/mellanox/mlxbf_gige/Makefile new file mode 100644 index 000000000000..e57c1375f236 --- /dev/null +++ b/drivers/net/ethernet/mellanox/mlxbf_gige/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-3-Clause + +obj-$(CONFIG_MLXBF_GIGE) += mlxbf_gige.o + +mlxbf_gige-y := mlxbf_gige_ethtool.o \ + mlxbf_gige_gpio.o \ + mlxbf_gige_intr.o \ + mlxbf_gige_main.o \ + mlxbf_gige_mdio.o \ + mlxbf_gige_rx.o \ + mlxbf_gige_tx.o diff --git a/drivers/net/ethernet/mellanox/mlxbf_gige/mlxbf_gige.h b/drivers/net/ethernet/mellanox/mlxbf_gige/mlxbf_gige.h new file mode 100644 index 000000000000..e3509e69ed1c --- /dev/null +++ b/drivers/net/ethernet/mellanox/mlxbf_gige/mlxbf_gige.h @@ -0,0 +1,190 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR BSD-3-Clause */ + +/* Header file for Gigabit Ethernet driver for Mellanox BlueField SoC + * - this file contains software data structures and any chip-specific + * data structures (e.g. TX WQE format) that are memory resident. + * + * Copyright (C) 2020-2021 NVIDIA CORPORATION & AFFILIATES + */ + +#ifndef __MLXBF_GIGE_H__ +#define __MLXBF_GIGE_H__ + +#include <linux/io-64-nonatomic-lo-hi.h> +#include <linux/irqreturn.h> +#include <linux/netdevice.h> +#include <linux/irq.h> + +/* The silicon design supports a maximum RX ring size of + * 32K entries. Based on current testing this maximum size + * is not required to be supported. Instead the RX ring + * will be capped at a realistic value of 1024 entries. + */ +#define MLXBF_GIGE_MIN_RXQ_SZ 32 +#define MLXBF_GIGE_MAX_RXQ_SZ 1024 +#define MLXBF_GIGE_DEFAULT_RXQ_SZ 128 + +#define MLXBF_GIGE_MIN_TXQ_SZ 4 +#define MLXBF_GIGE_MAX_TXQ_SZ 256 +#define MLXBF_GIGE_DEFAULT_TXQ_SZ 128 + +#define MLXBF_GIGE_DEFAULT_BUF_SZ 2048 + +#define MLXBF_GIGE_DMA_PAGE_SZ 4096 +#define MLXBF_GIGE_DMA_PAGE_SHIFT 12 + +/* There are four individual MAC RX filters. Currently + * two of them are being used: one for the broadcast MAC + * (index 0) and one for local MAC (index 1) + */ +#define MLXBF_GIGE_BCAST_MAC_FILTER_IDX 0 +#define MLXBF_GIGE_LOCAL_MAC_FILTER_IDX 1 + +/* Define for broadcast MAC literal */ +#define BCAST_MAC_ADDR 0xFFFFFFFFFFFF + +/* There are three individual interrupts: + * 1) Errors, "OOB" interrupt line + * 2) Receive Packet, "OOB_LLU" interrupt line + * 3) LLU and PLU Events, "OOB_PLU" interrupt line + */ +#define MLXBF_GIGE_ERROR_INTR_IDX 0 +#define MLXBF_GIGE_RECEIVE_PKT_INTR_IDX 1 +#define MLXBF_GIGE_LLU_PLU_INTR_IDX 2 +#define MLXBF_GIGE_PHY_INT_N 3 + +#define MLXBF_GIGE_MDIO_DEFAULT_PHY_ADDR 0x3 + +#define MLXBF_GIGE_DEFAULT_PHY_INT_GPIO 12 + +struct mlxbf_gige_stats { + u64 hw_access_errors; + u64 tx_invalid_checksums; + u64 tx_small_frames; + u64 tx_index_errors; + u64 sw_config_errors; + u64 sw_access_errors; + u64 rx_truncate_errors; + u64 rx_mac_errors; + u64 rx_din_dropped_pkts; + u64 tx_fifo_full; + u64 rx_filter_passed_pkts; + u64 rx_filter_discard_pkts; +}; + +struct mlxbf_gige { + void __iomem *base; + void __iomem *llu_base; + void __iomem *plu_base; + struct device *dev; + struct net_device *netdev; + struct platform_device *pdev; + void __iomem *mdio_io; + struct mii_bus *mdiobus; + void __iomem *gpio_io; + struct irq_domain *irqdomain; + u32 phy_int_gpio_mask; + spinlock_t lock; /* for packet processing indices */ + spinlock_t gpio_lock; /* for GPIO bus access */ + u16 rx_q_entries; + u16 tx_q_entries; + u64 *tx_wqe_base; + dma_addr_t tx_wqe_base_dma; + u64 *tx_wqe_next; + u64 *tx_cc; + dma_addr_t tx_cc_dma; + dma_addr_t *rx_wqe_base; + dma_addr_t rx_wqe_base_dma; + u64 *rx_cqe_base; + dma_addr_t rx_cqe_base_dma; + u16 tx_pi; + u16 prev_tx_ci; + u64 error_intr_count; + u64 rx_intr_count; + u64 llu_plu_intr_count; + struct sk_buff *rx_skb[MLXBF_GIGE_MAX_RXQ_SZ]; + struct sk_buff *tx_skb[MLXBF_GIGE_MAX_TXQ_SZ]; + int error_irq; + int rx_irq; + int llu_plu_irq; + int phy_irq; + int hw_phy_irq; + bool promisc_enabled; + u8 valid_polarity; + struct napi_struct napi; + struct mlxbf_gige_stats stats; +}; + +/* Rx Work Queue Element definitions */ +#define MLXBF_GIGE_RX_WQE_SZ 8 + +/* Rx Completion Queue Element definitions */ +#define MLXBF_GIGE_RX_CQE_SZ 8 +#define MLXBF_GIGE_RX_CQE_PKT_LEN_MASK GENMASK(10, 0) +#define MLXBF_GIGE_RX_CQE_VALID_MASK GENMASK(11, 11) +#define MLXBF_GIGE_RX_CQE_PKT_STATUS_MASK GENMASK(15, 12) +#define MLXBF_GIGE_RX_CQE_PKT_STATUS_MAC_ERR GENMASK(12, 12) +#define MLXBF_GIGE_RX_CQE_PKT_STATUS_TRUNCATED GENMASK(13, 13) +#define MLXBF_GIGE_RX_CQE_CHKSUM_MASK GENMASK(31, 16) + +/* Tx Work Queue Element definitions */ +#define MLXBF_GIGE_TX_WQE_SZ_QWORDS 2 +#define MLXBF_GIGE_TX_WQE_SZ 16 +#define MLXBF_GIGE_TX_WQE_PKT_LEN_MASK GENMASK(10, 0) +#define MLXBF_GIGE_TX_WQE_UPDATE_MASK GENMASK(31, 31) +#define MLXBF_GIGE_TX_WQE_CHKSUM_LEN_MASK GENMASK(42, 32) +#define MLXBF_GIGE_TX_WQE_CHKSUM_START_MASK GENMASK(55, 48) +#define MLXBF_GIGE_TX_WQE_CHKSUM_OFFSET_MASK GENMASK(63, 56) + +/* Macro to return packet length of specified TX WQE */ +#define MLXBF_GIGE_TX_WQE_PKT_LEN(tx_wqe_addr) \ + (*((tx_wqe_addr) + 1) & MLXBF_GIGE_TX_WQE_PKT_LEN_MASK) + +/* Tx Completion Count */ +#define MLXBF_GIGE_TX_CC_SZ 8 + +/* List of resources in ACPI table */ +enum mlxbf_gige_res { + MLXBF_GIGE_RES_MAC, + MLXBF_GIGE_RES_MDIO9, + MLXBF_GIGE_RES_GPIO0, + MLXBF_GIGE_RES_LLU, + MLXBF_GIGE_RES_PLU +}; + +/* Version of register data returned by mlxbf_gige_get_regs() */ +#define MLXBF_GIGE_REGS_VERSION 1 + +int mlxbf_gige_mdio_probe(struct platform_device *pdev, + struct mlxbf_gige *priv); +void mlxbf_gige_mdio_remove(struct mlxbf_gige *priv); +irqreturn_t mlxbf_gige_mdio_handle_phy_interrupt(int irq, void *dev_id); +void mlxbf_gige_mdio_enable_phy_int(struct mlxbf_gige *priv); + +void mlxbf_gige_set_mac_rx_filter(struct mlxbf_gige *priv, + unsigned int index, u64 dmac); +void mlxbf_gige_get_mac_rx_filter(struct mlxbf_gige *priv, + unsigned int index, u64 *dmac); +void mlxbf_gige_enable_promisc(struct mlxbf_gige *priv); +void mlxbf_gige_disable_promisc(struct mlxbf_gige *priv); +int mlxbf_gige_rx_init(struct mlxbf_gige *priv); +void mlxbf_gige_rx_deinit(struct mlxbf_gige *priv); +int mlxbf_gige_tx_init(struct mlxbf_gige *priv); +void mlxbf_gige_tx_deinit(struct mlxbf_gige *priv); +bool mlxbf_gige_handle_tx_complete(struct mlxbf_gige *priv); +netdev_tx_t mlxbf_gige_start_xmit(struct sk_buff *skb, + struct net_device *netdev); +struct sk_buff *mlxbf_gige_alloc_skb(struct mlxbf_gige *priv, + unsigned int map_len, + dma_addr_t *buf_dma, + enum dma_data_direction dir); +int mlxbf_gige_request_irqs(struct mlxbf_gige *priv); +void mlxbf_gige_free_irqs(struct mlxbf_gige *priv); +int mlxbf_gige_poll(struct napi_struct *napi, int budget); +extern const struct ethtool_ops mlxbf_gige_ethtool_ops; +void mlxbf_gige_update_tx_wqe_next(struct mlxbf_gige *priv); + +int mlxbf_gige_gpio_init(struct platform_device *pdev, struct mlxbf_gige *priv); +void mlxbf_gige_gpio_free(struct mlxbf_gige *priv); + +#endif /* !defined(__MLXBF_GIGE_H__) */ diff --git a/drivers/net/ethernet/mellanox/mlxbf_gige/mlxbf_gige_ethtool.c b/drivers/net/ethernet/mellanox/mlxbf_gige/mlxbf_gige_ethtool.c new file mode 100644 index 000000000000..92b798f8e73a --- /dev/null +++ b/drivers/net/ethernet/mellanox/mlxbf_gige/mlxbf_gige_ethtool.c @@ -0,0 +1,137 @@ +// 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. Each 64-bit word is converted + * to big-endian to make the output more readable. + * + * NOTE: 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 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" }, +}; + +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)); +} + +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, + .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, +}; diff --git a/drivers/net/ethernet/mellanox/mlxbf_gige/mlxbf_gige_gpio.c b/drivers/net/ethernet/mellanox/mlxbf_gige/mlxbf_gige_gpio.c new file mode 100644 index 000000000000..a8d966db5715 --- /dev/null +++ b/drivers/net/ethernet/mellanox/mlxbf_gige/mlxbf_gige_gpio.c @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: GPL-2.0-only OR BSD-3-Clause + +/* Initialize and handle GPIO interrupt triggered by INT_N PHY signal. + * This GPIO interrupt triggers the PHY state machine to bring the link + * up/down. + * + * Copyright (C) 2021 NVIDIA CORPORATION & AFFILIATES + */ + +#include <linux/acpi.h> +#include <linux/bitfield.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/gpio/driver.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/irqdomain.h> +#include <linux/irqreturn.h> +#include <linux/platform_device.h> +#include <linux/property.h> + +#include "mlxbf_gige.h" +#include "mlxbf_gige_regs.h" + +#define MLXBF_GIGE_GPIO_CAUSE_FALL_EN 0x48 +#define MLXBF_GIGE_GPIO_CAUSE_OR_CAUSE_EVTEN0 0x80 +#define MLXBF_GIGE_GPIO_CAUSE_OR_EVTEN0 0x94 +#define MLXBF_GIGE_GPIO_CAUSE_OR_CLRCAUSE 0x98 + +static void mlxbf_gige_gpio_enable(struct mlxbf_gige *priv) +{ + unsigned long flags; + u32 val; + + spin_lock_irqsave(&priv->gpio_lock, flags); + val = readl(priv->gpio_io + MLXBF_GIGE_GPIO_CAUSE_OR_CLRCAUSE); + val |= priv->phy_int_gpio_mask; + writel(val, priv->gpio_io + MLXBF_GIGE_GPIO_CAUSE_OR_CLRCAUSE); + + /* The INT_N interrupt level is active low. + * So enable cause fall bit to detect when GPIO + * state goes low. + */ + val = readl(priv->gpio_io + MLXBF_GIGE_GPIO_CAUSE_FALL_EN); + val |= priv->phy_int_gpio_mask; + writel(val, priv->gpio_io + MLXBF_GIGE_GPIO_CAUSE_FALL_EN); + + /* Enable PHY interrupt by setting the priority level */ + val = readl(priv->gpio_io + MLXBF_GIGE_GPIO_CAUSE_OR_EVTEN0); + val |= priv->phy_int_gpio_mask; + writel(val, priv->gpio_io + MLXBF_GIGE_GPIO_CAUSE_OR_EVTEN0); + spin_unlock_irqrestore(&priv->gpio_lock, flags); +} + +static void mlxbf_gige_gpio_disable(struct mlxbf_gige *priv) +{ + unsigned long flags; + u32 val; + + spin_lock_irqsave(&priv->gpio_lock, flags); + val = readl(priv->gpio_io + MLXBF_GIGE_GPIO_CAUSE_OR_EVTEN0); + val &= ~priv->phy_int_gpio_mask; + writel(val, priv->gpio_io + MLXBF_GIGE_GPIO_CAUSE_OR_EVTEN0); + spin_unlock_irqrestore(&priv->gpio_lock, flags); +} + +static irqreturn_t mlxbf_gige_gpio_handler(int irq, void *ptr) +{ + struct mlxbf_gige *priv; + u32 val; + + priv = ptr; + + /* Check if this interrupt is from PHY device. + * Return if it is not. + */ + val = readl(priv->gpio_io + MLXBF_GIGE_GPIO_CAUSE_OR_CAUSE_EVTEN0); + if (!(val & priv->phy_int_gpio_mask)) + return IRQ_NONE; + + /* Clear interrupt when done, otherwise, no further interrupt + * will be triggered. + */ + val = readl(priv->gpio_io + MLXBF_GIGE_GPIO_CAUSE_OR_CLRCAUSE); + val |= priv->phy_int_gpio_mask; + writel(val, priv->gpio_io + MLXBF_GIGE_GPIO_CAUSE_OR_CLRCAUSE); + + generic_handle_irq(priv->phy_irq); + + return IRQ_HANDLED; +} + +static void mlxbf_gige_gpio_mask(struct irq_data *irqd) +{ + struct mlxbf_gige *priv = irq_data_get_irq_chip_data(irqd); + + mlxbf_gige_gpio_disable(priv); +} + +static void mlxbf_gige_gpio_unmask(struct irq_data *irqd) +{ + struct mlxbf_gige *priv = irq_data_get_irq_chip_data(irqd); + + mlxbf_gige_gpio_enable(priv); +} + +static struct irq_chip mlxbf_gige_gpio_chip = { + .name = "mlxbf_gige_phy", + .irq_mask = mlxbf_gige_gpio_mask, + .irq_unmask = mlxbf_gige_gpio_unmask, +}; + +static int mlxbf_gige_gpio_domain_map(struct irq_domain *d, + unsigned int irq, + irq_hw_number_t hwirq) +{ + irq_set_chip_data(irq, d->host_data); + irq_set_chip_and_handler(irq, &mlxbf_gige_gpio_chip, handle_simple_irq); + irq_set_noprobe(irq); + + return 0; +} + +static const struct irq_domain_ops mlxbf_gige_gpio_domain_ops = { + .map = mlxbf_gige_gpio_domain_map, + .xlate = irq_domain_xlate_twocell, +}; + +#ifdef CONFIG_ACPI +static int mlxbf_gige_gpio_resources(struct acpi_resource *ares, + void *data) +{ + struct acpi_resource_gpio *gpio; + u32 *phy_int_gpio = data; + + if (ares->type == ACPI_RESOURCE_TYPE_GPIO) { + gpio = &ares->data.gpio; + *phy_int_gpio = gpio->pin_table[0]; + } + + return 1; +} +#endif + +void mlxbf_gige_gpio_free(struct mlxbf_gige *priv) +{ + irq_dispose_mapping(priv->phy_irq); + irq_domain_remove(priv->irqdomain); +} + +int mlxbf_gige_gpio_init(struct platform_device *pdev, + struct mlxbf_gige *priv) +{ + struct device *dev = &pdev->dev; + struct resource *res; + u32 phy_int_gpio = 0; + int ret; + + LIST_HEAD(resources); + + res = platform_get_resource(pdev, IORESOURCE_MEM, MLXBF_GIGE_RES_GPIO0); + if (!res) + return -ENODEV; + + priv->gpio_io = devm_ioremap(dev, res->start, resource_size(res)); + if (!priv->gpio_io) + return -ENOMEM; + +#ifdef CONFIG_ACPI + ret = acpi_dev_get_resources(ACPI_COMPANION(dev), + &resources, mlxbf_gige_gpio_resources, + &phy_int_gpio); + acpi_dev_free_resource_list(&resources); + if (ret < 0 || !phy_int_gpio) { + dev_err(dev, "Error retrieving the gpio phy pin"); + return -EINVAL; + } +#endif + + priv->phy_int_gpio_mask = BIT(phy_int_gpio); + + mlxbf_gige_gpio_disable(priv); + + priv->hw_phy_irq = platform_get_irq(pdev, MLXBF_GIGE_PHY_INT_N); + + priv->irqdomain = irq_domain_add_simple(NULL, 1, 0, + &mlxbf_gige_gpio_domain_ops, + priv); + if (!priv->irqdomain) { + dev_err(dev, "Failed to add IRQ domain\n"); + return -ENOMEM; + } + + priv->phy_irq = irq_create_mapping(priv->irqdomain, 0); + if (!priv->phy_irq) { + irq_domain_remove(priv->irqdomain); + priv->irqdomain = NULL; + dev_err(dev, "Error mapping PHY IRQ\n"); + return -EINVAL; + } + + ret = devm_request_irq(dev, priv->hw_phy_irq, mlxbf_gige_gpio_handler, + IRQF_ONESHOT | IRQF_SHARED, "mlxbf_gige_phy", priv); + if (ret) { + dev_err(dev, "Failed to request PHY IRQ"); + mlxbf_gige_gpio_free(priv); + return ret; + } + + return ret; +} diff --git a/drivers/net/ethernet/mellanox/mlxbf_gige/mlxbf_gige_intr.c b/drivers/net/ethernet/mellanox/mlxbf_gige/mlxbf_gige_intr.c new file mode 100644 index 000000000000..c38795be04a2 --- /dev/null +++ b/drivers/net/ethernet/mellanox/mlxbf_gige/mlxbf_gige_intr.c @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-2.0-only OR BSD-3-Clause + +/* Interrupt related logic for Mellanox Gigabit Ethernet driver + * + * Copyright (C) 2020-2021 NVIDIA CORPORATION & AFFILIATES + */ + +#include <linux/interrupt.h> + +#include "mlxbf_gige.h" +#include "mlxbf_gige_regs.h" + +static irqreturn_t mlxbf_gige_error_intr(int irq, void *dev_id) +{ + struct mlxbf_gige *priv; + u64 int_status; + + priv = dev_id; + + priv->error_intr_count++; + + int_status = readq(priv->base + MLXBF_GIGE_INT_STATUS); + + if (int_status & MLXBF_GIGE_INT_STATUS_HW_ACCESS_ERROR) + priv->stats.hw_access_errors++; + + if (int_status & MLXBF_GIGE_INT_STATUS_TX_CHECKSUM_INPUTS) { + priv->stats.tx_invalid_checksums++; + /* This error condition is latched into MLXBF_GIGE_INT_STATUS + * when the GigE silicon operates on the offending + * TX WQE. The write to MLXBF_GIGE_INT_STATUS at the bottom + * of this routine clears this error condition. + */ + } + + if (int_status & MLXBF_GIGE_INT_STATUS_TX_SMALL_FRAME_SIZE) { + priv->stats.tx_small_frames++; + /* This condition happens when the networking stack invokes + * this driver's "start_xmit()" method with a packet whose + * size < 60 bytes. The GigE silicon will automatically pad + * this small frame up to a minimum-sized frame before it is + * sent. The "tx_small_frame" condition is latched into the + * MLXBF_GIGE_INT_STATUS register when the GigE silicon + * operates on the offending TX WQE. The write to + * MLXBF_GIGE_INT_STATUS at the bottom of this routine + * clears this condition. + */ + } + + if (int_status & MLXBF_GIGE_INT_STATUS_TX_PI_CI_EXCEED_WQ_SIZE) + priv->stats.tx_index_errors++; + + if (int_status & MLXBF_GIGE_INT_STATUS_SW_CONFIG_ERROR) + priv->stats.sw_config_errors++; + + if (int_status & MLXBF_GIGE_INT_STATUS_SW_ACCESS_ERROR) + priv->stats.sw_access_errors++; + + /* Clear all error interrupts by writing '1' back to + * all the asserted bits in INT_STATUS. Do not write + * '1' back to 'receive packet' bit, since that is + * managed separately. + */ + + int_status &= ~MLXBF_GIGE_INT_STATUS_RX_RECEIVE_PACKET; + + writeq(int_status, priv->base + MLXBF_GIGE_INT_STATUS); + + return IRQ_HANDLED; +} + +static irqreturn_t mlxbf_gige_rx_intr(int irq, void *dev_id) +{ + struct mlxbf_gige *priv; + + priv = dev_id; + + priv->rx_intr_count++; + + /* NOTE: GigE silicon automatically disables "packet rx" interrupt by + * setting MLXBF_GIGE_INT_MASK bit0 upon triggering the interrupt + * to the ARM cores. Software needs to re-enable "packet rx" + * interrupts by clearing MLXBF_GIGE_INT_MASK bit0. + */ + + napi_schedule(&priv->napi); + + return IRQ_HANDLED; +} + +static irqreturn_t mlxbf_gige_llu_plu_intr(int irq, void *dev_id) +{ + struct mlxbf_gige *priv; + + priv = dev_id; + priv->llu_plu_intr_count++; + + return IRQ_HANDLED; +} + +int mlxbf_gige_request_irqs(struct mlxbf_gige *priv) +{ + int err; + + err = request_irq(priv->error_irq, mlxbf_gige_error_intr, 0, + "mlxbf_gige_error", priv); + if (err) { + dev_err(priv->dev, "Request error_irq failure\n"); + return err; + } + + err = request_irq(priv->rx_irq, mlxbf_gige_rx_intr, 0, + "mlxbf_gige_rx", priv); + if (err) { + dev_err(priv->dev, "Request rx_irq failure\n"); + goto free_error_irq; + } + + err = request_irq(priv->llu_plu_irq, mlxbf_gige_llu_plu_intr, 0, + "mlxbf_gige_llu_plu", priv); + if (err) { + dev_err(priv->dev, "Request llu_plu_irq failure\n"); + goto free_rx_irq; + } + + return 0; + +free_rx_irq: + free_irq(priv->rx_irq, priv); + +free_error_irq: + free_irq(priv->error_irq, priv); + + return err; +} + +void mlxbf_gige_free_irqs(struct mlxbf_gige *priv) +{ + free_irq(priv->error_irq, priv); + free_irq(priv->rx_irq, priv); + free_irq(priv->llu_plu_irq, priv); +} diff --git a/drivers/net/ethernet/mellanox/mlxbf_gige/mlxbf_gige_main.c b/drivers/net/ethernet/mellanox/mlxbf_gige/mlxbf_gige_main.c new file mode 100644 index 000000000000..a0a059e0154f --- /dev/null +++ b/drivers/net/ethernet/mellanox/mlxbf_gige/mlxbf_gige_main.c @@ -0,0 +1,452 @@ +// SPDX-License-Identifier: GPL-2.0-only OR BSD-3-Clause + +/* Gigabit Ethernet driver for Mellanox BlueField SoC + * + * Copyright (C) 2020-2021 NVIDIA CORPORATION & AFFILIATES + */ + +#include <linux/acpi.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/etherdevice.h> +#include <linux/interrupt.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/phy.h> +#include <linux/platform_device.h> +#include <linux/skbuff.h> + +#include "mlxbf_gige.h" +#include "mlxbf_gige_regs.h" + +#define DRV_NAME "mlxbf_gige" + +/* Allocate SKB whose payload pointer aligns with the Bluefield + * hardware DMA limitation, i.e. DMA operation can't cross + * a 4KB boundary. A maximum packet size of 2KB is assumed in the + * alignment formula. The alignment logic overallocates an SKB, + * and then adjusts the headroom so that the SKB data pointer is + * naturally aligned to a 2KB boundary. + */ +struct sk_buff *mlxbf_gige_alloc_skb(struct mlxbf_gige *priv, + unsigned int map_len, + dma_addr_t *buf_dma, + enum dma_data_direction dir) +{ + struct sk_buff *skb; + u64 addr, offset; + + /* Overallocate the SKB so that any headroom adjustment (to + * provide 2KB natural alignment) does not exceed payload area + */ + skb = netdev_alloc_skb(priv->netdev, MLXBF_GIGE_DEFAULT_BUF_SZ * 2); + if (!skb) + return NULL; + + /* Adjust the headroom so that skb->data is naturally aligned to + * a 2KB boundary, which is the maximum packet size supported. + */ + addr = (long)skb->data; + offset = (addr + MLXBF_GIGE_DEFAULT_BUF_SZ - 1) & + ~(MLXBF_GIGE_DEFAULT_BUF_SZ - 1); + offset -= addr; + if (offset) + skb_reserve(skb, offset); + + /* Return streaming DMA mapping to caller */ + *buf_dma = dma_map_single(priv->dev, skb->data, map_len, dir); + if (dma_mapping_error(priv->dev, *buf_dma)) { + dev_kfree_skb(skb); + *buf_dma = (dma_addr_t)0; + return NULL; + } + + return skb; +} + +static void mlxbf_gige_initial_mac(struct mlxbf_gige *priv) +{ + u8 mac[ETH_ALEN]; + u64 local_mac; + + memset(mac, 0, ETH_ALEN); + mlxbf_gige_get_mac_rx_filter(priv, MLXBF_GIGE_LOCAL_MAC_FILTER_IDX, + &local_mac); + u64_to_ether_addr(local_mac, mac); + + if (is_valid_ether_addr(mac)) { + ether_addr_copy(priv->netdev->dev_addr, mac); + } else { + /* Provide a random MAC if for some reason the device has + * not been configured with a valid MAC address already. + */ + eth_hw_addr_random(priv->netdev); + } + + local_mac = ether_addr_to_u64(priv->netdev->dev_addr); + mlxbf_gige_set_mac_rx_filter(priv, MLXBF_GIGE_LOCAL_MAC_FILTER_IDX, + local_mac); +} + +static void mlxbf_gige_cache_stats(struct mlxbf_gige *priv) +{ + struct mlxbf_gige_stats *p; + + /* Cache stats that will be cleared by clean port operation */ + p = &priv->stats; + p->rx_din_dropped_pkts += readq(priv->base + + MLXBF_GIGE_RX_DIN_DROP_COUNTER); + p->rx_filter_passed_pkts += readq(priv->base + + MLXBF_GIGE_RX_PASS_COUNTER_ALL); + p->rx_filter_discard_pkts += readq(priv->base + + MLXBF_GIGE_RX_DISC_COUNTER_ALL); +} + +static int mlxbf_gige_clean_port(struct mlxbf_gige *priv) +{ + u64 control; + u64 temp; + int err; + + /* Set the CLEAN_PORT_EN bit to trigger SW reset */ + control = readq(priv->base + MLXBF_GIGE_CONTROL); + control |= MLXBF_GIGE_CONTROL_CLEAN_PORT_EN; + writeq(control, priv->base + MLXBF_GIGE_CONTROL); + + /* Ensure completion of "clean port" write before polling status */ + mb(); + + err = readq_poll_timeout_atomic(priv->base + MLXBF_GIGE_STATUS, temp, + (temp & MLXBF_GIGE_STATUS_READY), + 100, 100000); + + /* Clear the CLEAN_PORT_EN bit at end of this loop */ + control = readq(priv->base + MLXBF_GIGE_CONTROL); + control &= ~MLXBF_GIGE_CONTROL_CLEAN_PORT_EN; + writeq(control, priv->base + MLXBF_GIGE_CONTROL); + + return err; +} + +static int mlxbf_gige_open(struct net_device *netdev) +{ + struct mlxbf_gige *priv = netdev_priv(netdev); + struct phy_device *phydev = netdev->phydev; + u64 int_en; + int err; + + err = mlxbf_gige_request_irqs(priv); + if (err) + return err; + mlxbf_gige_cache_stats(priv); + err = mlxbf_gige_clean_port(priv); + if (err) + goto free_irqs; + err = mlxbf_gige_rx_init(priv); + if (err) + goto free_irqs; + err = mlxbf_gige_tx_init(priv); + if (er |