summaryrefslogtreecommitdiffstats
path: root/drivers/staging
diff options
context:
space:
mode:
authorChris Packham <chris.packham@alliedtelesis.co.nz>2020-02-05 13:11:12 +1300
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2020-02-12 13:50:32 -0800
commit422d97b8b05ed38cc5f67522ddb821868ea272a7 (patch)
treeaecc83db4ac470447b390c93c9ed6438b8643429 /drivers/staging
parent96b06c0a16f737e9ea7dff1e23dd5f6d847e6731 (diff)
Revert "staging: octeon: delete driver"
This reverts commit 710d7fbe21ee2ceab121f1f84a20edf68f9f9742. Re-instate the code so subsequent commits can clean it up and get it building properly. Signed-off-by: Chris Packham <chris.packham@alliedtelesis.co.nz> Link: https://lore.kernel.org/r/20200205001116.14096-3-chris.packham@alliedtelesis.co.nz Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/staging')
-rw-r--r--drivers/staging/Kconfig2
-rw-r--r--drivers/staging/Makefile1
-rw-r--r--drivers/staging/octeon/Kconfig16
-rw-r--r--drivers/staging/octeon/Makefile19
-rw-r--r--drivers/staging/octeon/TODO9
-rw-r--r--drivers/staging/octeon/ethernet-defines.h40
-rw-r--r--drivers/staging/octeon/ethernet-mdio.c178
-rw-r--r--drivers/staging/octeon/ethernet-mdio.h28
-rw-r--r--drivers/staging/octeon/ethernet-mem.c154
-rw-r--r--drivers/staging/octeon/ethernet-mem.h9
-rw-r--r--drivers/staging/octeon/ethernet-rgmii.c158
-rw-r--r--drivers/staging/octeon/ethernet-rx.c538
-rw-r--r--drivers/staging/octeon/ethernet-rx.h31
-rw-r--r--drivers/staging/octeon/ethernet-sgmii.c30
-rw-r--r--drivers/staging/octeon/ethernet-spi.c226
-rw-r--r--drivers/staging/octeon/ethernet-tx.c717
-rw-r--r--drivers/staging/octeon/ethernet-tx.h14
-rw-r--r--drivers/staging/octeon/ethernet-util.h47
-rw-r--r--drivers/staging/octeon/ethernet.c992
-rw-r--r--drivers/staging/octeon/octeon-ethernet.h107
-rw-r--r--drivers/staging/octeon/octeon-stubs.h1433
21 files changed, 4749 insertions, 0 deletions
diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig
index 9ec982d16e1c..f46c1f7d0d39 100644
--- a/drivers/staging/Kconfig
+++ b/drivers/staging/Kconfig
@@ -42,6 +42,8 @@ source "drivers/staging/rtl8188eu/Kconfig"
source "drivers/staging/rts5208/Kconfig"
+source "drivers/staging/octeon/Kconfig"
+
source "drivers/staging/octeon-usb/Kconfig"
source "drivers/staging/vt6655/Kconfig"
diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile
index be3f0a814bf2..bb26d753066c 100644
--- a/drivers/staging/Makefile
+++ b/drivers/staging/Makefile
@@ -12,6 +12,7 @@ obj-$(CONFIG_R8712U) += rtl8712/
obj-$(CONFIG_R8188EU) += rtl8188eu/
obj-$(CONFIG_RTS5208) += rts5208/
obj-$(CONFIG_NETLOGIC_XLR_NET) += netlogic/
+obj-$(CONFIG_OCTEON_ETHERNET) += octeon/
obj-$(CONFIG_OCTEON_USB) += octeon-usb/
obj-$(CONFIG_VT6655) += vt6655/
obj-$(CONFIG_VT6656) += vt6656/
diff --git a/drivers/staging/octeon/Kconfig b/drivers/staging/octeon/Kconfig
new file mode 100644
index 000000000000..e7f4ddcc1361
--- /dev/null
+++ b/drivers/staging/octeon/Kconfig
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0
+config OCTEON_ETHERNET
+ tristate "Cavium Networks Octeon Ethernet support"
+ depends on CAVIUM_OCTEON_SOC || COMPILE_TEST
+ depends on NETDEVICES
+ depends on BROKEN
+ select PHYLIB
+ select MDIO_OCTEON
+ help
+ This driver supports the builtin ethernet ports on Cavium
+ Networks' products in the Octeon family. This driver supports the
+ CN3XXX and CN5XXX Octeon processors.
+
+ To compile this driver as a module, choose M here. The module
+ will be called octeon-ethernet.
+
diff --git a/drivers/staging/octeon/Makefile b/drivers/staging/octeon/Makefile
new file mode 100644
index 000000000000..3887cf5f1e84
--- /dev/null
+++ b/drivers/staging/octeon/Makefile
@@ -0,0 +1,19 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (C) 2005-2009 Cavium Networks
+#
+
+#
+# Makefile for Cavium OCTEON on-board ethernet driver
+#
+
+obj-${CONFIG_OCTEON_ETHERNET} := octeon-ethernet.o
+
+octeon-ethernet-y := ethernet.o
+octeon-ethernet-y += ethernet-mdio.o
+octeon-ethernet-y += ethernet-mem.o
+octeon-ethernet-y += ethernet-rgmii.o
+octeon-ethernet-y += ethernet-rx.o
+octeon-ethernet-y += ethernet-sgmii.o
+octeon-ethernet-y += ethernet-spi.o
+octeon-ethernet-y += ethernet-tx.o
diff --git a/drivers/staging/octeon/TODO b/drivers/staging/octeon/TODO
new file mode 100644
index 000000000000..67a0a1f6b922
--- /dev/null
+++ b/drivers/staging/octeon/TODO
@@ -0,0 +1,9 @@
+This driver is functional and supports Ethernet on OCTEON+/OCTEON2/OCTEON3
+chips at least up to CN7030.
+
+TODO:
+ - general code review and clean up
+ - make driver self-contained instead of being split between staging and
+ arch/mips/cavium-octeon.
+
+Contact: Aaro Koskinen <aaro.koskinen@iki.fi>
diff --git a/drivers/staging/octeon/ethernet-defines.h b/drivers/staging/octeon/ethernet-defines.h
new file mode 100644
index 000000000000..ef9e767b0e2e
--- /dev/null
+++ b/drivers/staging/octeon/ethernet-defines.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * This file is based on code from OCTEON SDK by Cavium Networks.
+ *
+ * Copyright (c) 2003-2007 Cavium Networks
+ */
+
+/*
+ * A few defines are used to control the operation of this driver:
+ * USE_ASYNC_IOBDMA
+ * Use asynchronous IO access to hardware. This uses Octeon's asynchronous
+ * IOBDMAs to issue IO accesses without stalling. Set this to zero
+ * to disable this. Note that IOBDMAs require CVMSEG.
+ * REUSE_SKBUFFS_WITHOUT_FREE
+ * Allows the TX path to free an skbuff into the FPA hardware pool. This
+ * can significantly improve performance for forwarding and bridging, but
+ * may be somewhat dangerous. Checks are made, but if any buffer is reused
+ * without the proper Linux cleanup, the networking stack may have very
+ * bizarre bugs.
+ */
+#ifndef __ETHERNET_DEFINES_H__
+#define __ETHERNET_DEFINES_H__
+
+#ifdef CONFIG_NETFILTER
+#define REUSE_SKBUFFS_WITHOUT_FREE 0
+#else
+#define REUSE_SKBUFFS_WITHOUT_FREE 1
+#endif
+
+#define USE_ASYNC_IOBDMA (CONFIG_CAVIUM_OCTEON_CVMSEG_SIZE > 0)
+
+/* Maximum number of SKBs to try to free per xmit packet. */
+#define MAX_OUT_QUEUE_DEPTH 1000
+
+#define FAU_TOTAL_TX_TO_CLEAN (CVMX_FAU_REG_END - sizeof(u32))
+#define FAU_NUM_PACKET_BUFFERS_TO_FREE (FAU_TOTAL_TX_TO_CLEAN - sizeof(u32))
+
+#define TOTAL_NUMBER_OF_PORTS (CVMX_PIP_NUM_INPUT_PORTS + 1)
+
+#endif /* __ETHERNET_DEFINES_H__ */
diff --git a/drivers/staging/octeon/ethernet-mdio.c b/drivers/staging/octeon/ethernet-mdio.c
new file mode 100644
index 000000000000..c798672d61b2
--- /dev/null
+++ b/drivers/staging/octeon/ethernet-mdio.c
@@ -0,0 +1,178 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * This file is based on code from OCTEON SDK by Cavium Networks.
+ *
+ * Copyright (c) 2003-2007 Cavium Networks
+ */
+
+#include <linux/kernel.h>
+#include <linux/ethtool.h>
+#include <linux/phy.h>
+#include <linux/ratelimit.h>
+#include <linux/of_mdio.h>
+#include <generated/utsrelease.h>
+#include <net/dst.h>
+
+#include "octeon-ethernet.h"
+#include "ethernet-defines.h"
+#include "ethernet-mdio.h"
+#include "ethernet-util.h"
+
+static void cvm_oct_get_drvinfo(struct net_device *dev,
+ struct ethtool_drvinfo *info)
+{
+ strlcpy(info->driver, KBUILD_MODNAME, sizeof(info->driver));
+ strlcpy(info->version, UTS_RELEASE, sizeof(info->version));
+ strlcpy(info->bus_info, "Builtin", sizeof(info->bus_info));
+}
+
+static int cvm_oct_nway_reset(struct net_device *dev)
+{
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+
+ if (dev->phydev)
+ return phy_start_aneg(dev->phydev);
+
+ return -EINVAL;
+}
+
+const struct ethtool_ops cvm_oct_ethtool_ops = {
+ .get_drvinfo = cvm_oct_get_drvinfo,
+ .nway_reset = cvm_oct_nway_reset,
+ .get_link = ethtool_op_get_link,
+ .get_link_ksettings = phy_ethtool_get_link_ksettings,
+ .set_link_ksettings = phy_ethtool_set_link_ksettings,
+};
+
+/**
+ * cvm_oct_ioctl - IOCTL support for PHY control
+ * @dev: Device to change
+ * @rq: the request
+ * @cmd: the command
+ *
+ * Returns Zero on success
+ */
+int cvm_oct_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
+{
+ if (!netif_running(dev))
+ return -EINVAL;
+
+ if (!dev->phydev)
+ return -EINVAL;
+
+ return phy_mii_ioctl(dev->phydev, rq, cmd);
+}
+
+void cvm_oct_note_carrier(struct octeon_ethernet *priv,
+ union cvmx_helper_link_info li)
+{
+ if (li.s.link_up) {
+ pr_notice_ratelimited("%s: %u Mbps %s duplex, port %d, queue %d\n",
+ netdev_name(priv->netdev), li.s.speed,
+ (li.s.full_duplex) ? "Full" : "Half",
+ priv->port, priv->queue);
+ } else {
+ pr_notice_ratelimited("%s: Link down\n",
+ netdev_name(priv->netdev));
+ }
+}
+
+void cvm_oct_adjust_link(struct net_device *dev)
+{
+ struct octeon_ethernet *priv = netdev_priv(dev);
+ union cvmx_helper_link_info link_info;
+
+ link_info.u64 = 0;
+ link_info.s.link_up = dev->phydev->link ? 1 : 0;
+ link_info.s.full_duplex = dev->phydev->duplex ? 1 : 0;
+ link_info.s.speed = dev->phydev->speed;
+ priv->link_info = link_info.u64;
+
+ /*
+ * The polling task need to know about link status changes.
+ */
+ if (priv->poll)
+ priv->poll(dev);
+
+ if (priv->last_link != dev->phydev->link) {
+ priv->last_link = dev->phydev->link;
+ cvmx_helper_link_set(priv->port, link_info);
+ cvm_oct_note_carrier(priv, link_info);
+ }
+}
+
+int cvm_oct_common_stop(struct net_device *dev)
+{
+ struct octeon_ethernet *priv = netdev_priv(dev);
+ int interface = INTERFACE(priv->port);
+ union cvmx_helper_link_info link_info;
+ union cvmx_gmxx_prtx_cfg gmx_cfg;
+ int index = INDEX(priv->port);
+
+ gmx_cfg.u64 = cvmx_read_csr(CVMX_GMXX_PRTX_CFG(index, interface));
+ gmx_cfg.s.en = 0;
+ cvmx_write_csr(CVMX_GMXX_PRTX_CFG(index, interface), gmx_cfg.u64);
+
+ priv->poll = NULL;
+
+ if (dev->phydev)
+ phy_disconnect(dev->phydev);
+
+ if (priv->last_link) {
+ link_info.u64 = 0;
+ priv->last_link = 0;
+
+ cvmx_helper_link_set(priv->port, link_info);
+ cvm_oct_note_carrier(priv, link_info);
+ }
+ return 0;
+}
+
+/**
+ * cvm_oct_phy_setup_device - setup the PHY
+ *
+ * @dev: Device to setup
+ *
+ * Returns Zero on success, negative on failure
+ */
+int cvm_oct_phy_setup_device(struct net_device *dev)
+{
+ struct octeon_ethernet *priv = netdev_priv(dev);
+ struct device_node *phy_node;
+ struct phy_device *phydev = NULL;
+
+ if (!priv->of_node)
+ goto no_phy;
+
+ phy_node = of_parse_phandle(priv->of_node, "phy-handle", 0);
+ if (!phy_node && of_phy_is_fixed_link(priv->of_node)) {
+ int rc;
+
+ rc = of_phy_register_fixed_link(priv->of_node);
+ if (rc)
+ return rc;
+
+ phy_node = of_node_get(priv->of_node);
+ }
+ if (!phy_node)
+ goto no_phy;
+
+ phydev = of_phy_connect(dev, phy_node, cvm_oct_adjust_link, 0,
+ priv->phy_mode);
+ of_node_put(phy_node);
+
+ if (!phydev)
+ return -ENODEV;
+
+ priv->last_link = 0;
+ phy_start(phydev);
+
+ return 0;
+no_phy:
+ /* If there is no phy, assume a direct MAC connection and that
+ * the link is up.
+ */
+ netif_carrier_on(dev);
+ return 0;
+}
diff --git a/drivers/staging/octeon/ethernet-mdio.h b/drivers/staging/octeon/ethernet-mdio.h
new file mode 100644
index 000000000000..e3771d48c49b
--- /dev/null
+++ b/drivers/staging/octeon/ethernet-mdio.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * This file is based on code from OCTEON SDK by Cavium Networks.
+ *
+ * Copyright (c) 2003-2007 Cavium Networks
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/ip.h>
+#include <linux/string.h>
+#include <linux/ethtool.h>
+#include <linux/seq_file.h>
+#include <linux/proc_fs.h>
+#include <net/dst.h>
+#ifdef CONFIG_XFRM
+#include <linux/xfrm.h>
+#include <net/xfrm.h>
+#endif /* CONFIG_XFRM */
+
+extern const struct ethtool_ops cvm_oct_ethtool_ops;
+
+void octeon_mdiobus_force_mod_depencency(void);
+
+int cvm_oct_ioctl(struct net_device *dev, struct ifreq *rq, int cmd);
+int cvm_oct_phy_setup_device(struct net_device *dev);
diff --git a/drivers/staging/octeon/ethernet-mem.c b/drivers/staging/octeon/ethernet-mem.c
new file mode 100644
index 000000000000..532594957ebc
--- /dev/null
+++ b/drivers/staging/octeon/ethernet-mem.c
@@ -0,0 +1,154 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * This file is based on code from OCTEON SDK by Cavium Networks.
+ *
+ * Copyright (c) 2003-2010 Cavium Networks
+ */
+
+#include <linux/kernel.h>
+#include <linux/netdevice.h>
+#include <linux/slab.h>
+
+#include "octeon-ethernet.h"
+#include "ethernet-mem.h"
+#include "ethernet-defines.h"
+
+/**
+ * cvm_oct_fill_hw_skbuff - fill the supplied hardware pool with skbuffs
+ * @pool: Pool to allocate an skbuff for
+ * @size: Size of the buffer needed for the pool
+ * @elements: Number of buffers to allocate
+ *
+ * Returns the actual number of buffers allocated.
+ */
+static int cvm_oct_fill_hw_skbuff(int pool, int size, int elements)
+{
+ int freed = elements;
+
+ while (freed) {
+ struct sk_buff *skb = dev_alloc_skb(size + 256);
+
+ if (unlikely(!skb))
+ break;
+ skb_reserve(skb, 256 - (((unsigned long)skb->data) & 0x7f));
+ *(struct sk_buff **)(skb->data - sizeof(void *)) = skb;
+ cvmx_fpa_free(skb->data, pool, size / 128);
+ freed--;
+ }
+ return elements - freed;
+}
+
+/**
+ * cvm_oct_free_hw_skbuff- free hardware pool skbuffs
+ * @pool: Pool to allocate an skbuff for
+ * @size: Size of the buffer needed for the pool
+ * @elements: Number of buffers to allocate
+ */
+static void cvm_oct_free_hw_skbuff(int pool, int size, int elements)
+{
+ char *memory;
+
+ do {
+ memory = cvmx_fpa_alloc(pool);
+ if (memory) {
+ struct sk_buff *skb =
+ *(struct sk_buff **)(memory - sizeof(void *));
+ elements--;
+ dev_kfree_skb(skb);
+ }
+ } while (memory);
+
+ if (elements < 0)
+ pr_warn("Freeing of pool %u had too many skbuffs (%d)\n",
+ pool, elements);
+ else if (elements > 0)
+ pr_warn("Freeing of pool %u is missing %d skbuffs\n",
+ pool, elements);
+}
+
+/**
+ * cvm_oct_fill_hw_memory - fill a hardware pool with memory.
+ * @pool: Pool to populate
+ * @size: Size of each buffer in the pool
+ * @elements: Number of buffers to allocate
+ *
+ * Returns the actual number of buffers allocated.
+ */
+static int cvm_oct_fill_hw_memory(int pool, int size, int elements)
+{
+ char *memory;
+ char *fpa;
+ int freed = elements;
+
+ while (freed) {
+ /*
+ * FPA memory must be 128 byte aligned. Since we are
+ * aligning we need to save the original pointer so we
+ * can feed it to kfree when the memory is returned to
+ * the kernel.
+ *
+ * We allocate an extra 256 bytes to allow for
+ * alignment and space for the original pointer saved
+ * just before the block.
+ */
+ memory = kmalloc(size + 256, GFP_ATOMIC);
+ if (unlikely(!memory)) {
+ pr_warn("Unable to allocate %u bytes for FPA pool %d\n",
+ elements * size, pool);
+ break;
+ }
+ fpa = (char *)(((unsigned long)memory + 256) & ~0x7fUL);
+ *((char **)fpa - 1) = memory;
+ cvmx_fpa_free(fpa, pool, 0);
+ freed--;
+ }
+ return elements - freed;
+}
+
+/**
+ * cvm_oct_free_hw_memory - Free memory allocated by cvm_oct_fill_hw_memory
+ * @pool: FPA pool to free
+ * @size: Size of each buffer in the pool
+ * @elements: Number of buffers that should be in the pool
+ */
+static void cvm_oct_free_hw_memory(int pool, int size, int elements)
+{
+ char *memory;
+ char *fpa;
+
+ do {
+ fpa = cvmx_fpa_alloc(pool);
+ if (fpa) {
+ elements--;
+ fpa = (char *)phys_to_virt(cvmx_ptr_to_phys(fpa));
+ memory = *((char **)fpa - 1);
+ kfree(memory);
+ }
+ } while (fpa);
+
+ if (elements < 0)
+ pr_warn("Freeing of pool %u had too many buffers (%d)\n",
+ pool, elements);
+ else if (elements > 0)
+ pr_warn("Warning: Freeing of pool %u is missing %d buffers\n",
+ pool, elements);
+}
+
+int cvm_oct_mem_fill_fpa(int pool, int size, int elements)
+{
+ int freed;
+
+ if (pool == CVMX_FPA_PACKET_POOL)
+ freed = cvm_oct_fill_hw_skbuff(pool, size, elements);
+ else
+ freed = cvm_oct_fill_hw_memory(pool, size, elements);
+ return freed;
+}
+
+void cvm_oct_mem_empty_fpa(int pool, int size, int elements)
+{
+ if (pool == CVMX_FPA_PACKET_POOL)
+ cvm_oct_free_hw_skbuff(pool, size, elements);
+ else
+ cvm_oct_free_hw_memory(pool, size, elements);
+}
diff --git a/drivers/staging/octeon/ethernet-mem.h b/drivers/staging/octeon/ethernet-mem.h
new file mode 100644
index 000000000000..692dcdb7154d
--- /dev/null
+++ b/drivers/staging/octeon/ethernet-mem.h
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * This file is based on code from OCTEON SDK by Cavium Networks.
+ *
+ * Copyright (c) 2003-2007 Cavium Networks
+ */
+
+int cvm_oct_mem_fill_fpa(int pool, int size, int elements);
+void cvm_oct_mem_empty_fpa(int pool, int size, int elements);
diff --git a/drivers/staging/octeon/ethernet-rgmii.c b/drivers/staging/octeon/ethernet-rgmii.c
new file mode 100644
index 000000000000..0c4fac31540a
--- /dev/null
+++ b/drivers/staging/octeon/ethernet-rgmii.c
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * This file is based on code from OCTEON SDK by Cavium Networks.
+ *
+ * Copyright (c) 2003-2007 Cavium Networks
+ */
+
+#include <linux/kernel.h>
+#include <linux/netdevice.h>
+#include <linux/interrupt.h>
+#include <linux/phy.h>
+#include <linux/ratelimit.h>
+#include <net/dst.h>
+
+#include "octeon-ethernet.h"
+#include "ethernet-defines.h"
+#include "ethernet-util.h"
+#include "ethernet-mdio.h"
+
+static DEFINE_SPINLOCK(global_register_lock);
+
+static void cvm_oct_set_hw_preamble(struct octeon_ethernet *priv, bool enable)
+{
+ union cvmx_gmxx_rxx_frm_ctl gmxx_rxx_frm_ctl;
+ union cvmx_ipd_sub_port_fcs ipd_sub_port_fcs;
+ union cvmx_gmxx_rxx_int_reg gmxx_rxx_int_reg;
+ int interface = INTERFACE(priv->port);
+ int index = INDEX(priv->port);
+
+ /* Set preamble checking. */
+ gmxx_rxx_frm_ctl.u64 = cvmx_read_csr(CVMX_GMXX_RXX_FRM_CTL(index,
+ interface));
+ gmxx_rxx_frm_ctl.s.pre_chk = enable;
+ cvmx_write_csr(CVMX_GMXX_RXX_FRM_CTL(index, interface),
+ gmxx_rxx_frm_ctl.u64);
+
+ /* Set FCS stripping. */
+ ipd_sub_port_fcs.u64 = cvmx_read_csr(CVMX_IPD_SUB_PORT_FCS);
+ if (enable)
+ ipd_sub_port_fcs.s.port_bit |= 1ull << priv->port;
+ else
+ ipd_sub_port_fcs.s.port_bit &=
+ 0xffffffffull ^ (1ull << priv->port);
+ cvmx_write_csr(CVMX_IPD_SUB_PORT_FCS, ipd_sub_port_fcs.u64);
+
+ /* Clear any error bits. */
+ gmxx_rxx_int_reg.u64 = cvmx_read_csr(CVMX_GMXX_RXX_INT_REG(index,
+ interface));
+ cvmx_write_csr(CVMX_GMXX_RXX_INT_REG(index, interface),
+ gmxx_rxx_int_reg.u64);
+}
+
+static void cvm_oct_check_preamble_errors(struct net_device *dev)
+{
+ struct octeon_ethernet *priv = netdev_priv(dev);
+ union cvmx_helper_link_info link_info;
+ unsigned long flags;
+
+ link_info.u64 = priv->link_info;
+
+ /*
+ * Take the global register lock since we are going to
+ * touch registers that affect more than one port.
+ */
+ spin_lock_irqsave(&global_register_lock, flags);
+
+ if (link_info.s.speed == 10 && priv->last_speed == 10) {
+ /*
+ * Read the GMXX_RXX_INT_REG[PCTERR] bit and see if we are
+ * getting preamble errors.
+ */
+ int interface = INTERFACE(priv->port);
+ int index = INDEX(priv->port);
+ union cvmx_gmxx_rxx_int_reg gmxx_rxx_int_reg;
+
+ gmxx_rxx_int_reg.u64 = cvmx_read_csr(CVMX_GMXX_RXX_INT_REG
+ (index, interface));
+ if (gmxx_rxx_int_reg.s.pcterr) {
+ /*
+ * We are getting preamble errors at 10Mbps. Most
+ * likely the PHY is giving us packets with misaligned
+ * preambles. In order to get these packets we need to
+ * disable preamble checking and do it in software.
+ */
+ cvm_oct_set_hw_preamble(priv, false);
+ printk_ratelimited("%s: Using 10Mbps with software preamble removal\n",
+ dev->name);
+ }
+ } else {
+ /*
+ * Since the 10Mbps preamble workaround is allowed we need to
+ * enable preamble checking, FCS stripping, and clear error
+ * bits on every speed change. If errors occur during 10Mbps
+ * operation the above code will change this stuff
+ */
+ if (priv->last_speed != link_info.s.speed)
+ cvm_oct_set_hw_preamble(priv, true);
+ priv->last_speed = link_info.s.speed;
+ }
+ spin_unlock_irqrestore(&global_register_lock, flags);
+}
+
+static void cvm_oct_rgmii_poll(struct net_device *dev)
+{
+ struct octeon_ethernet *priv = netdev_priv(dev);
+ union cvmx_helper_link_info link_info;
+ bool status_change;
+
+ link_info = cvmx_helper_link_get(priv->port);
+ if (priv->link_info != link_info.u64 &&
+ cvmx_helper_link_set(priv->port, link_info))
+ link_info.u64 = priv->link_info;
+ status_change = priv->link_info != link_info.u64;
+ priv->link_info = link_info.u64;
+
+ cvm_oct_check_preamble_errors(dev);
+
+ if (likely(!status_change))
+ return;
+
+ /* Tell core. */
+ if (link_info.s.link_up) {
+ if (!netif_carrier_ok(dev))
+ netif_carrier_on(dev);
+ } else if (netif_carrier_ok(dev)) {
+ netif_carrier_off(dev);
+ }
+ cvm_oct_note_carrier(priv, link_info);
+}
+
+int cvm_oct_rgmii_open(struct net_device *dev)
+{
+ struct octeon_ethernet *priv = netdev_priv(dev);
+ int ret;
+
+ ret = cvm_oct_common_open(dev, cvm_oct_rgmii_poll);
+ if (ret)
+ return ret;
+
+ if (dev->phydev) {
+ /*
+ * In phydev mode, we need still periodic polling for the
+ * preamble error checking, and we also need to call this
+ * function on every link state change.
+ *
+ * Only true RGMII ports need to be polled. In GMII mode, port
+ * 0 is really a RGMII port.
+ */
+ if ((priv->imode == CVMX_HELPER_INTERFACE_MODE_GMII &&
+ priv->port == 0) ||
+ (priv->imode == CVMX_HELPER_INTERFACE_MODE_RGMII)) {
+ priv->poll = cvm_oct_check_preamble_errors;
+ cvm_oct_check_preamble_errors(dev);
+ }
+ }
+
+ return 0;
+}
diff --git a/drivers/staging/octeon/ethernet-rx.c b/drivers/staging/octeon/ethernet-rx.c
new file mode 100644
index 000000000000..2c16230f993c
--- /dev/null
+++ b/drivers/staging/octeon/ethernet-rx.c
@@ -0,0 +1,538 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * This file is based on code from OCTEON SDK by Cavium Networks.
+ *
+ * Copyright (c) 2003-2010 Cavium Networks
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/cache.h>
+#include <linux/cpumask.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/ip.h>
+#include <linux/string.h>
+#include <linux/prefetch.h>
+#include <linux/ratelimit.h>
+#include <linux/smp.h>
+#include <linux/interrupt.h>
+#include <net/dst.h>
+#ifdef CONFIG_XFRM
+#include <linux/xfrm.h>
+#include <net/xfrm.h>
+#endif /* CONFIG_XFRM */
+
+#include "octeon-ethernet.h"
+#include "ethernet-defines.h"
+#include "ethernet-mem.h"
+#include "ethernet-rx.h"
+#include "ethernet-util.h"
+
+static atomic_t oct_rx_ready = ATOMIC_INIT(0);
+
+static struct oct_rx_group {
+ int irq;
+ int group;
+ struct napi_struct napi;
+} oct_rx_group[16];
+
+/**
+ * cvm_oct_do_interrupt - interrupt handler.
+ * @irq: Interrupt number.
+ * @napi_id: Cookie to identify the NAPI instance.
+ *
+ * The interrupt occurs whenever the POW has packets in our group.
+ *
+ */
+static irqreturn_t cvm_oct_do_interrupt(int irq, void *napi_id)
+{
+ /* Disable the IRQ and start napi_poll. */
+ disable_irq_nosync(irq);
+ napi_schedule(napi_id);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * cvm_oct_check_rcv_error - process receive errors
+ * @work: Work queue entry pointing to the packet.
+ *
+ * Returns Non-zero if the packet can be dropped, zero otherwise.
+ */
+static inline int cvm_oct_check_rcv_error(struct cvmx_wqe *work)
+{
+ int port;
+
+ if (octeon_has_feature(OCTEON_FEATURE_PKND))
+ port = work->word0.pip.cn68xx.pknd;
+ else
+ port = work->word1.cn38xx.ipprt;
+
+ if ((work->word2.snoip.err_code == 10) && (work->word1.len <= 64)) {
+ /*
+ * Ignore length errors on min size packets. Some
+ * equipment incorrectly pads packets to 64+4FCS
+ * instead of 60+4FCS. Note these packets still get
+ * counted as frame errors.
+ */
+ } else if (work->word2.snoip.err_code == 5 ||
+ work->word2.snoip.err_code == 7) {
+ /*
+ * We received a packet with either an alignment error
+ * or a FCS error. This may be signalling that we are
+ * running 10Mbps with GMXX_RXX_FRM_CTL[PRE_CHK]
+ * off. If this is the case we need to parse the
+ * packet to determine if we can remove a non spec
+ * preamble and generate a correct packet.
+ */
+ int interface = cvmx_helper_get_interface_num(port);
+ int index = cvmx_helper_get_interface_index_num(port);
+ union cvmx_gmxx_rxx_frm_ctl gmxx_rxx_frm_ctl;
+
+ gmxx_rxx_frm_ctl.u64 =
+ cvmx_read_csr(CVMX_GMXX_RXX_FRM_CTL(index, interface));
+ if (gmxx_rxx_frm_ctl.s.pre_chk == 0) {
+ u8 *ptr =
+ cvmx_phys_to_ptr(work->packet_ptr.s.addr);
+ int i = 0;
+
+ while (i < work->word1.len - 1) {
+ if (*ptr != 0x55)
+ break;
+ ptr++;
+ i++;
+ }
+
+ if (*ptr == 0xd5) {
+ /* Port received 0xd5 preamble */
+ work->packet_ptr.s.addr += i + 1;
+ work->word1.len -= i + 5;
+ } else if ((*ptr & 0xf) == 0xd) {
+ /* Port received 0xd preamble */
+ work->packet_ptr.s.addr += i;
+ work->word1.len -= i + 4;
+ for (i = 0; i < work->word1.len; i++) {
+ *ptr =
+ ((*ptr & 0xf0) >> 4) |
+ ((*(ptr + 1) & 0xf) << 4);
+ ptr++;
+ }
+ } else {
+ printk_ratelimited("Port %d unknown preamble, packet dropped\n",
+ port);
+ cvm_oct_free_work(work);
+ return 1;
+ }
+ }
+ } else {
+ printk_ratelimited("Port %d receive error code %d, packet dropped\n",
+ port, work->word2.snoip.err_code);
+ cvm_oct_free_work(work);
+ return 1;
+ }
+
+ return 0;
+}
+
+static void copy_segments_to_skb(struct cvmx_wqe *work, struct sk_buff *skb)
+{
+ int segments = work->word2.s.bufs;
+ union cvmx_buf_ptr segment_ptr = work->packet_ptr;
+ int len = work->word1.len;
+ int segment_size;
+
+ while (segments--) {
+ union cvmx_buf_ptr next_ptr;
+
+ next_ptr = *(union cvmx_buf_ptr *)
+ cvmx_phys_to_ptr(segment_ptr.s.addr - 8);
+
+ /*
+ * Octeon Errata PKI-100: The segment size is wrong.
+ *
+ * Until it is fixed, calculate the segment size based on
+ * the packet pool buffer size.
+ * When it is fixed, the following line should be replaced
+ * with this one:
+ * int segment_size = segment_ptr.s.size;
+ */
+ segment_size =
+ CVMX_FPA_PACKET_POOL_SIZE -
+ (segment_ptr.s.addr -
+ (((segment_ptr.s.addr >> 7) -
+ segment_ptr.s.back) << 7));
+
+ /* Don't copy more than what is left in the packet */
+ if (segment_size > len)
+ segment_size = len;
+
+ /* Copy the data into the packet */
+ skb_put_data(skb, cvmx_phys_to_ptr(segment_ptr.s.addr),
+ segment_size);
+ len -= segment_size;
+ segment_ptr = next_ptr;
+ }
+}
+
+static int cvm_oct_poll(struct oct_rx_group *rx_group, int budget)
+{
+ const int coreid = cvmx_get_core_num();
+ u64 old_group_mask;
+ u64 old_scratch;
+ int rx_count = 0;
+ int did_work_request = 0;
+ int packet_not_copied;
+
+ /* Prefetch cvm_oct_device since we know we need it soon */
+ prefetch(cvm_oct_device);
+
+ if (USE_ASYNC_IOBDMA) {
+ /* Save scratch in case userspace is using it */
+ CVMX_SYNCIOBDMA;
+ old_scratch = cvmx_scratch_read64(CVMX_SCR_SCRATCH);
+ }
+
+ /* Only allow work for our group (and preserve priorities) */
+ if (OCTEON_IS_MODEL(OCTEON_CN68XX)) {
+ old_group_mask = cvmx_read_csr(CVMX_SSO_PPX_GRP_MSK(coreid));
+ cvmx_write_csr(CVMX_SSO_PPX_GRP_MSK(coreid),
+ BIT(rx_group->group));
+ cvmx_read_csr(CVMX_SSO_PPX_GRP_MSK(coreid)); /* Flush */
+ } else {
+ old_group_mask = cvmx_read_csr(CVMX_POW_PP_GRP_MSKX(coreid));
+ cvmx_write_csr(CVMX_POW_PP_GRP_MSKX(coreid),
+ (old_group_mask & ~0xFFFFull) |
+ BIT(rx_group->group));
+ }
+
+ if (USE_ASYNC_IOBDMA) {
+ cvmx_pow_work_request_async(CVMX_SCR_SCRATCH, CVMX_POW_NO_WAIT);
+ did_work_request = 1;
+ }
+
+ while (rx_count < budget) {
+ struct sk_buff *skb = NULL;
+ struct sk_buff **pskb = NULL;
+ int skb_in_hw;
+ struct cvmx_wqe *work;
+ int port;
+
+ if (USE_ASYNC_IOBDMA && did_work_request)
+ work = cvmx_pow_work_response_async(CVMX_SCR_SCRATCH);
+ else
+ work = cvmx_pow_work_request_sync(CVMX_POW_NO_WAIT);
+
+ prefetch(work);
+ did_work_request = 0;
+ if (!work) {
+ if (OCTEON_IS_MODEL(OCTEON_CN68XX)) {
+ cvmx_write_csr(CVMX_SSO_WQ_IQ_DIS,
+ BIT(rx_group->group));
+ cvmx_write_csr(CVMX_SSO_WQ_INT,
+ BIT(rx_group->group));
+ } else {
+ union cvmx_pow_wq_int wq_int;
+
+ wq_int.u64 = 0;
+ wq_int.s.iq_dis = BIT(rx_group->group);
+ wq_int.s.wq_int = BIT(rx_group->group);
+ cvmx_write_csr(CVMX_POW_WQ_INT, wq_int.u64);
+ }
+ break;
+ }
+ pskb = (struct sk_buff **)
+ (cvm_oct_get_buffer_ptr(work->packet_ptr) -
+ sizeof(void *));
+ prefetch(pskb);
+
+ if (USE_ASYNC_IOBDMA && rx_count < (budget - 1)) {
+ cvmx_pow_work_request_async_nocheck(CVMX_SCR_SCRATCH,
+ CVMX_POW_NO_WAIT);
+ did_work_request = 1;
+ }
+ rx_count++;
+
+ skb_in_hw = work->word2.s.bufs == 1;
+ if (likely(skb_in_hw)) {
+ skb = *pskb;
+ prefetch(&skb->head);
+ prefetch(&skb->len);
+ }
+
+ if (octeon_has_feature(OCTEON_FEATURE_PKND))
+ port = work->word0.pip.cn68xx.pknd;
+ else
+ port = work->word1.cn38xx.ipprt;
+
+ prefetch(cvm_oct_device[port]);
+
+ /* Immediately throw away a