2019-05-19 20:08:20 +08:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
2009-04-25 20:53:39 +08:00
|
|
|
/*
|
|
|
|
* Driver for Xilinx TEMAC Ethernet device
|
|
|
|
*
|
|
|
|
* Copyright (c) 2008 Nissin Systems Co., Ltd., Yoshio Kashiwagi
|
|
|
|
* Copyright (c) 2005-2008 DLA Systems, David H. Lynch Jr. <dhlii@dlasys.net>
|
|
|
|
* Copyright (c) 2008-2009 Secret Lab Technologies Ltd.
|
|
|
|
*
|
|
|
|
* This is a driver for the Xilinx ll_temac ipcore which is often used
|
|
|
|
* in the Virtex and Spartan series of chips.
|
|
|
|
*
|
|
|
|
* Notes:
|
|
|
|
* - The ll_temac hardware uses indirect access for many of the TEMAC
|
|
|
|
* registers, include the MDIO bus. However, indirect access to MDIO
|
|
|
|
* registers take considerably more clock cycles than to TEMAC registers.
|
|
|
|
* MDIO accesses are long, so threads doing them should probably sleep
|
|
|
|
* rather than busywait. However, since only one indirect access can be
|
|
|
|
* in progress at any given time, that means that *all* indirect accesses
|
|
|
|
* could end up sleeping (to wait for an MDIO access to complete).
|
|
|
|
* Fortunately none of the indirect accesses are on the 'hot' path for tx
|
|
|
|
* or rx, so this should be okay.
|
|
|
|
*
|
|
|
|
* TODO:
|
|
|
|
* - Factor out locallink DMA code into separate driver
|
|
|
|
* - Fix support for hardware checksumming.
|
|
|
|
* - Testing. Lots and lots of testing.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/delay.h>
|
|
|
|
#include <linux/etherdevice.h>
|
|
|
|
#include <linux/mii.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/mutex.h>
|
|
|
|
#include <linux/netdevice.h>
|
2019-04-30 15:17:49 +08:00
|
|
|
#include <linux/if_ether.h>
|
2009-04-25 20:53:39 +08:00
|
|
|
#include <linux/of.h>
|
|
|
|
#include <linux/of_device.h>
|
2013-09-08 03:05:10 +08:00
|
|
|
#include <linux/of_irq.h>
|
2009-04-25 20:53:39 +08:00
|
|
|
#include <linux/of_mdio.h>
|
2016-12-07 20:25:29 +08:00
|
|
|
#include <linux/of_net.h>
|
2009-04-25 20:53:39 +08:00
|
|
|
#include <linux/of_platform.h>
|
2010-09-01 22:55:23 +08:00
|
|
|
#include <linux/of_address.h>
|
2009-04-25 20:53:39 +08:00
|
|
|
#include <linux/skbuff.h>
|
|
|
|
#include <linux/spinlock.h>
|
|
|
|
#include <linux/tcp.h> /* needed for sizeof(tcphdr) */
|
|
|
|
#include <linux/udp.h> /* needed for sizeof(udphdr) */
|
|
|
|
#include <linux/phy.h>
|
|
|
|
#include <linux/in.h>
|
|
|
|
#include <linux/io.h>
|
|
|
|
#include <linux/ip.h>
|
include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h
percpu.h is included by sched.h and module.h and thus ends up being
included when building most .c files. percpu.h includes slab.h which
in turn includes gfp.h making everything defined by the two files
universally available and complicating inclusion dependencies.
percpu.h -> slab.h dependency is about to be removed. Prepare for
this change by updating users of gfp and slab facilities include those
headers directly instead of assuming availability. As this conversion
needs to touch large number of source files, the following script is
used as the basis of conversion.
http://userweb.kernel.org/~tj/misc/slabh-sweep.py
The script does the followings.
* Scan files for gfp and slab usages and update includes such that
only the necessary includes are there. ie. if only gfp is used,
gfp.h, if slab is used, slab.h.
* When the script inserts a new include, it looks at the include
blocks and try to put the new include such that its order conforms
to its surrounding. It's put in the include block which contains
core kernel includes, in the same order that the rest are ordered -
alphabetical, Christmas tree, rev-Xmas-tree or at the end if there
doesn't seem to be any matching order.
* If the script can't find a place to put a new include (mostly
because the file doesn't have fitting include block), it prints out
an error message indicating which .h file needs to be added to the
file.
The conversion was done in the following steps.
1. The initial automatic conversion of all .c files updated slightly
over 4000 files, deleting around 700 includes and adding ~480 gfp.h
and ~3000 slab.h inclusions. The script emitted errors for ~400
files.
2. Each error was manually checked. Some didn't need the inclusion,
some needed manual addition while adding it to implementation .h or
embedding .c file was more appropriate for others. This step added
inclusions to around 150 files.
3. The script was run again and the output was compared to the edits
from #2 to make sure no file was left behind.
4. Several build tests were done and a couple of problems were fixed.
e.g. lib/decompress_*.c used malloc/free() wrappers around slab
APIs requiring slab.h to be added manually.
5. The script was run on all .h files but without automatically
editing them as sprinkling gfp.h and slab.h inclusions around .h
files could easily lead to inclusion dependency hell. Most gfp.h
inclusion directives were ignored as stuff from gfp.h was usually
wildly available and often used in preprocessor macros. Each
slab.h inclusion directive was examined and added manually as
necessary.
6. percpu.h was updated not to include slab.h.
7. Build test were done on the following configurations and failures
were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my
distributed build env didn't work with gcov compiles) and a few
more options had to be turned off depending on archs to make things
build (like ipr on powerpc/64 which failed due to missing writeq).
* x86 and x86_64 UP and SMP allmodconfig and a custom test config.
* powerpc and powerpc64 SMP allmodconfig
* sparc and sparc64 SMP allmodconfig
* ia64 SMP allmodconfig
* s390 SMP allmodconfig
* alpha SMP allmodconfig
* um on x86_64 SMP allmodconfig
8. percpu.h modifications were reverted so that it could be applied as
a separate patch and serve as bisection point.
Given the fact that I had only a couple of failures from tests on step
6, I'm fairly confident about the coverage of this conversion patch.
If there is a breakage, it's likely to be something in one of the arch
headers which should be easily discoverable easily on most builds of
the specific arch.
Signed-off-by: Tejun Heo <tj@kernel.org>
Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 16:04:11 +08:00
|
|
|
#include <linux/slab.h>
|
2011-06-08 13:49:33 +08:00
|
|
|
#include <linux/interrupt.h>
|
2020-02-21 14:47:58 +08:00
|
|
|
#include <linux/workqueue.h>
|
2011-06-29 17:55:59 +08:00
|
|
|
#include <linux/dma-mapping.h>
|
2019-05-23 20:02:20 +08:00
|
|
|
#include <linux/processor.h>
|
2019-04-30 15:17:49 +08:00
|
|
|
#include <linux/platform_data/xilinx-ll-temac.h>
|
2009-04-25 20:53:39 +08:00
|
|
|
|
|
|
|
#include "ll_temac.h"
|
|
|
|
|
2020-02-28 15:57:26 +08:00
|
|
|
/* Descriptors defines for Tx and Rx DMA */
|
|
|
|
#define TX_BD_NUM_DEFAULT 64
|
|
|
|
#define RX_BD_NUM_DEFAULT 1024
|
|
|
|
#define TX_BD_NUM_MAX 4096
|
|
|
|
#define RX_BD_NUM_MAX 4096
|
2009-04-25 20:53:39 +08:00
|
|
|
|
|
|
|
/* ---------------------------------------------------------------------
|
|
|
|
* Low level register access functions
|
|
|
|
*/
|
|
|
|
|
2019-05-04 18:10:30 +08:00
|
|
|
static u32 _temac_ior_be(struct temac_local *lp, int offset)
|
2009-04-25 20:53:39 +08:00
|
|
|
{
|
2019-04-30 15:17:51 +08:00
|
|
|
return ioread32be(lp->regs + offset);
|
2009-04-25 20:53:39 +08:00
|
|
|
}
|
|
|
|
|
2019-05-04 18:10:30 +08:00
|
|
|
static void _temac_iow_be(struct temac_local *lp, int offset, u32 value)
|
2009-04-25 20:53:39 +08:00
|
|
|
{
|
2019-04-30 15:17:51 +08:00
|
|
|
return iowrite32be(value, lp->regs + offset);
|
|
|
|
}
|
|
|
|
|
2019-05-04 18:10:30 +08:00
|
|
|
static u32 _temac_ior_le(struct temac_local *lp, int offset)
|
2019-04-30 15:17:51 +08:00
|
|
|
{
|
|
|
|
return ioread32(lp->regs + offset);
|
|
|
|
}
|
|
|
|
|
2019-05-04 18:10:30 +08:00
|
|
|
static void _temac_iow_le(struct temac_local *lp, int offset, u32 value)
|
2019-04-30 15:17:51 +08:00
|
|
|
{
|
|
|
|
return iowrite32(value, lp->regs + offset);
|
2009-04-25 20:53:39 +08:00
|
|
|
}
|
|
|
|
|
2019-05-23 20:02:20 +08:00
|
|
|
static bool hard_acs_rdy(struct temac_local *lp)
|
|
|
|
{
|
|
|
|
return temac_ior(lp, XTE_RDY0_OFFSET) & XTE_RDY0_HARD_ACS_RDY_MASK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool hard_acs_rdy_or_timeout(struct temac_local *lp, ktime_t timeout)
|
|
|
|
{
|
|
|
|
ktime_t cur = ktime_get();
|
|
|
|
|
|
|
|
return hard_acs_rdy(lp) || ktime_after(cur, timeout);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Poll for maximum 20 ms. This is similar to the 2 jiffies @ 100 Hz
|
|
|
|
* that was used before, and should cover MDIO bus speed down to 3200
|
|
|
|
* Hz.
|
|
|
|
*/
|
|
|
|
#define HARD_ACS_RDY_POLL_NS (20 * NSEC_PER_MSEC)
|
|
|
|
|
|
|
|
/**
|
|
|
|
* temac_indirect_busywait - Wait for current indirect register access
|
|
|
|
* to complete.
|
|
|
|
*/
|
2009-04-25 20:53:39 +08:00
|
|
|
int temac_indirect_busywait(struct temac_local *lp)
|
|
|
|
{
|
2019-05-23 20:02:20 +08:00
|
|
|
ktime_t timeout = ktime_add_ns(ktime_get(), HARD_ACS_RDY_POLL_NS);
|
2009-04-25 20:53:39 +08:00
|
|
|
|
2019-05-23 20:02:20 +08:00
|
|
|
spin_until_cond(hard_acs_rdy_or_timeout(lp, timeout));
|
|
|
|
if (WARN_ON(!hard_acs_rdy(lp)))
|
|
|
|
return -ETIMEDOUT;
|
|
|
|
else
|
|
|
|
return 0;
|
2009-04-25 20:53:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-05-23 20:02:20 +08:00
|
|
|
* temac_indirect_in32 - Indirect register read access. This function
|
|
|
|
* must be called without lp->indirect_lock being held.
|
2009-04-25 20:53:39 +08:00
|
|
|
*/
|
|
|
|
u32 temac_indirect_in32(struct temac_local *lp, int reg)
|
|
|
|
{
|
2019-05-23 20:02:20 +08:00
|
|
|
unsigned long flags;
|
|
|
|
int val;
|
2009-04-25 20:53:39 +08:00
|
|
|
|
2019-05-23 20:02:20 +08:00
|
|
|
spin_lock_irqsave(lp->indirect_lock, flags);
|
|
|
|
val = temac_indirect_in32_locked(lp, reg);
|
|
|
|
spin_unlock_irqrestore(lp->indirect_lock, flags);
|
|
|
|
return val;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* temac_indirect_in32_locked - Indirect register read access. This
|
|
|
|
* function must be called with lp->indirect_lock being held. Use
|
|
|
|
* this together with spin_lock_irqsave/spin_lock_irqrestore to avoid
|
|
|
|
* repeated lock/unlock and to ensure uninterrupted access to indirect
|
|
|
|
* registers.
|
|
|
|
*/
|
|
|
|
u32 temac_indirect_in32_locked(struct temac_local *lp, int reg)
|
|
|
|
{
|
|
|
|
/* This initial wait should normally not spin, as we always
|
|
|
|
* try to wait for indirect access to complete before
|
|
|
|
* releasing the indirect_lock.
|
|
|
|
*/
|
|
|
|
if (WARN_ON(temac_indirect_busywait(lp)))
|
2009-04-25 20:53:39 +08:00
|
|
|
return -ETIMEDOUT;
|
2019-05-23 20:02:20 +08:00
|
|
|
/* Initiate read from indirect register */
|
2009-04-25 20:53:39 +08:00
|
|
|
temac_iow(lp, XTE_CTL0_OFFSET, reg);
|
2019-05-23 20:02:20 +08:00
|
|
|
/* Wait for indirect register access to complete. We really
|
|
|
|
* should not see timeouts, and could even end up causing
|
|
|
|
* problem for following indirect access, so let's make a bit
|
|
|
|
* of WARN noise.
|
|
|
|
*/
|
|
|
|
if (WARN_ON(temac_indirect_busywait(lp)))
|
2009-04-25 20:53:39 +08:00
|
|
|
return -ETIMEDOUT;
|
2019-05-23 20:02:20 +08:00
|
|
|
/* Value is ready now */
|
|
|
|
return temac_ior(lp, XTE_LSW0_OFFSET);
|
2009-04-25 20:53:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-05-23 20:02:20 +08:00
|
|
|
* temac_indirect_out32 - Indirect register write access. This function
|
|
|
|
* must be called without lp->indirect_lock being held.
|
2009-04-25 20:53:39 +08:00
|
|
|
*/
|
|
|
|
void temac_indirect_out32(struct temac_local *lp, int reg, u32 value)
|
|
|
|
{
|
2019-05-23 20:02:20 +08:00
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
spin_lock_irqsave(lp->indirect_lock, flags);
|
|
|
|
temac_indirect_out32_locked(lp, reg, value);
|
|
|
|
spin_unlock_irqrestore(lp->indirect_lock, flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* temac_indirect_out32_locked - Indirect register write access. This
|
|
|
|
* function must be called with lp->indirect_lock being held. Use
|
|
|
|
* this together with spin_lock_irqsave/spin_lock_irqrestore to avoid
|
|
|
|
* repeated lock/unlock and to ensure uninterrupted access to indirect
|
|
|
|
* registers.
|
|
|
|
*/
|
|
|
|
void temac_indirect_out32_locked(struct temac_local *lp, int reg, u32 value)
|
|
|
|
{
|
|
|
|
/* As in temac_indirect_in32_locked(), we should normally not
|
|
|
|
* spin here. And if it happens, we actually end up silently
|
|
|
|
* ignoring the write request. Ouch.
|
|
|
|
*/
|
|
|
|
if (WARN_ON(temac_indirect_busywait(lp)))
|
2009-04-25 20:53:39 +08:00
|
|
|
return;
|
2019-05-23 20:02:20 +08:00
|
|
|
/* Initiate write to indirect register */
|
2009-04-25 20:53:39 +08:00
|
|
|
temac_iow(lp, XTE_LSW0_OFFSET, value);
|
|
|
|
temac_iow(lp, XTE_CTL0_OFFSET, CNTLREG_WRITE_ENABLE_MASK | reg);
|
2019-05-23 20:02:20 +08:00
|
|
|
/* As in temac_indirect_in32_locked(), we should not see timeouts
|
|
|
|
* here. And if it happens, we continue before the write has
|
|
|
|
* completed. Not good.
|
|
|
|
*/
|
|
|
|
WARN_ON(temac_indirect_busywait(lp));
|
2009-04-25 20:53:39 +08:00
|
|
|
}
|
|
|
|
|
2010-04-08 15:08:02 +08:00
|
|
|
/**
|
2019-04-30 15:17:51 +08:00
|
|
|
* temac_dma_in32_* - Memory mapped DMA read, these function expects a
|
|
|
|
* register input that is based on DCR word addresses which are then
|
|
|
|
* converted to memory mapped byte addresses. To be assigned to
|
|
|
|
* lp->dma_in32.
|
2010-04-08 15:08:02 +08:00
|
|
|
*/
|
2019-04-30 15:17:51 +08:00
|
|
|
static u32 temac_dma_in32_be(struct temac_local *lp, int reg)
|
2009-04-25 20:53:39 +08:00
|
|
|
{
|
2019-04-30 15:17:51 +08:00
|
|
|
return ioread32be(lp->sdma_regs + (reg << 2));
|
|
|
|
}
|
|
|
|
|
|
|
|
static u32 temac_dma_in32_le(struct temac_local *lp, int reg)
|
|
|
|
{
|
|
|
|
return ioread32(lp->sdma_regs + (reg << 2));
|
2009-04-25 20:53:39 +08:00
|
|
|
}
|
|
|
|
|
2010-04-08 15:08:02 +08:00
|
|
|
/**
|
2019-04-30 15:17:51 +08:00
|
|
|
* temac_dma_out32_* - Memory mapped DMA read, these function expects
|
|
|
|
* a register input that is based on DCR word addresses which are then
|
|
|
|
* converted to memory mapped byte addresses. To be assigned to
|
|
|
|
* lp->dma_out32.
|
2010-04-08 15:08:02 +08:00
|
|
|
*/
|
2019-04-30 15:17:51 +08:00
|
|
|
static void temac_dma_out32_be(struct temac_local *lp, int reg, u32 value)
|
|
|
|
{
|
|
|
|
iowrite32be(value, lp->sdma_regs + (reg << 2));
|
|
|
|
}
|
|
|
|
|
|
|
|
static void temac_dma_out32_le(struct temac_local *lp, int reg, u32 value)
|
2010-04-08 15:08:02 +08:00
|
|
|
{
|
2019-04-30 15:17:51 +08:00
|
|
|
iowrite32(value, lp->sdma_regs + (reg << 2));
|
2010-04-08 15:08:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* DMA register access functions can be DCR based or memory mapped.
|
|
|
|
* The PowerPC 440 is DCR based, the PowerPC 405 and MicroBlaze are both
|
|
|
|
* memory mapped.
|
|
|
|
*/
|
|
|
|
#ifdef CONFIG_PPC_DCR
|
|
|
|
|
|
|
|
/**
|
|
|
|
* temac_dma_dcr_in32 - DCR based DMA read
|
|
|
|
*/
|
|
|
|
static u32 temac_dma_dcr_in(struct temac_local *lp, int reg)
|
|
|
|
{
|
|
|
|
return dcr_read(lp->sdma_dcrs, reg);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* temac_dma_dcr_out32 - DCR based DMA write
|
|
|
|
*/
|
|
|
|
static void temac_dma_dcr_out(struct temac_local *lp, int reg, u32 value)
|
2009-04-25 20:53:39 +08:00
|
|
|
{
|
|
|
|
dcr_write(lp->sdma_dcrs, reg, value);
|
|
|
|
}
|
|
|
|
|
2010-04-08 15:08:02 +08:00
|
|
|
/**
|
|
|
|
* temac_dcr_setup - If the DMA is DCR based, then setup the address and
|
|
|
|
* I/O functions
|
|
|
|
*/
|
2010-08-06 23:25:50 +08:00
|
|
|
static int temac_dcr_setup(struct temac_local *lp, struct platform_device *op,
|
2010-04-08 15:08:02 +08:00
|
|
|
struct device_node *np)
|
|
|
|
{
|
|
|
|
unsigned int dcrs;
|
|
|
|
|
|
|
|
/* setup the dcr address mapping if it's in the device tree */
|
|
|
|
|
|
|
|
dcrs = dcr_resource_start(np, 0);
|
|
|
|
if (dcrs != 0) {
|
|
|
|
lp->sdma_dcrs = dcr_map(np, dcrs, dcr_resource_len(np, 0));
|
|
|
|
lp->dma_in = temac_dma_dcr_in;
|
|
|
|
lp->dma_out = temac_dma_dcr_out;
|
|
|
|
dev_dbg(&op->dev, "DCR base: %x\n", dcrs);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
/* no DCR in the device tree, indicate a failure */
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
|
|
|
/*
|
|
|
|
* temac_dcr_setup - This is a stub for when DCR is not supported,
|
2019-04-30 15:17:49 +08:00
|
|
|
* such as with MicroBlaze and x86
|
2010-04-08 15:08:02 +08:00
|
|
|
*/
|
2010-08-06 23:25:50 +08:00
|
|
|
static int temac_dcr_setup(struct temac_local *lp, struct platform_device *op,
|
2010-04-08 15:08:02 +08:00
|
|
|
struct device_node *np)
|
|
|
|
{
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
2010-07-08 18:24:51 +08:00
|
|
|
/**
|
2012-07-10 18:56:00 +08:00
|
|
|
* temac_dma_bd_release - Release buffer descriptor rings
|
2010-07-08 18:24:51 +08:00
|
|
|
*/
|
|
|
|
static void temac_dma_bd_release(struct net_device *ndev)
|
|
|
|
{
|
|
|
|
struct temac_local *lp = netdev_priv(ndev);
|
|
|
|
int i;
|
|
|
|
|
2011-11-08 07:31:58 +08:00
|
|
|
/* Reset Local Link (DMA) */
|
|
|
|
lp->dma_out(lp, DMA_CONTROL_REG, DMA_CONTROL_RST);
|
|
|
|
|
2020-02-28 15:57:26 +08:00
|
|
|
for (i = 0; i < lp->rx_bd_num; i++) {
|
2010-07-08 18:24:51 +08:00
|
|
|
if (!lp->rx_skb[i])
|
|
|
|
break;
|
|
|
|
else {
|
|
|
|
dma_unmap_single(ndev->dev.parent, lp->rx_bd_v[i].phys,
|
|
|
|
XTE_MAX_JUMBO_FRAME_SIZE, DMA_FROM_DEVICE);
|
|
|
|
dev_kfree_skb(lp->rx_skb[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (lp->rx_bd_v)
|
|
|
|
dma_free_coherent(ndev->dev.parent,
|
2020-02-28 15:57:26 +08:00
|
|
|
sizeof(*lp->rx_bd_v) * lp->rx_bd_num,
|
|
|
|
lp->rx_bd_v, lp->rx_bd_p);
|
2010-07-08 18:24:51 +08:00
|
|
|
if (lp->tx_bd_v)
|
|
|
|
dma_free_coherent(ndev->dev.parent,
|
2020-02-28 15:57:26 +08:00
|
|
|
sizeof(*lp->tx_bd_v) * lp->tx_bd_num,
|
|
|
|
lp->tx_bd_v, lp->tx_bd_p);
|
2010-07-08 18:24:51 +08:00
|
|
|
}
|
|
|
|
|
2009-04-25 20:53:39 +08:00
|
|
|
/**
|
|
|
|
* temac_dma_bd_init - Setup buffer descriptor rings
|
|
|
|
*/
|
|
|
|
static int temac_dma_bd_init(struct net_device *ndev)
|
|
|
|
{
|
|
|
|
struct temac_local *lp = netdev_priv(ndev);
|
|
|
|
struct sk_buff *skb;
|
2019-04-30 15:17:52 +08:00
|
|
|
dma_addr_t skb_dma_addr;
|
2009-04-25 20:53:39 +08:00
|
|
|
int i;
|
|
|
|
|
2020-02-28 15:57:26 +08:00
|
|
|
lp->rx_skb = devm_kcalloc(&ndev->dev, lp->rx_bd_num,
|
|
|
|
sizeof(*lp->rx_skb), GFP_KERNEL);
|
2013-02-04 01:43:58 +08:00
|
|
|
if (!lp->rx_skb)
|
2010-07-01 07:39:05 +08:00
|
|
|
goto out;
|
2013-02-04 01:43:58 +08:00
|
|
|
|
2009-04-25 20:53:39 +08:00
|
|
|
/* allocate the tx and rx ring buffer descriptors. */
|
tree-wide: fix comment/printk typos
"gadget", "through", "command", "maintain", "maintain", "controller", "address",
"between", "initiali[zs]e", "instead", "function", "select", "already",
"equal", "access", "management", "hierarchy", "registration", "interest",
"relative", "memory", "offset", "already",
Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
2010-11-02 03:38:34 +08:00
|
|
|
/* returns a virtual address and a physical address. */
|
cross-tree: phase out dma_zalloc_coherent()
We already need to zero out memory for dma_alloc_coherent(), as such
using dma_zalloc_coherent() is superflous. Phase it out.
This change was generated with the following Coccinelle SmPL patch:
@ replace_dma_zalloc_coherent @
expression dev, size, data, handle, flags;
@@
-dma_zalloc_coherent(dev, size, handle, flags)
+dma_alloc_coherent(dev, size, handle, flags)
Suggested-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Luis Chamberlain <mcgrof@kernel.org>
[hch: re-ran the script on the latest tree]
Signed-off-by: Christoph Hellwig <hch@lst.de>
2019-01-04 16:23:09 +08:00
|
|
|
lp->tx_bd_v = dma_alloc_coherent(ndev->dev.parent,
|
2020-02-28 15:57:26 +08:00
|
|
|
sizeof(*lp->tx_bd_v) * lp->tx_bd_num,
|
cross-tree: phase out dma_zalloc_coherent()
We already need to zero out memory for dma_alloc_coherent(), as such
using dma_zalloc_coherent() is superflous. Phase it out.
This change was generated with the following Coccinelle SmPL patch:
@ replace_dma_zalloc_coherent @
expression dev, size, data, handle, flags;
@@
-dma_zalloc_coherent(dev, size, handle, flags)
+dma_alloc_coherent(dev, size, handle, flags)
Suggested-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Luis Chamberlain <mcgrof@kernel.org>
[hch: re-ran the script on the latest tree]
Signed-off-by: Christoph Hellwig <hch@lst.de>
2019-01-04 16:23:09 +08:00
|
|
|
&lp->tx_bd_p, GFP_KERNEL);
|
2013-03-14 21:07:21 +08:00
|
|
|
if (!lp->tx_bd_v)
|
2010-07-01 07:39:05 +08:00
|
|
|
goto out;
|
2013-03-14 21:07:21 +08:00
|
|
|
|
cross-tree: phase out dma_zalloc_coherent()
We already need to zero out memory for dma_alloc_coherent(), as such
using dma_zalloc_coherent() is superflous. Phase it out.
This change was generated with the following Coccinelle SmPL patch:
@ replace_dma_zalloc_coherent @
expression dev, size, data, handle, flags;
@@
-dma_zalloc_coherent(dev, size, handle, flags)
+dma_alloc_coherent(dev, size, handle, flags)
Suggested-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Luis Chamberlain <mcgrof@kernel.org>
[hch: re-ran the script on the latest tree]
Signed-off-by: Christoph Hellwig <hch@lst.de>
2019-01-04 16:23:09 +08:00
|
|
|
lp->rx_bd_v = dma_alloc_coherent(ndev->dev.parent,
|
2020-02-28 15:57:26 +08:00
|
|
|
sizeof(*lp->rx_bd_v) * lp->rx_bd_num,
|
cross-tree: phase out dma_zalloc_coherent()
We already need to zero out memory for dma_alloc_coherent(), as such
using dma_zalloc_coherent() is superflous. Phase it out.
This change was generated with the following Coccinelle SmPL patch:
@ replace_dma_zalloc_coherent @
expression dev, size, data, handle, flags;
@@
-dma_zalloc_coherent(dev, size, handle, flags)
+dma_alloc_coherent(dev, size, handle, flags)
Suggested-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Luis Chamberlain <mcgrof@kernel.org>
[hch: re-ran the script on the latest tree]
Signed-off-by: Christoph Hellwig <hch@lst.de>
2019-01-04 16:23:09 +08:00
|
|
|
&lp->rx_bd_p, GFP_KERNEL);
|
2013-03-14 21:07:21 +08:00
|
|
|
if (!lp->rx_bd_v)
|
2010-07-01 07:39:05 +08:00
|
|
|
goto out;
|
2009-04-25 20:53:39 +08:00
|
|
|
|
2020-02-28 15:57:26 +08:00
|
|
|
for (i = 0; i < lp->tx_bd_num; i++) {
|
2019-04-30 15:17:52 +08:00
|
|
|
lp->tx_bd_v[i].next = cpu_to_be32(lp->tx_bd_p
|
2020-02-28 15:57:26 +08:00
|
|
|
+ sizeof(*lp->tx_bd_v) * ((i + 1) % lp->tx_bd_num));
|
2009-04-25 20:53:39 +08:00
|
|
|
}
|
|
|
|
|
2020-02-28 15:57:26 +08:00
|
|
|
for (i = 0; i < lp->rx_bd_num; i++) {
|
2019-04-30 15:17:52 +08:00
|
|
|
lp->rx_bd_v[i].next = cpu_to_be32(lp->rx_bd_p
|
2020-02-28 15:57:26 +08:00
|
|
|
+ sizeof(*lp->rx_bd_v) * ((i + 1) % lp->rx_bd_num));
|
2009-04-25 20:53:39 +08:00
|
|
|
|
2010-04-08 15:08:02 +08:00
|
|
|
skb = netdev_alloc_skb_ip_align(ndev,
|
|
|
|
XTE_MAX_JUMBO_FRAME_SIZE);
|
2013-03-08 23:03:25 +08:00
|
|
|
if (!skb)
|
2010-07-01 07:39:05 +08:00
|
|
|
goto out;
|
2013-03-08 23:03:25 +08:00
|
|
|
|
2009-04-25 20:53:39 +08:00
|
|
|
lp->rx_skb[i] = skb;
|
|
|
|
/* returns physical address of skb->data */
|
2019-04-30 15:17:52 +08:00
|
|
|
skb_dma_addr = dma_map_single(ndev->dev.parent, skb->data,
|
|
|
|
XTE_MAX_JUMBO_FRAME_SIZE,
|
|
|
|
DMA_FROM_DEVICE);
|
2020-02-21 14:47:33 +08:00
|
|
|
if (dma_mapping_error(ndev->dev.parent, skb_dma_addr))
|
|
|
|
goto out;
|
2019-04-30 15:17:52 +08:00
|
|
|
lp->rx_bd_v[i].phys = cpu_to_be32(skb_dma_addr);
|
|
|
|
lp->rx_bd_v[i].len = cpu_to_be32(XTE_MAX_JUMBO_FRAME_SIZE);
|
|
|
|
lp->rx_bd_v[i].app0 = cpu_to_be32(STS_CTRL_APP0_IRQONEND);
|
2009-04-25 20:53:39 +08:00
|
|
|
}
|
|
|
|
|
2019-04-30 15:17:58 +08:00
|
|
|
/* Configure DMA channel (irq setup) */
|
|
|
|
lp->dma_out(lp, TX_CHNL_CTRL, lp->tx_chnl_ctrl |
|
|
|
|
0x00000400 | // Use 1 Bit Wide Counters. Currently Not Used!
|
|
|
|
CHNL_CTRL_IRQ_EN | CHNL_CTRL_IRQ_ERR_EN |
|
|
|
|
CHNL_CTRL_IRQ_DLY_EN | CHNL_CTRL_IRQ_COAL_EN);
|
|
|
|
lp->dma_out(lp, RX_CHNL_CTRL, lp->rx_chnl_ctrl |
|
|
|
|
CHNL_CTRL_IRQ_IOE |
|
|
|
|
CHNL_CTRL_IRQ_EN | CHNL_CTRL_IRQ_ERR_EN |
|
|
|
|
CHNL_CTRL_IRQ_DLY_EN | CHNL_CTRL_IRQ_COAL_EN);
|
2009-04-25 20:53:39 +08:00
|
|
|
|
2013-10-01 14:17:10 +08:00
|
|
|
/* Init descriptor indexes */
|
|
|
|
lp->tx_bd_ci = 0;
|
|
|
|
lp->tx_bd_tail = 0;
|
|
|
|
lp->rx_bd_ci = 0;
|
2020-02-28 15:57:26 +08:00
|
|
|
lp->rx_bd_tail = lp->rx_bd_num - 1;
|
2013-10-01 14:17:10 +08:00
|
|
|
|
2019-04-30 15:17:59 +08:00
|
|
|
/* Enable RX DMA transfers */
|
|
|
|
wmb();
|
|
|
|
lp->dma_out(lp, RX_CURDESC_PTR, lp->rx_bd_p);
|
|
|
|
lp->dma_out(lp, RX_TAILDESC_PTR,
|
2020-02-21 14:47:45 +08:00
|
|
|
lp->rx_bd_p + (sizeof(*lp->rx_bd_v) * lp->rx_bd_tail));
|
2019-04-30 15:17:59 +08:00
|
|
|
|
|
|
|
/* Prepare for TX DMA transfer */
|
|
|
|
lp->dma_out(lp, TX_CURDESC_PTR, lp->tx_bd_p);
|
|
|
|
|
2009-04-25 20:53:39 +08:00
|
|
|
return 0;
|
2010-07-01 07:39:05 +08:00
|
|
|
|
|
|
|
out:
|
2010-07-08 18:24:51 +08:00
|
|
|
temac_dma_bd_release(ndev);
|
2010-07-01 07:39:05 +08:00
|
|
|
return -ENOMEM;
|
2009-04-25 20:53:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* ---------------------------------------------------------------------
|
|
|
|
* net_device_ops
|
|
|
|
*/
|
|
|
|
|
2013-01-01 11:30:19 +08:00
|
|
|
static void temac_do_set_mac_address(struct net_device *ndev)
|
2009-04-25 20:53:39 +08:00
|
|
|
{
|
|
|
|
struct temac_local *lp = netdev_priv(ndev);
|
2019-05-23 20:02:20 +08:00
|
|
|
unsigned long flags;
|
2009-04-25 20:53:39 +08:00
|
|
|
|
|
|
|
/* set up unicast MAC address filter set its mac address */
|
2019-05-23 20:02:20 +08:00
|
|
|
spin_lock_irqsave(lp->indirect_lock, flags);
|
|
|
|
temac_indirect_out32_locked(lp, XTE_UAW0_OFFSET,
|
|
|
|
(ndev->dev_addr[0]) |
|
|
|
|
(ndev->dev_addr[1] << 8) |
|
|
|
|
(ndev->dev_addr[2] << 16) |
|
|
|
|
(ndev->dev_addr[3] << 24));
|
2009-04-25 20:53:39 +08:00
|
|
|
/* There are reserved bits in EUAW1
|
|
|
|
* so don't affect them Set MAC bits [47:32] in EUAW1 */
|
2019-05-23 20:02:20 +08:00
|
|
|
temac_indirect_out32_locked(lp, XTE_UAW1_OFFSET,
|
|
|
|
(ndev->dev_addr[4] & 0x000000ff) |
|
|
|
|
(ndev->dev_addr[5] << 8));
|
|
|
|
spin_unlock_irqrestore(lp->indirect_lock, flags);
|
2013-01-01 11:30:19 +08:00
|
|
|
}
|
2009-04-25 20:53:39 +08:00
|
|
|
|
2016-12-07 20:25:29 +08:00
|
|
|
static int temac_init_mac_address(struct net_device *ndev, const void *address)
|
2013-01-01 11:30:19 +08:00
|
|
|
{
|
net: ethernet: fix similar warning reported by kbuild test robot
This patch fixes following (similar) warning reported by kbuild test robot:
In function ‘memcpy’,
inlined from ‘smsc75xx_init_mac_address’ at drivers/net/usb/smsc75xx.c:778:3,
inlined from ‘smsc75xx_bind’ at drivers/net/usb/smsc75xx.c:1501:2:
./include/linux/string.h:355:9: warning: argument 2 null where non-null expected [-Wnonnull]
return __builtin_memcpy(p, q, size);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~
drivers/net/usb/smsc75xx.c: In function ‘smsc75xx_bind’:
./include/linux/string.h:355:9: note: in a call to built-in function ‘__builtin_memcpy’
I've replaced the offending memcpy with ether_addr_copy, because I'm
100% sure, that of_get_mac_address can't return NULL as it returns valid
pointer or ERR_PTR encoded value, nothing else.
I'm hesitant to just change IS_ERR into IS_ERR_OR_NULL check, as this
would make the warning disappear also, but it would be confusing to
check for impossible return value just to make a compiler happy.
I'm now changing all occurencies of memcpy to ether_addr_copy after the
of_get_mac_address call, as it's very likely, that we're going to get
similar reports from kbuild test robot in the future.
Fixes: a51645f70f63 ("net: ethernet: support of_get_mac_address new ERR_PTR error")
Reported-by: kbuild test robot <lkp@intel.com>
Signed-off-by: Petr Štetiar <ynezz@true.cz>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-05-10 17:35:17 +08:00
|
|
|
ether_addr_copy(ndev->dev_addr, address);
|
2013-01-01 11:30:19 +08:00
|
|
|
if (!is_valid_ether_addr(ndev->dev_addr))
|
|
|
|
eth_hw_addr_random(ndev);
|
|
|
|
temac_do_set_mac_address(ndev);
|
2009-04-25 20:53:39 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-01-01 11:30:19 +08:00
|
|
|
static int temac_set_mac_address(struct net_device *ndev, void *p)
|
2010-02-17 15:55:07 +08:00
|
|
|
{
|
|
|
|
struct sockaddr *addr = p;
|
|
|
|
|
2013-01-01 11:30:19 +08:00
|
|
|
if (!is_valid_ether_addr(addr->sa_data))
|
|
|
|
return -EADDRNOTAVAIL;
|
|
|
|
memcpy(ndev->dev_addr, addr->sa_data, ETH_ALEN);
|
|
|
|
temac_do_set_mac_address(ndev);
|
|
|
|
return 0;
|
2010-02-17 15:55:07 +08:00
|
|
|
}
|
|
|
|
|
2009-04-25 20:53:39 +08:00
|
|
|
static void temac_set_multicast_list(struct net_device *ndev)
|
|
|
|
{
|
|
|
|
struct temac_local *lp = netdev_priv(ndev);
|
2019-05-23 20:02:20 +08:00
|
|
|
u32 multi_addr_msw, multi_addr_lsw;
|
2019-05-24 13:24:42 +08:00
|
|
|
int i = 0;
|
2019-05-23 20:02:20 +08:00
|
|
|
unsigned long flags;
|
|
|
|
bool promisc_mode_disabled = false;
|
2009-04-25 20:53:39 +08:00
|
|
|
|
2019-05-23 20:02:20 +08:00
|
|
|
if (ndev->flags & (IFF_PROMISC | IFF_ALLMULTI) ||
|
|
|
|
(netdev_mc_count(ndev) > MULTICAST_CAM_TABLE_NUM)) {
|
2009-04-25 20:53:39 +08:00
|
|
|
temac_indirect_out32(lp, XTE_AFM_OFFSET, XTE_AFM_EPPRM_MASK);
|
|
|
|
dev_info(&ndev->dev, "Promiscuous mode enabled.\n");
|
2019-05-23 20:02:20 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
spin_lock_irqsave(lp->indirect_lock, flags);
|
|
|
|
|
|
|
|
if (!netdev_mc_empty(ndev)) {
|
2010-04-02 05:22:57 +08:00
|
|
|
struct netdev_hw_addr *ha;
|
2009-04-25 20:53:39 +08:00
|
|
|
|
2010-04-02 05:22:57 +08:00
|
|
|
netdev_for_each_mc_addr(ha, ndev) {
|
2019-05-23 20:02:20 +08:00
|
|
|
if (WARN_ON(i >= MULTICAST_CAM_TABLE_NUM))
|
2009-04-25 20:53:39 +08:00
|
|
|
break;
|
2010-04-02 05:22:57 +08:00
|
|
|
multi_addr_msw = ((ha->addr[3] << 24) |
|
|
|
|
(ha->addr[2] << 16) |
|
|
|
|
(ha->addr[1] << 8) |
|
|
|
|
(ha->addr[0]));
|
2019-05-23 20:02:20 +08:00
|
|
|
temac_indirect_out32_locked(lp, XTE_MAW0_OFFSET,
|
|
|
|
multi_addr_msw);
|
2010-04-02 05:22:57 +08:00
|
|
|
multi_addr_lsw = ((ha->addr[5] << 8) |
|
|
|
|
(ha->addr[4]) | (i << 16));
|
2019-05-23 20:02:20 +08:00
|
|
|
temac_indirect_out32_locked(lp, XTE_MAW1_OFFSET,
|
|
|
|
multi_addr_lsw);
|
2010-02-23 17:19:49 +08:00
|
|
|
i++;
|
2009-04-25 20:53:39 +08:00
|
|
|
}
|
2019-05-23 20:02:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Clear all or remaining/unused address table entries */
|
|
|
|
while (i < MULTICAST_CAM_TABLE_NUM) {
|
2019-05-23 20:02:20 +08:00
|
|
|
temac_indirect_out32_locked(lp, XTE_MAW0_OFFSET, 0);
|
|
|
|
temac_indirect_out32_locked(lp, XTE_MAW1_OFFSET, i << 16);
|
2019-05-23 20:02:21 +08:00
|
|
|
i++;
|
2019-05-23 20:02:20 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Enable address filter block if currently disabled */
|
|
|
|
if (temac_indirect_in32_locked(lp, XTE_AFM_OFFSET)
|
|
|
|
& XTE_AFM_EPPRM_MASK) {
|
|
|
|
temac_indirect_out32_locked(lp, XTE_AFM_OFFSET, 0);
|
|
|
|
promisc_mode_disabled = true;
|
2009-04-25 20:53:39 +08:00
|
|
|
}
|
2019-05-23 20:02:20 +08:00
|
|
|
|
|
|
|
spin_unlock_irqrestore(lp->indirect_lock, flags);
|
|
|
|
|
|
|
|
if (promisc_mode_disabled)
|
|
|
|
dev_info(&ndev->dev, "Promiscuous mode disabled.\n");
|
2009-04-25 20:53:39 +08:00
|
|
|
}
|
|
|
|
|
2015-06-05 16:49:17 +08:00
|
|
|
static struct temac_option {
|
2009-04-25 20:53:39 +08:00
|
|
|
int flg;
|
|
|
|
u32 opt;
|
|
|
|
u32 reg;
|
|
|
|
u32 m_or;
|
|
|
|
u32 m_and;
|
|
|
|
} temac_options[] = {
|
|
|
|
/* Turn on jumbo packet support for both Rx and Tx */
|
|
|
|
{
|
|
|
|
.opt = XTE_OPTION_JUMBO,
|
|
|
|
.reg = XTE_TXC_OFFSET,
|
|
|
|
.m_or = XTE_TXC_TXJMBO_MASK,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.opt = XTE_OPTION_JUMBO,
|
|
|
|
.reg = XTE_RXC1_OFFSET,
|
|
|
|
.m_or =XTE_RXC1_RXJMBO_MASK,
|
|
|
|
},
|
|
|
|
/* Turn on VLAN packet support for both Rx and Tx */
|
|
|
|
{
|
|
|
|
.opt = XTE_OPTION_VLAN,
|
|
|
|
.reg = XTE_TXC_OFFSET,
|
|
|
|
.m_or =XTE_TXC_TXVLAN_MASK,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.opt = XTE_OPTION_VLAN,
|
|
|
|
.reg = XTE_RXC1_OFFSET,
|
|
|
|
.m_or =XTE_RXC1_RXVLAN_MASK,
|
|
|
|
},
|
|
|
|
/* Turn on FCS stripping on receive packets */
|
|
|
|
{
|
|
|
|
.opt = XTE_OPTION_FCS_STRIP,
|
|
|
|
.reg = XTE_RXC1_OFFSET,
|
|
|
|
.m_or =XTE_RXC1_RXFCS_MASK,
|
|
|
|
},
|
|
|
|
/* Turn on FCS insertion on transmit packets */
|
|
|
|
{
|
|
|
|
.opt = XTE_OPTION_FCS_INSERT,
|
|
|
|
.reg = XTE_TXC_OFFSET,
|
|
|
|
.m_or =XTE_TXC_TXFCS_MASK,
|
|
|
|
},
|
|
|
|
/* Turn on length/type field checking on receive packets */
|
|
|
|
{
|
|
|
|
.opt = XTE_OPTION_LENTYPE_ERR,
|
|
|
|
.reg = XTE_RXC1_OFFSET,
|
|
|
|
.m_or =XTE_RXC1_RXLT_MASK,
|
|
|
|
},
|
|
|
|
/* Turn on flow control */
|
|
|
|
{
|
|
|
|
.opt = XTE_OPTION_FLOW_CONTROL,
|
|
|
|
.reg = XTE_FCC_OFFSET,
|
|
|
|
.m_or =XTE_FCC_RXFLO_MASK,
|
|
|
|
},
|
|
|
|
/* Turn on flow control */
|
|
|
|
{
|
|
|
|
.opt = XTE_OPTION_FLOW_CONTROL,
|
|
|
|
.reg = XTE_FCC_OFFSET,
|
|
|
|
.m_or =XTE_FCC_TXFLO_MASK,
|
|
|
|
},
|
|
|
|
/* Turn on promiscuous frame filtering (all frames are received ) */
|
|
|
|
{
|
|
|
|
.opt = XTE_OPTION_PROMISC,
|
|
|
|
.reg = XTE_AFM_OFFSET,
|
|
|
|
.m_or =XTE_AFM_EPPRM_MASK,
|
|
|
|
},
|
|
|
|
/* Enable transmitter if not already enabled */
|
|
|
|
{
|
|
|
|
.opt = XTE_OPTION_TXEN,
|
|
|
|
.reg = XTE_TXC_OFFSET,
|
|
|
|
.m_or =XTE_TXC_TXEN_MASK,
|
|
|
|
},
|
|
|
|
/* Enable receiver? */
|
|
|
|
{
|
|
|
|
.opt = XTE_OPTION_RXEN,
|
|
|
|
.reg = XTE_RXC1_OFFSET,
|
|
|
|
.m_or =XTE_RXC1_RXEN_MASK,
|
|
|
|
},
|
|
|
|
{}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* temac_setoptions
|
|
|
|
*/
|
|
|
|
static u32 temac_setoptions(struct net_device *ndev, u32 options)
|
|
|
|
{
|
|
|
|
struct temac_local *lp = netdev_priv(ndev);
|
|
|
|
struct temac_option *tp = &temac_options[0];
|
|
|
|
int reg;
|
2019-05-23 20:02:20 +08:00
|
|
|
unsigned long flags;
|
2009-04-25 20:53:39 +08:00
|
|
|
|
2019-05-23 20:02:20 +08:00
|
|
|
spin_lock_irqsave(lp->indirect_lock, flags);
|
2009-04-25 20:53:39 +08:00
|
|
|
while (tp->opt) {
|
2019-05-23 20:02:20 +08:00
|
|
|
reg = temac_indirect_in32_locked(lp, tp->reg) & ~tp->m_or;
|
|
|
|
if (options & tp->opt) {
|
2009-04-25 20:53:39 +08:00
|
|
|
reg |= tp->m_or;
|
2019-05-23 20:02:20 +08:00
|
|
|
temac_indirect_out32_locked(lp, tp->reg, reg);
|
|
|
|
}
|
2009-04-25 20:53:39 +08:00
|
|
|
tp++;
|
|
|
|
}
|
2019-05-23 20:02:20 +08:00
|
|
|
spin_unlock_irqrestore(lp->indirect_lock, flags);
|
2009-04-25 20:53:39 +08:00
|
|
|
lp->options |= options;
|
|
|
|
|
2010-09-23 13:40:09 +08:00
|
|
|
return 0;
|
2009-04-25 20:53:39 +08:00
|
|
|
}
|
|
|
|
|
2010-06-11 18:17:00 +08:00
|
|
|
/* Initialize temac */
|
2009-04-25 20:53:39 +08:00
|
|
|
static void temac_device_reset(struct net_device *ndev)
|
|
|
|
{
|
|
|
|
struct temac_local *lp = netdev_priv(ndev);
|
|
|
|
u32 timeout;
|
|
|
|
u32 val;
|
2019-05-23 20:02:20 +08:00
|
|
|
unsigned long flags;
|
2009-04-25 20:53:39 +08:00
|
|
|
|
|
|
|
/* Perform a software reset */
|
|
|
|
|
|
|
|
/* 0x300 host enable bit ? */
|
|
|
|
/* reset PHY through control register ?:1 */
|
|
|
|
|
|
|
|
dev_dbg(&ndev->dev, "%s()\n", __func__);
|
|
|
|
|
|
|
|
/* Reset the receiver and wait for it to finish reset */
|
|
|
|
temac_indirect_out32(lp, XTE_RXC1_OFFSET, XTE_RXC1_RXRST_MASK);
|
|
|
|
timeout = 1000;
|
|
|
|
while (temac_indirect_in32(lp, XTE_RXC1_OFFSET) & XTE_RXC1_RXRST_MASK) {
|
|
|
|
udelay(1);
|
|
|
|
if (--timeout == 0) {
|
|
|
|
dev_err(&ndev->dev,
|
|
|
|
"temac_device_reset RX reset timeout!!\n");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Reset the transmitter and wait for it to finish reset */
|
|
|
|
temac_indirect_out32(lp, XTE_TXC_OFFSET, XTE_TXC_TXRST_MASK);
|
|
|
|
timeout = 1000;
|
|
|
|
while (temac_indirect_in32(lp, XTE_TXC_OFFSET) & XTE_TXC_TXRST_MASK) {
|
|
|
|
udelay(1);
|
|
|
|
if (--timeout == 0) {
|
|
|
|
dev_err(&ndev->dev,
|
|
|
|
"temac_device_reset TX reset timeout!!\n");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Disable the receiver */
|
2019-05-23 20:02:20 +08:00
|
|
|
spin_lock_irqsave(lp->indirect_lock, flags);
|
|
|
|
val = temac_indirect_in32_locked(lp, XTE_RXC1_OFFSET);
|
|
|
|
temac_indirect_out32_locked(lp, XTE_RXC1_OFFSET,
|
|
|
|
val & ~XTE_RXC1_RXEN_MASK);
|
|
|
|
spin_unlock_irqrestore(lp->indirect_lock, flags);
|
2009-04-25 20:53:39 +08:00
|
|
|
|
|
|
|
/* Reset Local Link (DMA) */
|
2010-04-08 15:08:02 +08:00
|
|
|
lp->dma_out(lp, DMA_CONTROL_REG, DMA_CONTROL_RST);
|
2009-04-25 20:53:39 +08:00
|
|
|
timeout = 1000;
|
2010-04-08 15:08:02 +08:00
|
|
|
while (lp->dma_in(lp, DMA_CONTROL_REG) & DMA_CONTROL_RST) {
|
2009-04-25 20:53:39 +08:00
|
|
|
udelay(1);
|
|
|
|
if (--timeout == 0) {
|
|
|
|
dev_err(&ndev->dev,
|
|
|
|
"temac_device_reset DMA reset timeout!!\n");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2010-04-08 15:08:02 +08:00
|
|
|
lp->dma_out(lp, DMA_CONTROL_REG, DMA_TAIL_ENABLE);
|
2009-04-25 20:53:39 +08:00
|
|
|
|
2010-07-01 07:39:05 +08:00
|
|
|
if (temac_dma_bd_init(ndev)) {
|
|
|
|
dev_err(&ndev->dev,
|
|
|
|
"temac_device_reset descriptor allocation failed\n");
|
|
|
|
}
|
2009-04-25 20:53:39 +08:00
|
|
|
|
2019-05-23 20:02:20 +08:00
|
|
|
spin_lock_irqsave(lp->indirect_lock, flags);
|
|
|
|
temac_indirect_out32_locked(lp, XTE_RXC0_OFFSET, 0);
|
|
|
|
temac_indirect_out32_locked(lp, XTE_RXC1_OFFSET, 0);
|
|
|
|
temac_indirect_out32_locked(lp, XTE_TXC_OFFSET, 0);
|
|
|
|
temac_indirect_out32_locked(lp, XTE_FCC_OFFSET, XTE_FCC_RXFLO_MASK);
|
|
|
|
spin_unlock_irqrestore(lp->indirect_lock, flags);
|
2009-04-25 20:53:39 +08:00
|
|
|
|
|
|
|
/* Sync default options with HW
|
|
|
|
* but leave receiver and transmitter disabled. */
|
|
|
|
temac_setoptions(ndev,
|
|
|
|
lp->options & ~(XTE_OPTION_TXEN | XTE_OPTION_RXEN));
|
|
|
|
|
2013-01-01 11:30:19 +08:00
|
|
|
temac_do_set_mac_address(ndev);
|
2009-04-25 20:53:39 +08:00
|
|
|
|
|
|
|
/* Set address filter table */
|
|
|
|
temac_set_multicast_list(ndev);
|
|
|
|
if (temac_setoptions(ndev, lp->options))
|
|
|
|
dev_err(&ndev->dev, "Error setting TEMAC options\n");
|
|
|
|
|
|
|
|
/* Init Driver variable */
|
2016-05-03 22:33:13 +08:00
|
|
|
netif_trans_update(ndev); /* prevent tx timeout */
|
2009-04-25 20:53:39 +08:00
|
|
|
}
|
|
|
|
|
2015-06-05 16:49:17 +08:00
|
|
|
static void temac_adjust_link(struct net_device *ndev)
|
2009-04-25 20:53:39 +08:00
|
|
|
{
|
|
|
|
struct temac_local *lp = netdev_priv(ndev);
|
2016-07-14 07:48:51 +08:00
|
|
|
struct phy_device *phy = ndev->phydev;
|
2009-04-25 20:53:39 +08:00
|
|
|
u32 mii_speed;
|
|
|
|
int link_state;
|
2019-05-23 20:02:20 +08:00
|
|
|
unsigned long flags;
|
2009-04-25 20:53:39 +08:00
|
|
|
|
|
|
|
/* hash together the state values to decide if something has changed */
|
|
|
|
link_state = phy->speed | (phy->duplex << 1) | phy->link;
|
|
|
|
|
|
|
|
if (lp->last_link != link_state) {
|
2019-05-23 20:02:20 +08:00
|
|
|
spin_lock_irqsave(lp->indirect_lock, flags);
|
|
|
|
mii_speed = temac_indirect_in32_locked(lp, XTE_EMCFG_OFFSET);
|
2009-04-25 20:53:39 +08:00
|
|
|
mii_speed &= ~XTE_EMCFG_LINKSPD_MASK;
|
|
|
|
|
|
|
|
switch (phy->speed) {
|
|
|
|
case SPEED_1000: mii_speed |= XTE_EMCFG_LINKSPD_1000; break;
|
|
|
|
case SPEED_100: mii_speed |= XTE_EMCFG_LINKSPD_100; break;
|
|
|
|
case SPEED_10: mii_speed |= XTE_EMCFG_LINKSPD_10; break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Write new speed setting out to TEMAC */
|
2019-05-23 20:02:20 +08:00
|
|
|
temac_indirect_out32_locked(lp, XTE_EMCFG_OFFSET, mii_speed);
|
|
|
|
spin_unlock_irqrestore(lp->indirect_lock, flags);
|
|
|
|
|
2009-04-25 20:53:39 +08:00
|
|
|
lp->last_link = link_state;
|
|
|
|
phy_print_status(phy);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-30 15:17:50 +08:00
|
|
|
#ifdef CONFIG_64BIT
|
|
|
|
|
2019-05-04 18:10:30 +08:00
|
|
|
static void ptr_to_txbd(void *p, struct cdmac_bd *bd)
|
2019-04-30 15:17:50 +08:00
|
|
|
{
|
|
|
|
bd->app3 = (u32)(((u64)p) >> 32);
|
|
|
|
bd->app4 = (u32)((u64)p & 0xFFFFFFFF);
|
|
|
|
}
|
|
|
|
|
2019-05-04 18:10:30 +08:00
|
|
|
static void *ptr_from_txbd(struct cdmac_bd *bd)
|
2019-04-30 15:17:50 +08:00
|
|
|
{
|
|
|
|
return (void *)(((u64)(bd->app3) << 32) | bd->app4);
|
|
|
|
}
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
2019-05-04 18:10:30 +08:00
|
|
|
static void ptr_to_txbd(void *p, struct cdmac_bd *bd)
|
2019-04-30 15:17:50 +08:00
|
|
|
{
|
|
|
|
bd->app4 = (u32)p;
|
|
|
|
}
|
|
|
|
|
2019-05-04 18:10:30 +08:00
|
|
|
static void *ptr_from_txbd(struct cdmac_bd *bd)
|
2019-04-30 15:17:50 +08:00
|
|
|
{
|
|
|
|
return (void *)(bd->app4);
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
2009-04-25 20:53:39 +08:00
|
|
|
static void temac_start_xmit_done(struct net_device *ndev)
|
|
|
|
{
|
|
|
|
struct temac_local *lp = netdev_priv(ndev);
|
|
|
|
struct cdmac_bd *cur_p;
|
|
|
|
unsigned int stat = 0;
|
2019-04-30 15:17:50 +08:00
|
|
|
struct sk_buff *skb;
|
2009-04-25 20:53:39 +08:00
|
|
|
|
|
|
|
cur_p = &lp->tx_bd_v[lp->tx_bd_ci];
|
2019-04-30 15:17:52 +08:00
|
|
|
stat = be32_to_cpu(cur_p->app0);
|
2009-04-25 20:53:39 +08:00
|
|
|
|
|
|
|
while (stat & STS_CTRL_APP0_CMPLT) {
|
2019-04-30 15:17:52 +08:00
|
|
|
dma_unmap_single(ndev->dev.parent, be32_to_cpu(cur_p->phys),
|
|
|
|
be32_to_cpu(cur_p->len), DMA_TO_DEVICE);
|
2019-04-30 15:17:50 +08:00
|
|
|
skb = (struct sk_buff *)ptr_from_txbd(cur_p);
|
|
|
|
if (skb)
|
|
|
|
dev_consume_skb_irq(skb);
|
2009-04-25 20:53:39 +08:00
|
|
|
cur_p->app0 = 0;
|
2010-05-27 11:44:30 +08:00
|
|
|
cur_p->app1 = 0;
|
|
|
|
cur_p->app2 = 0;
|
|
|
|
cur_p->app3 = 0;
|
|
|
|
cur_p->app4 = 0;
|
2009-04-25 20:53:39 +08:00
|
|
|
|
|
|
|
ndev->stats.tx_packets++;
|
2019-04-30 15:17:52 +08:00
|
|
|
ndev->stats.tx_bytes += be32_to_cpu(cur_p->len);
|
2009-04-25 20:53:39 +08:00
|
|
|
|
|
|
|
lp->tx_bd_ci++;
|
2020-02-28 15:57:26 +08:00
|
|
|
if (lp->tx_bd_ci >= lp->tx_bd_num)
|
2009-04-25 20:53:39 +08:00
|
|
|
lp->tx_bd_ci = 0;
|
|
|
|
|
|
|
|
cur_p = &lp->tx_bd_v[lp->tx_bd_ci];
|
2019-04-30 15:17:52 +08:00
|
|
|
stat = be32_to_cpu(cur_p->app0);
|
2009-04-25 20:53:39 +08:00
|
|
|
}
|
|
|
|
|
2020-02-21 14:47:21 +08:00
|
|
|
/* Matches barrier in temac_start_xmit */
|
|
|
|
smp_mb();
|
|
|
|
|
2009-04-25 20:53:39 +08:00
|
|
|
netif_wake_queue(ndev);
|
|
|
|
}
|
|
|
|
|
2010-05-27 11:44:30 +08:00
|
|
|
static inline int temac_check_tx_bd_space(struct temac_local *lp, int num_frag)
|
|
|
|
{
|
|
|
|
struct cdmac_bd *cur_p;
|
|
|
|
int tail;
|
|
|
|
|
|
|
|
tail = lp->tx_bd_tail;
|
|
|
|
cur_p = &lp->tx_bd_v[tail];
|
|
|
|
|
|
|
|
do {
|
|
|
|
if (cur_p->app0)
|
|
|
|
return NETDEV_TX_BUSY;
|
|
|
|
|
|
|
|
tail++;
|
2020-02-28 15:57:26 +08:00
|
|
|
if (tail >= lp->tx_bd_num)
|
2010-05-27 11:44:30 +08:00
|
|
|
tail = 0;
|
|
|
|
|
|
|
|
cur_p = &lp->tx_bd_v[tail];
|
|
|
|
num_frag--;
|
|
|
|
} while (num_frag >= 0);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-09-19 18:32:40 +08:00
|
|
|
static netdev_tx_t
|
|
|
|
temac_start_xmit(struct sk_buff *skb, struct net_device *ndev)
|
2009-04-25 20:53:39 +08:00
|
|
|
{
|
|
|
|
struct temac_local *lp = netdev_priv(ndev);
|
|
|
|
struct cdmac_bd *cur_p;
|
2020-02-28 15:57:12 +08:00
|
|
|
dma_addr_t tail_p, skb_dma_addr;
|
2009-04-25 20:53:39 +08:00
|
|
|
int ii;
|
|
|
|
unsigned long num_frag;
|
|
|
|
skb_frag_t *frag;
|
|
|
|
|
|
|
|
num_frag = skb_shinfo(skb)->nr_frags;
|
|
|
|
frag = &skb_shinfo(skb)->frags[0];
|
|
|
|
cur_p = &lp->tx_bd_v[lp->tx_bd_tail];
|
|
|
|
|
2019-04-30 15:17:56 +08:00
|
|
|
if (temac_check_tx_bd_space(lp, num_frag + 1)) {
|
2020-02-21 14:47:21 +08:00
|
|
|
if (netif_queue_stopped(ndev))
|
|
|
|
return NETDEV_TX_BUSY;
|
|
|
|
|
|
|
|
netif_stop_queue(ndev);
|
|
|
|
|
|
|
|
/* Matches barrier in temac_start_xmit_done */
|
|
|
|
smp_mb();
|
|
|
|
|
|
|
|
/* Space might have just been freed - check again */
|
|
|
|
if (temac_check_tx_bd_space(lp, num_frag))
|
|
|
|
return NETDEV_TX_BUSY;
|
|
|
|
|
|
|
|
netif_wake_queue(ndev);
|
2009-04-25 20:53:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
cur_p->app0 = 0;
|
|
|
|
if (skb->ip_summed == CHECKSUM_PARTIAL) {
|
2010-12-14 23:24:08 +08:00
|
|
|
unsigned int csum_start_off = skb_checksum_start_offset(skb);
|
2010-05-27 11:44:30 +08:00
|
|
|
unsigned int csum_index_off = csum_start_off + skb->csum_offset;
|
|
|
|
|
2019-04-30 15:17:52 +08:00
|
|
|
cur_p->app0 |= cpu_to_be32(0x000001); /* TX Checksum Enabled */
|
|
|
|
cur_p->app1 = cpu_to_be32((csum_start_off << 16)
|
|
|
|
| csum_index_off);
|
2010-05-27 11:44:30 +08:00
|
|
|
cur_p->app2 = 0; /* initial checksum seed */
|
2009-04-25 20:53:39 +08:00
|
|
|
}
|
2010-05-27 11:44:30 +08:00
|
|
|
|
2019-04-30 15:17:52 +08:00
|
|
|
cur_p->app0 |= cpu_to_be32(STS_CTRL_APP0_SOP);
|
|
|
|
skb_dma_addr = dma_map_single(ndev->dev.parent, skb->data,
|
|
|
|
skb_headlen(skb), DMA_TO_DEVICE);
|
|
|
|
cur_p->len = cpu_to_be32(skb_headlen(skb));
|
2020-02-21 14:47:58 +08:00
|
|
|
if (WARN_ON_ONCE(dma_mapping_error(ndev->dev.parent, skb_dma_addr))) {
|
|
|
|
dev_kfree_skb_any(skb);
|
|
|
|
ndev->stats.tx_dropped++;
|
|
|
|
return NETDEV_TX_OK;
|
|
|
|
}
|
2019-04-30 15:17:52 +08:00
|
|
|
cur_p->phys = cpu_to_be32(skb_dma_addr);
|
2019-04-30 15:17:50 +08:00
|
|
|
ptr_to_txbd((void *)skb, cur_p);
|
2009-04-25 20:53:39 +08:00
|
|
|
|
|
|
|
for (ii = 0; ii < num_frag; ii++) {
|
2020-02-28 15:57:26 +08:00
|
|
|
if (++lp->tx_bd_tail >= lp->tx_bd_num)
|
2009-04-25 20:53:39 +08:00
|
|
|
lp->tx_bd_tail = 0;
|
|
|
|
|
|
|
|
cur_p = &lp->tx_bd_v[lp->tx_bd_tail];
|
2019-04-30 15:17:52 +08:00
|
|
|
skb_dma_addr = dma_map_single(ndev->dev.parent,
|
|
|
|
skb_frag_address(frag),
|
|
|
|
skb_frag_size(frag),
|
|
|
|
DMA_TO_DEVICE);
|
2020-02-21 14:47:33 +08:00
|
|
|
if (dma_mapping_error(ndev->dev.parent, skb_dma_addr)) {
|
|
|
|
if (--lp->tx_bd_tail < 0)
|
2020-02-28 15:57:26 +08:00
|
|
|
lp->tx_bd_tail = lp->tx_bd_num - 1;
|
2020-02-21 14:47:33 +08:00
|
|
|
cur_p = &lp->tx_bd_v[lp->tx_bd_tail];
|
|
|
|
while (--ii >= 0) {
|
|
|
|
--frag;
|
|
|
|
dma_unmap_single(ndev->dev.parent,
|
|
|
|
be32_to_cpu(cur_p->phys),
|
|
|
|
skb_frag_size(frag),
|
|
|
|
DMA_TO_DEVICE);
|
|
|
|
if (--lp->tx_bd_tail < 0)
|
2020-02-28 15:57:26 +08:00
|
|
|
lp->tx_bd_tail = lp->tx_bd_num - 1;
|
2020-02-21 14:47:33 +08:00
|
|
|
cur_p = &lp->tx_bd_v[lp->tx_bd_tail];
|
|
|
|
}
|
|
|
|
dma_unmap_single(ndev->dev.parent,
|
|
|
|
be32_to_cpu(cur_p->phys),
|
|
|
|
skb_headlen(skb), DMA_TO_DEVICE);
|
2020-02-21 14:47:58 +08:00
|
|
|
dev_kfree_skb_any(skb);
|
|
|
|
ndev->stats.tx_dropped++;
|
|
|
|
return NETDEV_TX_OK;
|
2020-02-21 14:47:33 +08:00
|
|
|
}
|
2019-04-30 15:17:52 +08:00
|
|
|
cur_p->phys = cpu_to_be32(skb_dma_addr);
|
|
|
|
cur_p->len = cpu_to_be32(skb_frag_size(frag));
|
2009-04-25 20:53:39 +08:00
|
|
|
cur_p->app0 = 0;
|
|
|
|
frag++;
|
|
|
|
}
|
2019-04-30 15:17:52 +08:00
|
|
|
cur_p->app0 |= cpu_to_be32(STS_CTRL_APP0_EOP);
|
2009-04-25 20:53:39 +08:00
|
|
|
|
|
|
|
tail_p = lp->tx_bd_p + sizeof(*lp->tx_bd_v) * lp->tx_bd_tail;
|
|
|
|
lp->tx_bd_tail++;
|
2020-02-28 15:57:26 +08:00
|
|
|
if (lp->tx_bd_tail >= lp->tx_bd_num)
|
2009-04-25 20:53:39 +08:00
|
|
|
lp->tx_bd_tail = 0;
|
|
|
|
|
2011-06-20 05:51:26 +08:00
|
|
|
skb_tx_timestamp(skb);
|
|
|
|
|
2009-04-25 20:53:39 +08:00
|
|
|
/* Kick off the transfer */
|
2019-04-30 15:17:59 +08:00
|
|
|
wmb();
|
2010-04-08 15:08:02 +08:00
|
|
|
lp->dma_out(lp, TX_TAILDESC_PTR, tail_p); /* DMA start */
|
2009-04-25 20:53:39 +08:00
|
|
|
|
2009-06-23 14:03:08 +08:00
|
|
|
return NETDEV_TX_OK;
|
2009-04-25 20:53:39 +08:00
|
|
|
}
|
|
|
|
|
2020-02-21 14:47:58 +08:00
|
|
|
static int ll_temac_recv_buffers_available(struct temac_local *lp)
|
|
|
|
{
|
|
|
|
int available;
|
|
|
|
|
|
|
|
if (!lp->rx_skb[lp->rx_bd_ci])
|
|
|
|
return 0;
|
|
|
|
available = 1 + lp->rx_bd_tail - lp->rx_bd_ci;
|
|
|
|
if (available <= 0)
|
2020-02-28 15:57:26 +08:00
|
|
|
available += lp->rx_bd_num;
|
2020-02-21 14:47:58 +08:00
|
|
|
return available;
|
|
|
|
}
|
2009-04-25 20:53:39 +08:00
|
|
|
|
|
|
|
static void ll_temac_recv(struct net_device *ndev)
|
|
|
|
{
|
|
|
|
struct temac_local *lp = netdev_priv(ndev);
|
|
|
|
unsigned long flags;
|
2020-02-21 14:47:45 +08:00
|
|
|
int rx_bd;
|
|
|
|
bool update_tail = false;
|
2009-04-25 20:53:39 +08:00
|
|
|
|
|
|
|
spin_lock_irqsave(&lp->rx_lock, flags);
|
|
|
|
|
2020-02-21 14:47:45 +08:00
|
|
|
/* Process all received buffers, passing them on network
|
|
|
|
* stack. After this, the buffer descriptors will be in an
|
|
|
|
* un-allocated stage, where no skb is allocated for it, and
|
|
|
|
* they are therefore not available for TEMAC/DMA.
|
|
|
|
*/
|
|
|
|
do {
|
|
|
|
struct cdmac_bd *bd = &lp->rx_bd_v[lp->rx_bd_ci];
|
|
|
|
struct sk_buff *skb = lp->rx_skb[lp->rx_bd_ci];
|
|
|
|
unsigned int bdstat = be32_to_cpu(bd->app0);
|
|
|
|
int length;
|
|
|
|
|
|
|
|
/* While this should not normally happen, we can end
|
|
|
|
* here when GFP_ATOMIC allocations fail, and we
|
|
|
|
* therefore have un-allocated buffers.
|
|
|
|
*/
|
|
|
|
if (!skb)
|
|
|
|
break;
|
2009-04-25 20:53:39 +08:00
|
|
|
|
2020-02-21 14:47:45 +08:00
|
|
|
/* Loop over all completed buffer descriptors */
|
|
|
|
if (!(bdstat & STS_CTRL_APP0_CMPLT))
|
|
|
|
break;
|
2009-04-25 20:53:39 +08:00
|
|
|
|
2020-02-21 14:47:45 +08:00
|
|
|
dma_unmap_single(ndev->dev.parent, be32_to_cpu(bd->phys),
|
2019-04-30 15:17:55 +08:00
|
|
|
XTE_MAX_JUMBO_FRAME_SIZE, DMA_FROM_DEVICE);
|
2020-02-21 14:47:45 +08:00
|
|
|
/* The buffer is not valid for DMA anymore */
|
|
|
|
bd->phys = 0;
|
|
|
|
bd->len = 0;
|
2009-04-25 20:53:39 +08:00
|
|
|
|
2020-02-21 14:47:45 +08:00
|
|
|
length = be32_to_cpu(bd->app4) & 0x3FFF;
|
2009-04-25 20:53:39 +08:00
|
|
|
skb_put(skb, length);
|
|
|
|
skb->protocol = eth_type_trans(skb, ndev);
|
2010-09-03 04:07:41 +08:00
|
|
|
skb_checksum_none_assert(skb);
|
2009-04-25 20:53:39 +08:00
|
|
|
|
2010-05-27 11:44:30 +08:00
|
|
|
/* if we're doing rx csum offload, set it up */
|
|
|
|
if (((lp->temac_features & TEMAC_FEATURE_RX_CSUM) != 0) &&
|
2014-03-13 01:22:36 +08:00
|
|
|
(skb->protocol == htons(ETH_P_IP)) &&
|
|
|
|
(skb->len > 64)) {
|
2010-05-27 11:44:30 +08:00
|
|
|
|
2019-04-30 15:17:52 +08:00
|
|
|
/* Convert from device endianness (be32) to cpu
|
|
|
|
* endiannes, and if necessary swap the bytes
|
|
|
|
* (back) for proper IP checksum byte order
|
|
|
|
* (be16).
|
|
|
|
*/
|
2020-02-21 14:47:45 +08:00
|
|
|
skb->csum = htons(be32_to_cpu(bd->app3) & 0xFFFF);
|
2010-05-27 11:44:30 +08:00
|
|
|
skb->ip_summed = CHECKSUM_COMPLETE;
|
|
|
|
}
|
|
|
|
|
2011-06-20 05:51:26 +08:00
|
|
|
if (!skb_defer_rx_timestamp(skb))
|
|
|
|
netif_rx(skb);
|
2020-02-21 14:47:45 +08:00
|
|
|
/* The skb buffer is now owned by network stack above */
|
|
|
|
lp->rx_skb[lp->rx_bd_ci] = NULL;
|
2009-04-25 20:53:39 +08:00
|
|
|
|
|
|
|
ndev->stats.rx_packets++;
|
|
|
|
ndev->stats.rx_bytes += length;
|
|
|
|
|
2020-02-21 14:47:45 +08:00
|
|
|
rx_bd = lp->rx_bd_ci;
|
2020-02-28 15:57:26 +08:00
|
|
|
if (++lp->rx_bd_ci >= lp->rx_bd_num)
|
2020-02-21 14:47:45 +08:00
|
|
|
lp->rx_bd_ci = 0;
|
|
|
|
} while (rx_bd != lp->rx_bd_tail);
|
|
|
|
|
2020-02-21 14:47:58 +08:00
|
|
|
/* DMA operations will halt when the last buffer descriptor is
|
|
|
|
* processed (ie. the one pointed to by RX_TAILDESC_PTR).
|
|
|
|
* When that happens, no more interrupt events will be
|
|
|
|
* generated. No IRQ_COAL or IRQ_DLY, and not even an
|
|
|
|
* IRQ_ERR. To avoid stalling, we schedule a delayed work
|
|
|
|
* when there is a potential risk of that happening. The work
|
|
|
|
* will call this function, and thus re-schedule itself until
|
|
|
|
* enough buffers are available again.
|
|
|
|
*/
|
|
|
|
if (ll_temac_recv_buffers_available(lp) < lp->coalesce_count_rx)
|
|
|
|
schedule_delayed_work(&lp->restart_work, HZ / 1000);
|
|
|
|
|
2020-02-21 14:47:45 +08:00
|
|
|
/* Allocate new buffers for those buffer descriptors that were
|
|
|
|
* passed to network stack. Note that GFP_ATOMIC allocations
|
|
|
|
* can fail (e.g. when a larger burst of GFP_ATOMIC
|
|
|
|
* allocations occurs), so while we try to allocate all
|
|
|
|
* buffers in the same interrupt where they were processed, we
|
|
|
|
* continue with what we could get in case of allocation
|
|
|
|
* failure. Allocation of remaining buffers will be retried
|
|
|
|
* in following calls.
|
|
|
|
*/
|
|
|
|
while (1) {
|
|
|
|
struct sk_buff *skb;
|
|
|
|
struct cdmac_bd *bd;
|
|
|
|
dma_addr_t skb_dma_addr;
|
|
|
|
|
|
|
|
rx_bd = lp->rx_bd_tail + 1;
|
2020-02-28 15:57:26 +08:00
|
|
|
if (rx_bd >= lp->rx_bd_num)
|
2020-02-21 14:47:45 +08:00
|
|
|
rx_bd = 0;
|
|
|
|
bd = &lp->rx_bd_v[rx_bd];
|
|
|
|
|
|
|
|
if (bd->phys)
|
|
|
|
break; /* All skb's allocated */
|
|
|
|
|
|
|
|
skb = netdev_alloc_skb_ip_align(ndev, XTE_MAX_JUMBO_FRAME_SIZE);
|
|
|
|
if (!skb) {
|
|
|
|
dev_warn(&ndev->dev, "skb alloc failed\n");
|
|
|
|
break;
|
2009-04-25 20:53:39 +08:00
|
|
|
}
|
|
|
|
|
2020-02-21 14:47:45 +08:00
|
|
|
skb_dma_addr = dma_map_single(ndev->dev.parent, skb->data,
|
2019-04-30 15:17:52 +08:00
|
|
|
XTE_MAX_JUMBO_FRAME_SIZE,
|
|
|
|
DMA_FROM_DEVICE);
|
2020-02-21 14:47:45 +08:00
|
|
|
if (WARN_ON_ONCE(dma_mapping_error(ndev->dev.parent,
|
|
|
|
skb_dma_addr))) {
|
|
|
|
dev_kfree_skb_any(skb);
|
|
|
|
break;
|
|
|
|
}
|
2009-04-25 20:53:39 +08:00
|
|
|
|
2020-02-21 14:47:45 +08:00
|
|
|
bd->phys = cpu_to_be32(skb_dma_addr);
|
|
|
|
bd->len = cpu_to_be32(XTE_MAX_JUMBO_FRAME_SIZE);
|
|
|
|
bd->app0 = cpu_to_be32(STS_CTRL_APP0_IRQONEND);
|
|
|
|
lp->rx_skb[rx_bd] = skb;
|
|
|
|
|
|
|
|
lp->rx_bd_tail = rx_bd;
|
|
|
|
update_tail = true;
|
|
|
|
}
|
2009-04-25 20:53:39 +08:00
|
|
|
|
2020-02-21 14:47:45 +08:00
|
|
|
/* Move tail pointer when buffers have been allocated */
|
|
|
|
if (update_tail) {
|
|
|
|
lp->dma_out(lp, RX_TAILDESC_PTR,
|
|
|
|
lp->rx_bd_p + sizeof(*lp->rx_bd_v) * lp->rx_bd_tail);
|
2009-04-25 20:53:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
spin_unlock_irqrestore(&lp->rx_lock, flags);
|
|
|
|
}
|
|
|
|
|
2020-02-21 14:47:58 +08:00
|
|
|
/* Function scheduled to ensure a restart in case of DMA halt
|
|
|
|
* condition caused by running out of buffer descriptors.
|
|
|
|
*/
|
|
|
|
static void ll_temac_restart_work_func(struct work_struct *work)
|
|
|
|
{
|
|
|
|
struct temac_local *lp = container_of(work, struct temac_local,
|
|
|
|
restart_work.work);
|
|
|
|
struct net_device *ndev = lp->ndev;
|
|
|
|
|
|
|
|
ll_temac_recv(ndev);
|
|
|
|
}
|
|
|
|
|
2009-04-25 20:53:39 +08:00
|
|
|
static irqreturn_t ll_temac_tx_irq(int irq, void *_ndev)
|
|
|
|
{
|
|
|
|
struct net_device *ndev = _ndev;
|
|
|
|
struct temac_local *lp = netdev_priv(ndev);
|
|
|
|
unsigned int status;
|
|
|
|
|
2010-04-08 15:08:02 +08:00
|
|
|
status = lp->dma_in(lp, TX_IRQ_REG);
|
|
|
|
lp->dma_out(lp, TX_IRQ_REG, status);
|
2009-04-25 20:53:39 +08:00
|
|
|
|
|
|
|
if (status & (IRQ_COAL | IRQ_DLY))
|
|
|
|
temac_start_xmit_done(lp->ndev);
|
2019-05-07 14:42:57 +08:00
|
|
|
if (status & (IRQ_ERR | IRQ_DMAERR))
|
|
|
|
dev_err_ratelimited(&ndev->dev,
|
|
|
|
"TX error 0x%x TX_CHNL_STS=0x%08x\n",
|
|
|
|
status, lp->dma_in(lp, TX_CHNL_STS));
|
2009-04-25 20:53:39 +08:00
|
|
|
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
static irqreturn_t ll_temac_rx_irq(int irq, void *_ndev)
|
|
|
|
{
|
|
|
|
struct net_device *ndev = _ndev;
|
|
|
|
struct temac_local *lp = netdev_priv(ndev);
|
|
|
|
unsigned int status;
|
|
|
|
|
|
|
|
/* Read and clear the status registers */
|
2010-04-08 15:08:02 +08:00
|
|
|
status = lp->dma_in(lp, RX_IRQ_REG);
|
|
|
|
lp->dma_out(lp, RX_IRQ_REG, status);
|
2009-04-25 20:53:39 +08:00
|
|
|
|
|
|
|
if (status & (IRQ_COAL | IRQ_DLY))
|
|
|
|
ll_temac_recv(lp->ndev);
|
2019-05-07 14:42:57 +08:00
|
|
|
if (status & (IRQ_ERR | IRQ_DMAERR))
|
|
|
|
dev_err_ratelimited(&ndev->dev,
|
|
|
|
"RX error 0x%x RX_CHNL_STS=0x%08x\n",
|
|
|
|
status, lp->dma_in(lp, RX_CHNL_STS));
|
2009-04-25 20:53:39 +08:00
|
|
|
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int temac_open(struct net_device *ndev)
|
|
|
|
{
|
|
|
|
struct temac_local *lp = netdev_priv(ndev);
|
2016-07-14 07:48:51 +08:00
|
|
|
struct phy_device *phydev = NULL;
|
2009-04-25 20:53:39 +08:00
|
|
|
int rc;
|
|
|
|
|
|
|
|
dev_dbg(&ndev->dev, "temac_open()\n");
|
|
|
|
|
|
|
|
if (lp->phy_node) {
|
2016-07-14 07:48:51 +08:00
|
|
|
phydev = of_phy_connect(lp->ndev, lp->phy_node,
|
|
|
|
temac_adjust_link, 0, 0);
|
|
|
|
if (!phydev) {
|
2009-04-25 20:53:39 +08:00
|
|
|
dev_err(lp->dev, "of_phy_connect() failed\n");
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
2019-04-30 15:17:49 +08:00
|
|
|
phy_start(phydev);
|
|
|
|
} else if (strlen(lp->phy_name) > 0) {
|
|
|
|
phydev = phy_connect(lp->ndev, lp->phy_name, temac_adjust_link,
|
|
|
|
lp->phy_interface);
|
2019-05-03 20:50:24 +08:00
|
|
|
if (IS_ERR(phydev)) {
|
2019-04-30 15:17:49 +08:00
|
|
|
dev_err(lp->dev, "phy_connect() failed\n");
|
2019-05-03 20:50:24 +08:00
|
|
|
return PTR_ERR(phydev);
|
2019-04-30 15:17:49 +08:00
|
|
|
}
|
2016-07-14 07:48:51 +08:00
|
|
|
phy_start(phydev);
|
2009-04-25 20:53:39 +08:00
|
|
|
}
|
|
|
|
|
2011-11-08 07:31:58 +08:00
|
|
|
temac_device_reset(ndev);
|
|
|
|
|
2009-04-25 20:53:39 +08:00
|
|
|
rc = request_irq(lp->tx_irq, ll_temac_tx_irq, 0, ndev->name, ndev);
|
|
|
|
if (rc)
|
|
|
|
goto err_tx_irq;
|
|
|
|
rc = request_irq(lp->rx_irq, ll_temac_rx_irq, 0, ndev->name, ndev);
|
|
|
|
if (rc)
|
|
|
|
goto err_rx_irq;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err_rx_irq:
|
|
|
|
free_irq(lp->tx_irq, ndev);
|
|
|
|
err_tx_irq:
|
2016-07-14 07:48:51 +08:00
|
|
|
if (phydev)
|
|
|
|
phy_disconnect(phydev);
|
2009-04-25 20:53:39 +08:00
|
|
|
dev_err(lp->dev, "request_irq() failed\n");
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int temac_stop(struct net_device *ndev)
|
|
|
|
{
|
|
|
|
struct temac_local *lp = netdev_priv(ndev);
|
2016-07-14 07:48:51 +08:00
|
|
|
struct phy_device *phydev = ndev->phydev;
|
2009-04-25 20:53:39 +08:00
|
|
|
|
|
|
|
dev_dbg(&ndev->dev, "temac_close()\n");
|
|
|
|
|
2020-02-21 14:47:58 +08:00
|
|
|
cancel_delayed_work_sync(&lp->restart_work);
|
|
|
|
|
2009-04-25 20:53:39 +08:00
|
|
|
free_irq(lp->tx_irq, ndev);
|
|
|
|
free_irq(lp->rx_irq, ndev);
|
|
|
|
|
2016-07-14 07:48:51 +08:00
|
|
|
if (phydev)
|
|
|
|
phy_disconnect(phydev);
|
2009-04-25 20:53:39 +08:00
|
|
|
|
2010-07-08 18:24:51 +08:00
|
|
|
temac_dma_bd_release(ndev);
|
|
|
|
|
2009-04-25 20:53:39 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef CONFIG_NET_POLL_CONTROLLER
|
|
|
|
static void
|
|
|
|
temac_poll_controller(struct net_device *ndev)
|
|
|
|
{
|
|
|
|
struct temac_local *lp = netdev_priv(ndev);
|
|
|
|
|
|
|
|
disable_irq(lp->tx_irq);
|
|
|
|
disable_irq(lp->rx_irq);
|
|
|
|
|
2010-08-18 08:26:34 +08:00
|
|
|
ll_temac_rx_irq(lp->tx_irq, ndev);
|
|
|
|
ll_temac_tx_irq(lp->rx_irq, ndev);
|
2009-04-25 20:53:39 +08:00
|
|
|
|
|
|
|
enable_irq(lp->tx_irq);
|
|
|
|
enable_irq(lp->rx_irq);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static const struct net_device_ops temac_netdev_ops = {
|
|
|
|
.ndo_open = temac_open,
|
|
|
|
.ndo_stop = temac_stop,
|
|
|
|
.ndo_start_xmit = temac_start_xmit,
|
2019-05-23 20:02:22 +08:00
|
|
|
.ndo_set_rx_mode = temac_set_multicast_list,
|
2013-01-01 11:30:19 +08:00
|
|
|
.ndo_set_mac_address = temac_set_mac_address,
|
2010-07-10 19:10:44 +08:00
|
|
|
.ndo_validate_addr = eth_validate_addr,
|
2020-01-22 05:09:33 +08:00
|
|
|
.ndo_do_ioctl = phy_do_ioctl_running,
|
2009-04-25 20:53:39 +08:00
|
|
|
#ifdef CONFIG_NET_POLL_CONTROLLER
|
|
|
|
.ndo_poll_controller = temac_poll_controller,
|
|
|
|
#endif
|
|
|
|
};
|
|
|
|
|
|
|
|
/* ---------------------------------------------------------------------
|
|
|
|
* SYSFS device attributes
|
|
|
|
*/
|
|
|
|
static ssize_t temac_show_llink_regs(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
|
|
|
{
|
|
|
|
struct net_device *ndev = dev_get_drvdata(dev);
|
|
|
|
struct temac_local *lp = netdev_priv(ndev);
|
|
|
|
int i, len = 0;
|
|
|
|
|
|
|
|
for (i = 0; i < 0x11; i++)
|
2010-04-08 15:08:02 +08:00
|
|
|
len += sprintf(buf + len, "%.8x%s", lp->dma_in(lp, i),
|
2009-04-25 20:53:39 +08:00
|
|
|
(i % 8) == 7 ? "\n" : " ");
|
|
|
|
len += sprintf(buf + len, "\n");
|
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
static DEVICE_ATTR(llink_regs, 0440, temac_show_llink_regs, NULL);
|
|
|
|
|
|
|
|
static struct attribute *temac_device_attrs[] = {
|
|
|
|
&dev_attr_llink_regs.attr,
|
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct attribute_group temac_attr_group = {
|
|
|
|
.attrs = temac_device_attrs,
|
|
|
|
};
|
|
|
|
|
2020-02-28 15:57:26 +08:00
|
|
|
/* ---------------------------------------------------------------------
|
|
|
|
* ethtool support
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void ll_temac_ethtools_get_ringparam(struct net_device *ndev,
|
|
|
|
struct ethtool_ringparam *ering)
|
|
|
|
{
|
|
|
|
struct temac_local *lp = netdev_priv(ndev);
|
|
|
|
|
|
|
|
ering->rx_max_pending = RX_BD_NUM_MAX;
|
|
|
|
ering->rx_mini_max_pending = 0;
|
|
|
|
ering->rx_jumbo_max_pending = 0;
|
|
|
|
ering->tx_max_pending = TX_BD_NUM_MAX;
|
|
|
|
ering->rx_pending = lp->rx_bd_num;
|
|
|
|
ering->rx_mini_pending = 0;
|
|
|
|
ering->rx_jumbo_pending = 0;
|
|
|
|
ering->tx_pending = lp->tx_bd_num;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ll_temac_ethtools_set_ringparam(struct net_device *ndev,
|
|
|
|
struct ethtool_ringparam *ering)
|
|
|
|
{
|
|
|
|
struct temac_local *lp = netdev_priv(ndev);
|
|
|
|
|
|
|
|
if (ering->rx_pending > RX_BD_NUM_MAX ||
|
|
|
|
ering->rx_mini_pending ||
|
|
|
|
ering->rx_jumbo_pending ||
|
|
|
|
ering->rx_pending > TX_BD_NUM_MAX)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (netif_running(ndev))
|
|
|
|
return -EBUSY;
|
|
|
|
|
|
|
|
lp->rx_bd_num = ering->rx_pending;
|
|
|
|
lp->tx_bd_num = ering->tx_pending;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-10-19 05:35:25 +08:00
|
|
|
static const struct ethtool_ops temac_ethtool_ops = {
|
2016-11-16 02:06:36 +08:00
|
|
|
.nway_reset = phy_ethtool_nway_reset,
|
2011-10-19 05:35:25 +08:00
|
|
|
.get_link = ethtool_op_get_link,
|
2012-04-04 06:59:30 +08:00
|
|
|
.get_ts_info = ethtool_op_get_ts_info,
|
2016-07-14 07:48:52 +08:00
|
|
|
.get_link_ksettings = phy_ethtool_get_link_ksettings,
|
|
|
|
.set_link_ksettings = phy_ethtool_set_link_ksettings,
|
2020-02-28 15:57:26 +08:00
|
|
|
.get_ringparam = ll_temac_ethtools_get_ringparam,
|
|
|
|
.set_ringparam = ll_temac_ethtools_set_ringparam,
|
2011-10-19 05:35:25 +08:00
|
|
|
};
|
|
|
|
|
2019-04-30 15:17:49 +08:00
|
|
|
static int temac_probe(struct platform_device *pdev)
|
2009-04-25 20:53:39 +08:00
|
|
|
{
|
2019-04-30 15:17:49 +08:00
|
|
|
struct ll_temac_platform_data *pdata = dev_get_platdata(&pdev->dev);
|
|
|
|
struct device_node *temac_np = dev_of_node(&pdev->dev), *dma_np;
|
2009-04-25 20:53:39 +08:00
|
|
|
struct temac_local *lp;
|
|
|
|
struct net_device *ndev;
|
2019-04-30 15:17:49 +08:00
|
|
|
struct resource *res;
|
2009-04-25 20:53:39 +08:00
|
|
|
const void *addr;
|
2010-05-27 11:44:30 +08:00
|
|
|
__be32 *p;
|
2019-04-30 15:17:51 +08:00
|
|
|
bool little_endian;
|
2016-12-07 20:25:29 +08:00
|
|
|
int rc = 0;
|
2009-04-25 20:53:39 +08:00
|
|
|
|
|
|
|
/* Init network device structure */
|
2019-04-30 15:17:48 +08:00
|
|
|
ndev = devm_alloc_etherdev(&pdev->dev, sizeof(*lp));
|
2012-01-29 21:47:52 +08:00
|
|
|
if (!ndev)
|
2009-04-25 20:53:39 +08:00
|
|
|
return -ENOMEM;
|
2012-01-29 21:47:52 +08:00
|
|
|
|
2019-04-30 15:17:49 +08:00
|
|
|
platform_set_drvdata(pdev, ndev);
|
|
|
|
SET_NETDEV_DEV(ndev, &pdev->dev);
|
2013-12-03 00:51:13 +08:00
|
|
|
ndev->features = NETIF_F_SG;
|
2009-04-25 20:53:39 +08:00
|
|
|
ndev->netdev_ops = &temac_netdev_ops;
|
2011-10-19 05:35:25 +08:00
|
|
|
ndev->ethtool_ops = &temac_ethtool_ops;
|
2009-04-25 20:53:39 +08:00
|
|
|
#if 0
|
|
|
|
ndev->features |= NETIF_F_IP_CSUM; /* Can checksum TCP/UDP over IPv4. */
|
|
|
|
ndev->features |= NETIF_F_HW_CSUM; /* Can checksum all the packets. */
|
|
|
|
ndev->features |= NETIF_F_IPV6_CSUM; /* Can checksum IPV6 TCP/UDP */
|
|
|
|
ndev->features |= NETIF_F_HIGHDMA; /* Can DMA to high memory. */
|
2013-04-19 10:04:27 +08:00
|
|
|
ndev->features |= NETIF_F_HW_VLAN_CTAG_TX; /* Transmit VLAN hw accel */
|
|
|
|
ndev->features |= NETIF_F_HW_VLAN_CTAG_RX; /* Receive VLAN hw acceleration */
|
|
|
|
ndev->features |= NETIF_F_HW_VLAN_CTAG_FILTER; /* Receive VLAN filtering */
|
2009-04-25 20:53:39 +08:00
|
|
|
ndev->features |= NETIF_F_VLAN_CHALLENGED; /* cannot handle VLAN pkts */
|
|
|
|
ndev->features |= NETIF_F_GSO; /* Enable software GSO. */
|
|
|
|
ndev->features |= NETIF_F_MULTI_QUEUE; /* Has multiple TX/RX queues */
|
|
|
|
ndev->features |= NETIF_F_LRO; /* large receive offload */
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* setup temac private info structure */
|
|
|
|
lp = netdev_priv(ndev);
|
|
|
|
lp->ndev = ndev;
|
2019-04-30 15:17:49 +08:00
|
|
|
lp->dev = &pdev->dev;
|
2009-04-25 20:53:39 +08:00
|
|
|
lp->options = XTE_OPTION_DEFAULTS;
|
2020-02-28 15:57:26 +08:00
|
|
|
lp->rx_bd_num = RX_BD_NUM_DEFAULT;
|
|
|
|
lp->tx_bd_num = TX_BD_NUM_DEFAULT;
|
2009-04-25 20:53:39 +08:00
|
|
|
spin_lock_init(&lp->rx_lock);
|
2020-02-21 14:47:58 +08:00
|
|
|
INIT_DELAYED_WORK(&lp->restart_work, ll_temac_restart_work_func);
|
net: ll_temac: Support indirect_mutex share within TEMAC IP
Indirect register access goes through a DCR bus bridge, which
allows only one outstanding transaction. And to make matters
worse, each TEMAC IP block contains two Ethernet interfaces, and
although they seem to have separate registers for indirect access,
they actually share the registers. Or to be more specific, MSW, LSW
and CTL registers are physically shared between Ethernet interfaces
in same TEMAC IP, with RDY register being (almost) specificic to
the Ethernet interface. The 0x10000 bit in RDY reflects combined
bus ready state though.
So we need to take care to synchronize not only within a single
device, but also between devices in same TEMAC IP.
This commit allows to do that with legacy platform devices.
For OF devices, the xlnx,compound parent of the temac node should be
used to find siblings, and setup a shared indirect_mutex between them.
I will leave this work to somebody else, as I don't have hardware to
test that. No regression is introduced by that, as before this commit
using two Ethernet interfaces in same TEMAC block is simply broken.
Signed-off-by: Esben Haabendal <esben@geanix.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-04-30 15:17:54 +08:00
|
|
|
|
|
|
|
/* Setup mutex for synchronization of indirect register access */
|
|
|
|
if (pdata) {
|
2019-05-23 20:02:20 +08:00
|
|
|
if (!pdata->indirect_lock) {
|
net: ll_temac: Support indirect_mutex share within TEMAC IP
Indirect register access goes through a DCR bus bridge, which
allows only one outstanding transaction. And to make matters
worse, each TEMAC IP block contains two Ethernet interfaces, and
although they seem to have separate registers for indirect access,
they actually share the registers. Or to be more specific, MSW, LSW
and CTL registers are physically shared between Ethernet interfaces
in same TEMAC IP, with RDY register being (almost) specificic to
the Ethernet interface. The 0x10000 bit in RDY reflects combined
bus ready state though.
So we need to take care to synchronize not only within a single
device, but also between devices in same TEMAC IP.
This commit allows to do that with legacy platform devices.
For OF devices, the xlnx,compound parent of the temac node should be
used to find siblings, and setup a shared indirect_mutex between them.
I will leave this work to somebody else, as I don't have hardware to
test that. No regression is introduced by that, as before this commit
using two Ethernet interfaces in same TEMAC block is simply broken.
Signed-off-by: Esben Haabendal <esben@geanix.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-04-30 15:17:54 +08:00
|
|
|
dev_err(&pdev->dev,
|
2019-05-23 20:02:20 +08:00
|
|
|
"indirect_lock missing in platform_data\n");
|
net: ll_temac: Support indirect_mutex share within TEMAC IP
Indirect register access goes through a DCR bus bridge, which
allows only one outstanding transaction. And to make matters
worse, each TEMAC IP block contains two Ethernet interfaces, and
although they seem to have separate registers for indirect access,
they actually share the registers. Or to be more specific, MSW, LSW
and CTL registers are physically shared between Ethernet interfaces
in same TEMAC IP, with RDY register being (almost) specificic to
the Ethernet interface. The 0x10000 bit in RDY reflects combined
bus ready state though.
So we need to take care to synchronize not only within a single
device, but also between devices in same TEMAC IP.
This commit allows to do that with legacy platform devices.
For OF devices, the xlnx,compound parent of the temac node should be
used to find siblings, and setup a shared indirect_mutex between them.
I will leave this work to somebody else, as I don't have hardware to
test that. No regression is introduced by that, as before this commit
using two Ethernet interfaces in same TEMAC block is simply broken.
Signed-off-by: Esben Haabendal <esben@geanix.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-04-30 15:17:54 +08:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
2019-05-23 20:02:20 +08:00
|
|
|
lp->indirect_lock = pdata->indirect_lock;
|
net: ll_temac: Support indirect_mutex share within TEMAC IP
Indirect register access goes through a DCR bus bridge, which
allows only one outstanding transaction. And to make matters
worse, each TEMAC IP block contains two Ethernet interfaces, and
although they seem to have separate registers for indirect access,
they actually share the registers. Or to be more specific, MSW, LSW
and CTL registers are physically shared between Ethernet interfaces
in same TEMAC IP, with RDY register being (almost) specificic to
the Ethernet interface. The 0x10000 bit in RDY reflects combined
bus ready state though.
So we need to take care to synchronize not only within a single
device, but also between devices in same TEMAC IP.
This commit allows to do that with legacy platform devices.
For OF devices, the xlnx,compound parent of the temac node should be
used to find siblings, and setup a shared indirect_mutex between them.
I will leave this work to somebody else, as I don't have hardware to
test that. No regression is introduced by that, as before this commit
using two Ethernet interfaces in same TEMAC block is simply broken.
Signed-off-by: Esben Haabendal <esben@geanix.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-04-30 15:17:54 +08:00
|
|
|
} else {
|
2019-05-23 20:02:20 +08:00
|
|
|
lp->indirect_lock = devm_kmalloc(&pdev->dev,
|
|
|
|
sizeof(*lp->indirect_lock),
|
|
|
|
GFP_KERNEL);
|
|
|
|
spin_lock_init(lp->indirect_lock);
|
net: ll_temac: Support indirect_mutex share within TEMAC IP
Indirect register access goes through a DCR bus bridge, which
allows only one outstanding transaction. And to make matters
worse, each TEMAC IP block contains two Ethernet interfaces, and
although they seem to have separate registers for indirect access,
they actually share the registers. Or to be more specific, MSW, LSW
and CTL registers are physically shared between Ethernet interfaces
in same TEMAC IP, with RDY register being (almost) specificic to
the Ethernet interface. The 0x10000 bit in RDY reflects combined
bus ready state though.
So we need to take care to synchronize not only within a single
device, but also between devices in same TEMAC IP.
This commit allows to do that with legacy platform devices.
For OF devices, the xlnx,compound parent of the temac node should be
used to find siblings, and setup a shared indirect_mutex between them.
I will leave this work to somebody else, as I don't have hardware to
test that. No regression is introduced by that, as before this commit
using two Ethernet interfaces in same TEMAC block is simply broken.
Signed-off-by: Esben Haabendal <esben@geanix.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-04-30 15:17:54 +08:00
|
|
|
}
|
2009-04-25 20:53:39 +08:00
|
|
|
|
|
|
|
/* map device registers */
|
2019-04-30 15:17:49 +08:00
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
2020-01-06 16:43:50 +08:00
|
|
|
lp->regs = devm_ioremap(&pdev->dev, res->start,
|
2019-04-30 15:17:49 +08:00
|
|
|
resource_size(res));
|
|
|
|
if (IS_ERR(lp->regs)) {
|
|
|
|
dev_err(&pdev->dev, "could not map TEMAC registers\n");
|
|
|
|
return PTR_ERR(lp->regs);
|
2009-04-25 20:53:39 +08:00
|
|
|
}
|
|
|
|
|
2019-04-30 15:17:51 +08:00
|
|
|
/* Select register access functions with the specified
|
|
|
|
* endianness mode. Default for OF devices is big-endian.
|
|
|
|
*/
|
|
|
|
little_endian = false;
|
|
|
|
if (temac_np) {
|
|
|
|
if (of_get_property(temac_np, "little-endian", NULL))
|
|
|
|
little_endian = true;
|
|
|
|
} else if (pdata) {
|
|
|
|
little_endian = pdata->reg_little_endian;
|
|
|
|
}
|
|
|
|
if (little_endian) {
|
|
|
|
lp->temac_ior = _temac_ior_le;
|
|
|
|
lp->temac_iow = _temac_iow_le;
|
|
|
|
} else {
|
|
|
|
lp->temac_ior = _temac_ior_be;
|
|
|
|
lp->temac_iow = _temac_iow_be;
|
|
|
|
}
|
|
|
|
|
2010-05-27 11:44:30 +08:00
|
|
|
/* Setup checksum offload, but default to off if not specified */
|
|
|
|
lp->temac_features = 0;
|
2019-04-30 15:17:49 +08:00
|
|
|
if (temac_np) {
|
|
|
|
p = (__be32 *)of_get_property(temac_np, "xlnx,txcsum", NULL);
|
|
|
|
if (p && be32_to_cpu(*p))
|
|
|
|
lp->temac_features |= TEMAC_FEATURE_TX_CSUM;
|
|
|
|
p = (__be32 *)of_get_property(temac_np, "xlnx,rxcsum", NULL);
|
|
|
|
if (p && be32_to_cpu(*p))
|
|
|
|
lp->temac_features |= TEMAC_FEATURE_RX_CSUM;
|
|
|
|
} else if (pdata) {
|
|
|
|
if (pdata->txcsum)
|
|
|
|
lp->temac_features |= TEMAC_FEATURE_TX_CSUM;
|
|
|
|
if (pdata->rxcsum)
|
|
|
|
lp->temac_features |= TEMAC_FEATURE_RX_CSUM;
|
|
|
|
}
|
|
|
|
if (lp->temac_features & TEMAC_FEATURE_TX_CSUM)
|
2010-05-27 11:44:30 +08:00
|
|
|
/* Can checksum TCP/UDP over IPv4. */
|
|
|
|
ndev->features |= NETIF_F_IP_CSUM;
|
2009-04-25 20:53:39 +08:00
|
|
|
|
2019-04-30 15:17:49 +08:00
|
|
|
/* Setup LocalLink DMA */
|
|
|
|
if (temac_np) {
|
|
|
|
/* Find the DMA node, map the DMA registers, and
|
|
|
|
* decode the DMA IRQs.
|
|
|
|
*/
|
|
|
|
dma_np = of_parse_phandle(temac_np, "llink-connected", 0);
|
|
|
|
if (!dma_np) {
|
|
|
|
dev_err(&pdev->dev, "could not find DMA node\n");
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
2010-04-08 15:08:02 +08:00
|
|
|
|
2019-04-30 15:17:49 +08:00
|
|
|
/* Setup the DMA register accesses, could be DCR or
|
|
|
|
* memory mapped.
|
|
|
|
*/
|
|
|
|
if (temac_dcr_setup(lp, pdev, dma_np)) {
|
|
|
|
/* no DCR in the device tree, try non-DCR */
|
|
|
|
lp->sdma_regs = devm_of_iomap(&pdev->dev, dma_np, 0,
|
|
|
|
NULL);
|
|
|
|
if (IS_ERR(lp->sdma_regs)) {
|
|
|
|
dev_err(&pdev->dev,
|
|
|
|
"unable to map DMA registers\n");
|
|
|
|
of_node_put(dma_np);
|
|
|
|
return PTR_ERR(lp->sdma_regs);
|
|
|
|
}
|
2019-04-30 15:17:51 +08:00
|
|
|
if (of_get_property(dma_np, "little-endian", NULL)) {
|
|
|
|
lp->dma_in = temac_dma_in32_le;
|
|
|
|
lp->dma_out = temac_dma_out32_le;
|
|
|
|
} else {
|
|
|
|
lp->dma_in = temac_dma_in32_be;
|
|
|
|
lp->dma_out = temac_dma_out32_be;
|
|
|
|
}
|
2019-04-30 15:17:49 +08:00
|
|
|
dev_dbg(&pdev->dev, "MEM base: %p\n", lp->sdma_regs);
|
2010-04-08 15:08:02 +08:00
|
|
|
}
|
2010-07-09 14:43:20 +08:00
|
|
|
|
2019-04-30 15:17:49 +08:00
|
|
|
/* Get DMA RX and TX interrupts */
|
|
|
|
lp->rx_irq = irq_of_parse_and_map(dma_np, 0);
|
|
|
|
lp->tx_irq = irq_of_parse_and_map(dma_np, 1);
|
|
|
|
|
2019-04-30 15:17:58 +08:00
|
|
|
/* Use defaults for IRQ delay/coalescing setup. These
|
|
|
|
* are configuration values, so does not belong in
|
|
|
|
* device-tree.
|
|
|
|
*/
|
|
|
|
lp->tx_chnl_ctrl = 0x10220000;
|
|
|
|
lp->rx_chnl_ctrl = 0xff070000;
|
2020-02-21 14:47:58 +08:00
|
|
|
lp->coalesce_count_rx = 0x07;
|
2019-04-30 15:17:58 +08:00
|
|
|
|
2019-04-30 15:17:49 +08:00
|
|
|
/* Finished with the DMA node; drop the reference */
|
|
|
|
of_node_put(dma_np);
|
|
|
|
} else if (pdata) {
|
|
|
|
/* 2nd memory resource specifies DMA registers */
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
2020-01-06 16:43:50 +08:00
|
|
|
lp->sdma_regs = devm_ioremap(&pdev->dev, res->start,
|
2019-04-30 15:17:49 +08:00
|
|
|
resource_size(res));
|
|
|
|
if (IS_ERR(lp->sdma_regs)) {
|
|
|
|
dev_err(&pdev->dev,
|
|
|
|
"could not map DMA registers\n");
|
|
|
|
return PTR_ERR(lp->sdma_regs);
|
|
|
|
}
|
2019-04-30 15:17:51 +08:00
|
|
|
if (pdata->dma_little_endian) {
|
|
|
|
lp->dma_in = temac_dma_in32_le;
|
|
|
|
lp->dma_out = temac_dma_out32_le;
|
|
|
|
} else {
|
|
|
|
lp->dma_in = temac_dma_in32_be;
|
|
|
|
lp->dma_out = temac_dma_out32_be;
|
|
|
|
}
|
2010-07-09 14:43:20 +08:00
|
|
|
|
2019-04-30 15:17:49 +08:00
|
|
|
/* Get DMA RX and TX interrupts */
|
|
|
|
lp->rx_irq = platform_get_irq(pdev, 0);
|
|
|
|
lp->tx_irq = platform_get_irq(pdev, 1);
|
2019-04-30 15:17:58 +08:00
|
|
|
|
|
|
|
/* IRQ delay/coalescing setup */
|
|
|
|
if (pdata->tx_irq_timeout || pdata->tx_irq_count)
|
|
|
|
lp->tx_chnl_ctrl = (pdata->tx_irq_timeout << 24) |
|
|
|
|
(pdata->tx_irq_count << 16);
|
|
|
|
else
|
|
|
|
lp->tx_chnl_ctrl = 0x10220000;
|
2020-02-21 14:47:58 +08:00
|
|
|
if (pdata->rx_irq_timeout || pdata->rx_irq_count) {
|
2019-04-30 15:17:58 +08:00
|
|
|
lp->rx_chnl_ctrl = (pdata->rx_irq_timeout << 24) |
|
|
|
|
(pdata->rx_irq_count << 16);
|
2020-02-21 14:47:58 +08:00
|
|
|
lp->coalesce_count_rx = pdata->rx_irq_count;
|
|
|
|
} else {
|
2019-04-30 15:17:58 +08:00
|
|
|
lp->rx_chnl_ctrl = 0xff070000;
|
2020-02-21 14:47:58 +08:00
|
|
|
lp->coalesce_count_rx = 0x07;
|
|
|
|
}
|
2009-04-25 20:53:39 +08:00
|
|
|
}
|
|
|
|
|
2019-04-30 15:17:49 +08:00
|
|
|
/* Error handle returned DMA RX and TX interrupts */
|
|
|
|
if (lp->rx_irq < 0) {
|
|
|
|
if (lp->rx_irq != -EPROBE_DEFER)
|
|
|
|
dev_err(&pdev->dev, "could not get DMA RX irq\n");
|
|
|
|
return lp->rx_irq;
|
|
|
|
}
|
|
|
|
if (lp->tx_irq < 0) {
|
|
|
|
if (lp->tx_irq != -EPROBE_DEFER)
|
|
|
|
dev_err(&pdev->dev, "could not get DMA TX irq\n");
|
|
|
|
return lp->tx_irq;
|
|
|
|
}
|
2009-04-25 20:53:39 +08:00
|
|
|
|
2019-04-30 15:17:49 +08:00
|
|
|
if (temac_np) {
|
|
|
|
/* Retrieve the MAC address */
|
|
|
|
addr = of_get_mac_address(temac_np);
|
2019-05-07 05:27:04 +08:00
|
|
|
if (IS_ERR(addr)) {
|
2019-04-30 15:17:49 +08:00
|
|
|
dev_err(&pdev->dev, "could not find MAC address\n");
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
temac_init_mac_address(ndev, addr);
|
|
|
|
} else if (pdata) {
|
|
|
|
temac_init_mac_address(ndev, pdata->mac_addr);
|
2009-04-25 20:53:39 +08:00
|
|
|
}
|
|
|
|
|
2019-04-30 15:17:48 +08:00
|
|
|
rc = temac_mdio_setup(lp, pdev);
|
2009-04-25 20:53:39 +08:00
|
|
|
if (rc)
|
2019-04-30 15:17:49 +08:00
|
|
|
dev_warn(&pdev->dev, "error registering MDIO bus\n");
|
|
|
|
|
|
|
|
if (temac_np) {
|
|
|
|
lp->phy_node = of_parse_phandle(temac_np, "phy-handle", 0);
|
|
|
|
if (lp->phy_node)
|
|
|
|
dev_dbg(lp->dev, "using PHY node %pOF\n", temac_np);
|
|
|
|
} else if (pdata) {
|
|
|
|
snprintf(lp->phy_name, sizeof(lp->phy_name),
|
|
|
|
PHY_ID_FMT, lp->mii_bus->id, pdata->phy_addr);
|
|
|
|
lp->phy_interface = pdata->phy_interface;
|
|
|
|
}
|
2009-04-25 20:53:39 +08:00
|
|
|
|
|
|
|
/* Add the device attributes */
|
|
|
|
rc = sysfs_create_group(&lp->dev->kobj, &temac_attr_group);
|
|
|
|
if (rc) {
|
|
|
|
dev_err(lp->dev, "Error creating sysfs files\n");
|
2019-04-30 15:17:48 +08:00
|
|
|
goto err_sysfs_create;
|
2009-04-25 20:53:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
rc = register_netdev(lp->ndev);
|
|
|
|
if (rc) {
|
|
|
|
dev_err(lp->dev, "register_netdev() error (%i)\n", rc);
|
|
|
|
goto err_register_ndev;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
2019-04-30 15:17:48 +08:00
|
|
|
err_register_ndev:
|
2009-04-25 20:53:39 +08:00
|
|
|
sysfs_remove_group(&lp->dev->kobj, &temac_attr_group);
|
2019-04-30 15:17:48 +08:00
|
|
|
err_sysfs_create:
|
2019-04-30 15:17:49 +08:00
|
|
|
if (lp->phy_node)
|
|
|
|
of_node_put(lp->phy_node);
|
2019-04-30 15:17:48 +08:00
|
|
|
temac_mdio_teardown(lp);
|
2009-04-25 20:53:39 +08:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2019-04-30 15:17:49 +08:00
|
|
|
static int temac_remove(struct platform_device *pdev)
|
2009-04-25 20:53:39 +08:00
|
|
|
{
|
2019-04-30 15:17:49 +08:00
|
|
|
struct net_device *ndev = platform_get_drvdata(pdev);
|
2009-04-25 20:53:39 +08:00
|
|
|
struct temac_local *lp = netdev_priv(ndev);
|
|
|
|
|
|
|
|
unregister_netdev(ndev);
|
|
|
|
sysfs_remove_group(&lp->dev->kobj, &temac_attr_group);
|
2019-04-30 15:17:49 +08:00
|
|
|
if (lp->phy_node)
|
|
|
|
of_node_put(lp->phy_node);
|
2019-04-30 15:17:48 +08:00
|
|
|
temac_mdio_teardown(lp);
|
2009-04-25 20:53:39 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-03-18 02:37:40 +08:00
|
|
|
static const struct of_device_id temac_of_match[] = {
|
2009-04-25 20:53:39 +08:00
|
|
|
{ .compatible = "xlnx,xps-ll-temac-1.01.b", },
|
2010-02-17 15:14:20 +08:00
|
|
|
{ .compatible = "xlnx,xps-ll-temac-2.00.a", },
|
|
|
|
{ .compatible = "xlnx,xps-ll-temac-2.02.a", },
|
|
|
|
{ .compatible = "xlnx,xps-ll-temac-2.03.a", },
|
2009-04-25 20:53:39 +08:00
|
|
|
{},
|
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(of, temac_of_match);
|
|
|
|
|
2019-04-30 15:17:49 +08:00
|
|
|
static struct platform_driver temac_driver = {
|
|
|
|
.probe = temac_probe,
|
|
|
|
.remove = temac_remove,
|
2009-04-25 20:53:39 +08:00
|
|
|
.driver = {
|
|
|
|
.name = "xilinx_temac",
|
2010-04-14 07:13:02 +08:00
|
|
|
.of_match_table = temac_of_match,
|
2009-04-25 20:53:39 +08:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2019-04-30 15:17:49 +08:00
|
|
|
module_platform_driver(temac_driver);
|
2009-04-25 20:53:39 +08:00
|
|
|
|
|
|
|
MODULE_DESCRIPTION("Xilinx LL_TEMAC Ethernet driver");
|
|
|
|
MODULE_AUTHOR("Yoshio Kashiwagi");
|
|
|
|
MODULE_LICENSE("GPL");
|