2009-08-18 20:23:40 +08:00
|
|
|
/*
|
|
|
|
* Driver for BCM963xx builtin Ethernet mac
|
|
|
|
*
|
|
|
|
* Copyright (C) 2008 Maxime Bizon <mbizon@freebox.fr>
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
*/
|
|
|
|
#include <linux/init.h>
|
2011-06-10 11:36:43 +08:00
|
|
|
#include <linux/interrupt.h>
|
2009-08-18 20:23:40 +08:00
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/clk.h>
|
|
|
|
#include <linux/etherdevice.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>
|
2009-08-18 20:23:40 +08:00
|
|
|
#include <linux/delay.h>
|
|
|
|
#include <linux/ethtool.h>
|
|
|
|
#include <linux/crc32.h>
|
|
|
|
#include <linux/err.h>
|
|
|
|
#include <linux/dma-mapping.h>
|
|
|
|
#include <linux/platform_device.h>
|
|
|
|
#include <linux/if_vlan.h>
|
|
|
|
|
|
|
|
#include <bcm63xx_dev_enet.h>
|
|
|
|
#include "bcm63xx_enet.h"
|
|
|
|
|
|
|
|
static char bcm_enet_driver_name[] = "bcm63xx_enet";
|
|
|
|
static char bcm_enet_driver_version[] = "1.0";
|
|
|
|
|
|
|
|
static int copybreak __read_mostly = 128;
|
|
|
|
module_param(copybreak, int, 0);
|
|
|
|
MODULE_PARM_DESC(copybreak, "Receive copy threshold");
|
|
|
|
|
2013-06-05 05:53:34 +08:00
|
|
|
/* io registers memory shared between all devices */
|
|
|
|
static void __iomem *bcm_enet_shared_base[3];
|
2009-08-18 20:23:40 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* io helpers to access mac registers
|
|
|
|
*/
|
|
|
|
static inline u32 enet_readl(struct bcm_enet_priv *priv, u32 off)
|
|
|
|
{
|
|
|
|
return bcm_readl(priv->base + off);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void enet_writel(struct bcm_enet_priv *priv,
|
|
|
|
u32 val, u32 off)
|
|
|
|
{
|
|
|
|
bcm_writel(val, priv->base + off);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2013-06-05 05:53:35 +08:00
|
|
|
* io helpers to access switch registers
|
2009-08-18 20:23:40 +08:00
|
|
|
*/
|
2013-06-05 05:53:35 +08:00
|
|
|
static inline u32 enetsw_readl(struct bcm_enet_priv *priv, u32 off)
|
|
|
|
{
|
|
|
|
return bcm_readl(priv->base + off);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void enetsw_writel(struct bcm_enet_priv *priv,
|
|
|
|
u32 val, u32 off)
|
|
|
|
{
|
|
|
|
bcm_writel(val, priv->base + off);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline u16 enetsw_readw(struct bcm_enet_priv *priv, u32 off)
|
|
|
|
{
|
|
|
|
return bcm_readw(priv->base + off);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void enetsw_writew(struct bcm_enet_priv *priv,
|
|
|
|
u16 val, u32 off)
|
|
|
|
{
|
|
|
|
bcm_writew(val, priv->base + off);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline u8 enetsw_readb(struct bcm_enet_priv *priv, u32 off)
|
|
|
|
{
|
|
|
|
return bcm_readb(priv->base + off);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void enetsw_writeb(struct bcm_enet_priv *priv,
|
|
|
|
u8 val, u32 off)
|
|
|
|
{
|
|
|
|
bcm_writeb(val, priv->base + off);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* io helpers to access shared registers */
|
2009-08-18 20:23:40 +08:00
|
|
|
static inline u32 enet_dma_readl(struct bcm_enet_priv *priv, u32 off)
|
|
|
|
{
|
2013-06-05 05:53:34 +08:00
|
|
|
return bcm_readl(bcm_enet_shared_base[0] + off);
|
2009-08-18 20:23:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static inline void enet_dma_writel(struct bcm_enet_priv *priv,
|
|
|
|
u32 val, u32 off)
|
|
|
|
{
|
2013-06-05 05:53:34 +08:00
|
|
|
bcm_writel(val, bcm_enet_shared_base[0] + off);
|
|
|
|
}
|
|
|
|
|
2013-06-13 03:53:05 +08:00
|
|
|
static inline u32 enet_dmac_readl(struct bcm_enet_priv *priv, u32 off, int chan)
|
2013-06-05 05:53:34 +08:00
|
|
|
{
|
2013-06-13 03:53:05 +08:00
|
|
|
return bcm_readl(bcm_enet_shared_base[1] +
|
|
|
|
bcm63xx_enetdmacreg(off) + chan * priv->dma_chan_width);
|
2013-06-05 05:53:34 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static inline void enet_dmac_writel(struct bcm_enet_priv *priv,
|
2013-06-13 03:53:05 +08:00
|
|
|
u32 val, u32 off, int chan)
|
2013-06-05 05:53:34 +08:00
|
|
|
{
|
2013-06-13 03:53:05 +08:00
|
|
|
bcm_writel(val, bcm_enet_shared_base[1] +
|
|
|
|
bcm63xx_enetdmacreg(off) + chan * priv->dma_chan_width);
|
2013-06-05 05:53:34 +08:00
|
|
|
}
|
|
|
|
|
2013-06-13 03:53:05 +08:00
|
|
|
static inline u32 enet_dmas_readl(struct bcm_enet_priv *priv, u32 off, int chan)
|
2013-06-05 05:53:34 +08:00
|
|
|
{
|
2013-06-13 03:53:05 +08:00
|
|
|
return bcm_readl(bcm_enet_shared_base[2] + off + chan * priv->dma_chan_width);
|
2013-06-05 05:53:34 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static inline void enet_dmas_writel(struct bcm_enet_priv *priv,
|
2013-06-13 03:53:05 +08:00
|
|
|
u32 val, u32 off, int chan)
|
2013-06-05 05:53:34 +08:00
|
|
|
{
|
2013-06-13 03:53:05 +08:00
|
|
|
bcm_writel(val, bcm_enet_shared_base[2] + off + chan * priv->dma_chan_width);
|
2009-08-18 20:23:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* write given data into mii register and wait for transfer to end
|
|
|
|
* with timeout (average measured transfer time is 25us)
|
|
|
|
*/
|
|
|
|
static int do_mdio_op(struct bcm_enet_priv *priv, unsigned int data)
|
|
|
|
{
|
|
|
|
int limit;
|
|
|
|
|
|
|
|
/* make sure mii interrupt status is cleared */
|
|
|
|
enet_writel(priv, ENET_IR_MII, ENET_IR_REG);
|
|
|
|
|
|
|
|
enet_writel(priv, data, ENET_MIIDATA_REG);
|
|
|
|
wmb();
|
|
|
|
|
|
|
|
/* busy wait on mii interrupt bit, with timeout */
|
|
|
|
limit = 1000;
|
|
|
|
do {
|
|
|
|
if (enet_readl(priv, ENET_IR_REG) & ENET_IR_MII)
|
|
|
|
break;
|
|
|
|
udelay(1);
|
2009-09-21 18:08:48 +08:00
|
|
|
} while (limit-- > 0);
|
2009-08-18 20:23:40 +08:00
|
|
|
|
|
|
|
return (limit < 0) ? 1 : 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* MII internal read callback
|
|
|
|
*/
|
|
|
|
static int bcm_enet_mdio_read(struct bcm_enet_priv *priv, int mii_id,
|
|
|
|
int regnum)
|
|
|
|
{
|
|
|
|
u32 tmp, val;
|
|
|
|
|
|
|
|
tmp = regnum << ENET_MIIDATA_REG_SHIFT;
|
|
|
|
tmp |= 0x2 << ENET_MIIDATA_TA_SHIFT;
|
|
|
|
tmp |= mii_id << ENET_MIIDATA_PHYID_SHIFT;
|
|
|
|
tmp |= ENET_MIIDATA_OP_READ_MASK;
|
|
|
|
|
|
|
|
if (do_mdio_op(priv, tmp))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
val = enet_readl(priv, ENET_MIIDATA_REG);
|
|
|
|
val &= 0xffff;
|
|
|
|
return val;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* MII internal write callback
|
|
|
|
*/
|
|
|
|
static int bcm_enet_mdio_write(struct bcm_enet_priv *priv, int mii_id,
|
|
|
|
int regnum, u16 value)
|
|
|
|
{
|
|
|
|
u32 tmp;
|
|
|
|
|
|
|
|
tmp = (value & 0xffff) << ENET_MIIDATA_DATA_SHIFT;
|
|
|
|
tmp |= 0x2 << ENET_MIIDATA_TA_SHIFT;
|
|
|
|
tmp |= regnum << ENET_MIIDATA_REG_SHIFT;
|
|
|
|
tmp |= mii_id << ENET_MIIDATA_PHYID_SHIFT;
|
|
|
|
tmp |= ENET_MIIDATA_OP_WRITE_MASK;
|
|
|
|
|
|
|
|
(void)do_mdio_op(priv, tmp);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* MII read callback from phylib
|
|
|
|
*/
|
|
|
|
static int bcm_enet_mdio_read_phylib(struct mii_bus *bus, int mii_id,
|
|
|
|
int regnum)
|
|
|
|
{
|
|
|
|
return bcm_enet_mdio_read(bus->priv, mii_id, regnum);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* MII write callback from phylib
|
|
|
|
*/
|
|
|
|
static int bcm_enet_mdio_write_phylib(struct mii_bus *bus, int mii_id,
|
|
|
|
int regnum, u16 value)
|
|
|
|
{
|
|
|
|
return bcm_enet_mdio_write(bus->priv, mii_id, regnum, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* MII read callback from mii core
|
|
|
|
*/
|
|
|
|
static int bcm_enet_mdio_read_mii(struct net_device *dev, int mii_id,
|
|
|
|
int regnum)
|
|
|
|
{
|
|
|
|
return bcm_enet_mdio_read(netdev_priv(dev), mii_id, regnum);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* MII write callback from mii core
|
|
|
|
*/
|
|
|
|
static void bcm_enet_mdio_write_mii(struct net_device *dev, int mii_id,
|
|
|
|
int regnum, int value)
|
|
|
|
{
|
|
|
|
bcm_enet_mdio_write(netdev_priv(dev), mii_id, regnum, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* refill rx queue
|
|
|
|
*/
|
|
|
|
static int bcm_enet_refill_rx(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct bcm_enet_priv *priv;
|
|
|
|
|
|
|
|
priv = netdev_priv(dev);
|
|
|
|
|
|
|
|
while (priv->rx_desc_count < priv->rx_ring_size) {
|
|
|
|
struct bcm_enet_desc *desc;
|
|
|
|
struct sk_buff *skb;
|
|
|
|
dma_addr_t p;
|
|
|
|
int desc_idx;
|
|
|
|
u32 len_stat;
|
|
|
|
|
|
|
|
desc_idx = priv->rx_dirty_desc;
|
|
|
|
desc = &priv->rx_desc_cpu[desc_idx];
|
|
|
|
|
|
|
|
if (!priv->rx_skb[desc_idx]) {
|
|
|
|
skb = netdev_alloc_skb(dev, priv->rx_skb_size);
|
|
|
|
if (!skb)
|
|
|
|
break;
|
|
|
|
priv->rx_skb[desc_idx] = skb;
|
|
|
|
p = dma_map_single(&priv->pdev->dev, skb->data,
|
|
|
|
priv->rx_skb_size,
|
|
|
|
DMA_FROM_DEVICE);
|
|
|
|
desc->address = p;
|
|
|
|
}
|
|
|
|
|
|
|
|
len_stat = priv->rx_skb_size << DMADESC_LENGTH_SHIFT;
|
|
|
|
len_stat |= DMADESC_OWNER_MASK;
|
|
|
|
if (priv->rx_dirty_desc == priv->rx_ring_size - 1) {
|
2013-06-13 03:53:05 +08:00
|
|
|
len_stat |= (DMADESC_WRAP_MASK >> priv->dma_desc_shift);
|
2009-08-18 20:23:40 +08:00
|
|
|
priv->rx_dirty_desc = 0;
|
|
|
|
} else {
|
|
|
|
priv->rx_dirty_desc++;
|
|
|
|
}
|
|
|
|
wmb();
|
|
|
|
desc->len_stat = len_stat;
|
|
|
|
|
|
|
|
priv->rx_desc_count++;
|
|
|
|
|
|
|
|
/* tell dma engine we allocated one buffer */
|
2013-06-13 03:53:05 +08:00
|
|
|
if (priv->dma_has_sram)
|
|
|
|
enet_dma_writel(priv, 1, ENETDMA_BUFALLOC_REG(priv->rx_chan));
|
|
|
|
else
|
|
|
|
enet_dmac_writel(priv, 1, ENETDMAC_BUFALLOC, priv->rx_chan);
|
2009-08-18 20:23:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* If rx ring is still empty, set a timer to try allocating
|
|
|
|
* again at a later time. */
|
|
|
|
if (priv->rx_desc_count == 0 && netif_running(dev)) {
|
|
|
|
dev_warn(&priv->pdev->dev, "unable to refill rx ring\n");
|
|
|
|
priv->rx_timeout.expires = jiffies + HZ;
|
|
|
|
add_timer(&priv->rx_timeout);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* timer callback to defer refill rx queue in case we're OOM
|
|
|
|
*/
|
2017-10-17 08:28:57 +08:00
|
|
|
static void bcm_enet_refill_rx_timer(struct timer_list *t)
|
2009-08-18 20:23:40 +08:00
|
|
|
{
|
2017-10-17 08:28:57 +08:00
|
|
|
struct bcm_enet_priv *priv = from_timer(priv, t, rx_timeout);
|
|
|
|
struct net_device *dev = priv->net_dev;
|
2009-08-18 20:23:40 +08:00
|
|
|
|
|
|
|
spin_lock(&priv->rx_lock);
|
2017-10-17 08:28:57 +08:00
|
|
|
bcm_enet_refill_rx(dev);
|
2009-08-18 20:23:40 +08:00
|
|
|
spin_unlock(&priv->rx_lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* extract packet from rx queue
|
|
|
|
*/
|
|
|
|
static int bcm_enet_receive_queue(struct net_device *dev, int budget)
|
|
|
|
{
|
|
|
|
struct bcm_enet_priv *priv;
|
|
|
|
struct device *kdev;
|
|
|
|
int processed;
|
|
|
|
|
|
|
|
priv = netdev_priv(dev);
|
|
|
|
kdev = &priv->pdev->dev;
|
|
|
|
processed = 0;
|
|
|
|
|
|
|
|
/* don't scan ring further than number of refilled
|
|
|
|
* descriptor */
|
|
|
|
if (budget > priv->rx_desc_count)
|
|
|
|
budget = priv->rx_desc_count;
|
|
|
|
|
|
|
|
do {
|
|
|
|
struct bcm_enet_desc *desc;
|
|
|
|
struct sk_buff *skb;
|
|
|
|
int desc_idx;
|
|
|
|
u32 len_stat;
|
|
|
|
unsigned int len;
|
|
|
|
|
|
|
|
desc_idx = priv->rx_curr_desc;
|
|
|
|
desc = &priv->rx_desc_cpu[desc_idx];
|
|
|
|
|
|
|
|
/* make sure we actually read the descriptor status at
|
|
|
|
* each loop */
|
|
|
|
rmb();
|
|
|
|
|
|
|
|
len_stat = desc->len_stat;
|
|
|
|
|
|
|
|
/* break if dma ownership belongs to hw */
|
|
|
|
if (len_stat & DMADESC_OWNER_MASK)
|
|
|
|
break;
|
|
|
|
|
|
|
|
processed++;
|
|
|
|
priv->rx_curr_desc++;
|
|
|
|
if (priv->rx_curr_desc == priv->rx_ring_size)
|
|
|
|
priv->rx_curr_desc = 0;
|
|
|
|
priv->rx_desc_count--;
|
|
|
|
|
|
|
|
/* if the packet does not have start of packet _and_
|
|
|
|
* end of packet flag set, then just recycle it */
|
2013-06-13 03:53:05 +08:00
|
|
|
if ((len_stat & (DMADESC_ESOP_MASK >> priv->dma_desc_shift)) !=
|
|
|
|
(DMADESC_ESOP_MASK >> priv->dma_desc_shift)) {
|
2010-08-25 03:24:07 +08:00
|
|
|
dev->stats.rx_dropped++;
|
2009-08-18 20:23:40 +08:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* recycle packet if it's marked as bad */
|
2013-06-05 05:53:35 +08:00
|
|
|
if (!priv->enet_is_sw &&
|
|
|
|
unlikely(len_stat & DMADESC_ERR_MASK)) {
|
2010-08-25 03:24:07 +08:00
|
|
|
dev->stats.rx_errors++;
|
2009-08-18 20:23:40 +08:00
|
|
|
|
|
|
|
if (len_stat & DMADESC_OVSIZE_MASK)
|
2010-08-25 03:24:07 +08:00
|
|
|
dev->stats.rx_length_errors++;
|
2009-08-18 20:23:40 +08:00
|
|
|
if (len_stat & DMADESC_CRC_MASK)
|
2010-08-25 03:24:07 +08:00
|
|
|
dev->stats.rx_crc_errors++;
|
2009-08-18 20:23:40 +08:00
|
|
|
if (len_stat & DMADESC_UNDER_MASK)
|
2010-08-25 03:24:07 +08:00
|
|
|
dev->stats.rx_frame_errors++;
|
2009-08-18 20:23:40 +08:00
|
|
|
if (len_stat & DMADESC_OV_MASK)
|
2010-08-25 03:24:07 +08:00
|
|
|
dev->stats.rx_fifo_errors++;
|
2009-08-18 20:23:40 +08:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* valid packet */
|
|
|
|
skb = priv->rx_skb[desc_idx];
|
|
|
|
len = (len_stat & DMADESC_LENGTH_MASK) >> DMADESC_LENGTH_SHIFT;
|
|
|
|
/* don't include FCS */
|
|
|
|
len -= 4;
|
|
|
|
|
|
|
|
if (len < copybreak) {
|
|
|
|
struct sk_buff *nskb;
|
|
|
|
|
2014-12-10 11:41:17 +08:00
|
|
|
nskb = napi_alloc_skb(&priv->napi, len);
|
2009-08-18 20:23:40 +08:00
|
|
|
if (!nskb) {
|
|
|
|
/* forget packet, just rearm desc */
|
2010-08-25 03:24:07 +08:00
|
|
|
dev->stats.rx_dropped++;
|
2009-08-18 20:23:40 +08:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
dma_sync_single_for_cpu(kdev, desc->address,
|
|
|
|
len, DMA_FROM_DEVICE);
|
|
|
|
memcpy(nskb->data, skb->data, len);
|
|
|
|
dma_sync_single_for_device(kdev, desc->address,
|
|
|
|
len, DMA_FROM_DEVICE);
|
|
|
|
skb = nskb;
|
|
|
|
} else {
|
|
|
|
dma_unmap_single(&priv->pdev->dev, desc->address,
|
|
|
|
priv->rx_skb_size, DMA_FROM_DEVICE);
|
|
|
|
priv->rx_skb[desc_idx] = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
skb_put(skb, len);
|
|
|
|
skb->protocol = eth_type_trans(skb, dev);
|
2010-08-25 03:24:07 +08:00
|
|
|
dev->stats.rx_packets++;
|
|
|
|
dev->stats.rx_bytes += len;
|
2009-08-18 20:23:40 +08:00
|
|
|
netif_receive_skb(skb);
|
|
|
|
|
|
|
|
} while (--budget > 0);
|
|
|
|
|
|
|
|
if (processed || !priv->rx_desc_count) {
|
|
|
|
bcm_enet_refill_rx(dev);
|
|
|
|
|
|
|
|
/* kick rx dma */
|
2013-06-13 03:53:05 +08:00
|
|
|
enet_dmac_writel(priv, priv->dma_chan_en_mask,
|
|
|
|
ENETDMAC_CHANCFG, priv->rx_chan);
|
2009-08-18 20:23:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return processed;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* try to or force reclaim of transmitted buffers
|
|
|
|
*/
|
|
|
|
static int bcm_enet_tx_reclaim(struct net_device *dev, int force)
|
|
|
|
{
|
|
|
|
struct bcm_enet_priv *priv;
|
|
|
|
int released;
|
|
|
|
|
|
|
|
priv = netdev_priv(dev);
|
|
|
|
released = 0;
|
|
|
|
|
|
|
|
while (priv->tx_desc_count < priv->tx_ring_size) {
|
|
|
|
struct bcm_enet_desc *desc;
|
|
|
|
struct sk_buff *skb;
|
|
|
|
|
|
|
|
/* We run in a bh and fight against start_xmit, which
|
|
|
|
* is called with bh disabled */
|
|
|
|
spin_lock(&priv->tx_lock);
|
|
|
|
|
|
|
|
desc = &priv->tx_desc_cpu[priv->tx_dirty_desc];
|
|
|
|
|
|
|
|
if (!force && (desc->len_stat & DMADESC_OWNER_MASK)) {
|
|
|
|
spin_unlock(&priv->tx_lock);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ensure other field of the descriptor were not read
|
|
|
|
* before we checked ownership */
|
|
|
|
rmb();
|
|
|
|
|
|
|
|
skb = priv->tx_skb[priv->tx_dirty_desc];
|
|
|
|
priv->tx_skb[priv->tx_dirty_desc] = NULL;
|
|
|
|
dma_unmap_single(&priv->pdev->dev, desc->address, skb->len,
|
|
|
|
DMA_TO_DEVICE);
|
|
|
|
|
|
|
|
priv->tx_dirty_desc++;
|
|
|
|
if (priv->tx_dirty_desc == priv->tx_ring_size)
|
|
|
|
priv->tx_dirty_desc = 0;
|
|
|
|
priv->tx_desc_count++;
|
|
|
|
|
|
|
|
spin_unlock(&priv->tx_lock);
|
|
|
|
|
|
|
|
if (desc->len_stat & DMADESC_UNDER_MASK)
|
2010-08-25 03:24:07 +08:00
|
|
|
dev->stats.tx_errors++;
|
2009-08-18 20:23:40 +08:00
|
|
|
|
|
|
|
dev_kfree_skb(skb);
|
|
|
|
released++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (netif_queue_stopped(dev) && released)
|
|
|
|
netif_wake_queue(dev);
|
|
|
|
|
|
|
|
return released;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* poll func, called by network core
|
|
|
|
*/
|
|
|
|
static int bcm_enet_poll(struct napi_struct *napi, int budget)
|
|
|
|
{
|
|
|
|
struct bcm_enet_priv *priv;
|
|
|
|
struct net_device *dev;
|
2015-03-03 19:45:12 +08:00
|
|
|
int rx_work_done;
|
2009-08-18 20:23:40 +08:00
|
|
|
|
|
|
|
priv = container_of(napi, struct bcm_enet_priv, napi);
|
|
|
|
dev = priv->net_dev;
|
|
|
|
|
|
|
|
/* ack interrupts */
|
2013-06-13 03:53:05 +08:00
|
|
|
enet_dmac_writel(priv, priv->dma_chan_int_mask,
|
|
|
|
ENETDMAC_IR, priv->rx_chan);
|
|
|
|
enet_dmac_writel(priv, priv->dma_chan_int_mask,
|
|
|
|
ENETDMAC_IR, priv->tx_chan);
|
2009-08-18 20:23:40 +08:00
|
|
|
|
|
|
|
/* reclaim sent skb */
|
2015-03-03 19:45:12 +08:00
|
|
|
bcm_enet_tx_reclaim(dev, 0);
|
2009-08-18 20:23:40 +08:00
|
|
|
|
|
|
|
spin_lock(&priv->rx_lock);
|
|
|
|
rx_work_done = bcm_enet_receive_queue(dev, budget);
|
|
|
|
spin_unlock(&priv->rx_lock);
|
|
|
|
|
2015-03-03 19:45:12 +08:00
|
|
|
if (rx_work_done >= budget) {
|
|
|
|
/* rx queue is not yet empty/clean */
|
2009-08-18 20:23:40 +08:00
|
|
|
return rx_work_done;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* no more packet in rx/tx queue, remove device from poll
|
|
|
|
* queue */
|
2017-01-31 00:22:01 +08:00
|
|
|
napi_complete_done(napi, rx_work_done);
|
2009-08-18 20:23:40 +08:00
|
|
|
|
|
|
|
/* restore rx/tx interrupt */
|
2013-06-13 03:53:05 +08:00
|
|
|
enet_dmac_writel(priv, priv->dma_chan_int_mask,
|
|
|
|
ENETDMAC_IRMASK, priv->rx_chan);
|
|
|
|
enet_dmac_writel(priv, priv->dma_chan_int_mask,
|
|
|
|
ENETDMAC_IRMASK, priv->tx_chan);
|
2009-08-18 20:23:40 +08:00
|
|
|
|
|
|
|
return rx_work_done;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* mac interrupt handler
|
|
|
|
*/
|
|
|
|
static irqreturn_t bcm_enet_isr_mac(int irq, void *dev_id)
|
|
|
|
{
|
|
|
|
struct net_device *dev;
|
|
|
|
struct bcm_enet_priv *priv;
|
|
|
|
u32 stat;
|
|
|
|
|
|
|
|
dev = dev_id;
|
|
|
|
priv = netdev_priv(dev);
|
|
|
|
|
|
|
|
stat = enet_readl(priv, ENET_IR_REG);
|
|
|
|
if (!(stat & ENET_IR_MIB))
|
|
|
|
return IRQ_NONE;
|
|
|
|
|
|
|
|
/* clear & mask interrupt */
|
|
|
|
enet_writel(priv, ENET_IR_MIB, ENET_IR_REG);
|
|
|
|
enet_writel(priv, 0, ENET_IRMASK_REG);
|
|
|
|
|
|
|
|
/* read mib registers in workqueue */
|
|
|
|
schedule_work(&priv->mib_update_task);
|
|
|
|
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* rx/tx dma interrupt handler
|
|
|
|
*/
|
|
|
|
static irqreturn_t bcm_enet_isr_dma(int irq, void *dev_id)
|
|
|
|
{
|
|
|
|
struct net_device *dev;
|
|
|
|
struct bcm_enet_priv *priv;
|
|
|
|
|
|
|
|
dev = dev_id;
|
|
|
|
priv = netdev_priv(dev);
|
|
|
|
|
|
|
|
/* mask rx/tx interrupts */
|
2013-06-13 03:53:05 +08:00
|
|
|
enet_dmac_writel(priv, 0, ENETDMAC_IRMASK, priv->rx_chan);
|
|
|
|
enet_dmac_writel(priv, 0, ENETDMAC_IRMASK, priv->tx_chan);
|
2009-08-18 20:23:40 +08:00
|
|
|
|
|
|
|
napi_schedule(&priv->napi);
|
|
|
|
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* tx request callback
|
|
|
|
*/
|
|
|
|
static int bcm_enet_start_xmit(struct sk_buff *skb, struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct bcm_enet_priv *priv;
|
|
|
|
struct bcm_enet_desc *desc;
|
|
|
|
u32 len_stat;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
priv = netdev_priv(dev);
|
|
|
|
|
|
|
|
/* lock against tx reclaim */
|
|
|
|
spin_lock(&priv->tx_lock);
|
|
|
|
|
|
|
|
/* make sure the tx hw queue is not full, should not happen
|
|
|
|
* since we stop queue before it's the case */
|
|
|
|
if (unlikely(!priv->tx_desc_count)) {
|
|
|
|
netif_stop_queue(dev);
|
|
|
|
dev_err(&priv->pdev->dev, "xmit called with no tx desc "
|
|
|
|
"available?\n");
|
|
|
|
ret = NETDEV_TX_BUSY;
|
|
|
|
goto out_unlock;
|
|
|
|
}
|
|
|
|
|
2013-06-05 05:53:35 +08:00
|
|
|
/* pad small packets sent on a switch device */
|
|
|
|
if (priv->enet_is_sw && skb->len < 64) {
|
|
|
|
int needed = 64 - skb->len;
|
|
|
|
char *data;
|
|
|
|
|
|
|
|
if (unlikely(skb_tailroom(skb) < needed)) {
|
|
|
|
struct sk_buff *nskb;
|
|
|
|
|
|
|
|
nskb = skb_copy_expand(skb, 0, needed, GFP_ATOMIC);
|
|
|
|
if (!nskb) {
|
|
|
|
ret = NETDEV_TX_BUSY;
|
|
|
|
goto out_unlock;
|
|
|
|
}
|
|
|
|
dev_kfree_skb(skb);
|
|
|
|
skb = nskb;
|
|
|
|
}
|
2017-06-13 20:28:18 +08:00
|
|
|
data = skb_put_zero(skb, needed);
|
2013-06-05 05:53:35 +08:00
|
|
|
}
|
|
|
|
|
2009-08-18 20:23:40 +08:00
|
|
|
/* point to the next available desc */
|
|
|
|
desc = &priv->tx_desc_cpu[priv->tx_curr_desc];
|
|
|
|
priv->tx_skb[priv->tx_curr_desc] = skb;
|
|
|
|
|
|
|
|
/* fill descriptor */
|
|
|
|
desc->address = dma_map_single(&priv->pdev->dev, skb->data, skb->len,
|
|
|
|
DMA_TO_DEVICE);
|
|
|
|
|
|
|
|
len_stat = (skb->len << DMADESC_LENGTH_SHIFT) & DMADESC_LENGTH_MASK;
|
2013-06-13 03:53:05 +08:00
|
|
|
len_stat |= (DMADESC_ESOP_MASK >> priv->dma_desc_shift) |
|
2009-08-18 20:23:40 +08:00
|
|
|
DMADESC_APPEND_CRC |
|
|
|
|
DMADESC_OWNER_MASK;
|
|
|
|
|
|
|
|
priv->tx_curr_desc++;
|
|
|
|
if (priv->tx_curr_desc == priv->tx_ring_size) {
|
|
|
|
priv->tx_curr_desc = 0;
|
2013-06-13 03:53:05 +08:00
|
|
|
len_stat |= (DMADESC_WRAP_MASK >> priv->dma_desc_shift);
|
2009-08-18 20:23:40 +08:00
|
|
|
}
|
|
|
|
priv->tx_desc_count--;
|
|
|
|
|
|
|
|
/* dma might be already polling, make sure we update desc
|
|
|
|
* fields in correct order */
|
|
|
|
wmb();
|
|
|
|
desc->len_stat = len_stat;
|
|
|
|
wmb();
|
|
|
|
|
|
|
|
/* kick tx dma */
|
2013-06-13 03:53:05 +08:00
|
|
|
enet_dmac_writel(priv, priv->dma_chan_en_mask,
|
|
|
|
ENETDMAC_CHANCFG, priv->tx_chan);
|
2009-08-18 20:23:40 +08:00
|
|
|
|
|
|
|
/* stop queue if no more desc available */
|
|
|
|
if (!priv->tx_desc_count)
|
|
|
|
netif_stop_queue(dev);
|
|
|
|
|
2010-08-25 03:24:07 +08:00
|
|
|
dev->stats.tx_bytes += skb->len;
|
|
|
|
dev->stats.tx_packets++;
|
2009-08-18 20:23:40 +08:00
|
|
|
ret = NETDEV_TX_OK;
|
|
|
|
|
|
|
|
out_unlock:
|
|
|
|
spin_unlock(&priv->tx_lock);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Change the interface's mac address.
|
|
|
|
*/
|
|
|
|
static int bcm_enet_set_mac_address(struct net_device *dev, void *p)
|
|
|
|
{
|
|
|
|
struct bcm_enet_priv *priv;
|
|
|
|
struct sockaddr *addr = p;
|
|
|
|
u32 val;
|
|
|
|
|
|
|
|
priv = netdev_priv(dev);
|
|
|
|
memcpy(dev->dev_addr, addr->sa_data, ETH_ALEN);
|
|
|
|
|
|
|
|
/* use perfect match register 0 to store my mac address */
|
|
|
|
val = (dev->dev_addr[2] << 24) | (dev->dev_addr[3] << 16) |
|
|
|
|
(dev->dev_addr[4] << 8) | dev->dev_addr[5];
|
|
|
|
enet_writel(priv, val, ENET_PML_REG(0));
|
|
|
|
|
|
|
|
val = (dev->dev_addr[0] << 8 | dev->dev_addr[1]);
|
|
|
|
val |= ENET_PMH_DATAVALID_MASK;
|
|
|
|
enet_writel(priv, val, ENET_PMH_REG(0));
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2011-03-31 09:57:33 +08:00
|
|
|
* Change rx mode (promiscuous/allmulti) and update multicast list
|
2009-08-18 20:23:40 +08:00
|
|
|
*/
|
|
|
|
static void bcm_enet_set_multicast_list(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct bcm_enet_priv *priv;
|
2010-04-02 05:22:57 +08:00
|
|
|
struct netdev_hw_addr *ha;
|
2009-08-18 20:23:40 +08:00
|
|
|
u32 val;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
priv = netdev_priv(dev);
|
|
|
|
|
|
|
|
val = enet_readl(priv, ENET_RXCFG_REG);
|
|
|
|
|
|
|
|
if (dev->flags & IFF_PROMISC)
|
|
|
|
val |= ENET_RXCFG_PROMISC_MASK;
|
|
|
|
else
|
|
|
|
val &= ~ENET_RXCFG_PROMISC_MASK;
|
|
|
|
|
|
|
|
/* only 3 perfect match registers left, first one is used for
|
|
|
|
* own mac address */
|
2010-02-08 12:30:35 +08:00
|
|
|
if ((dev->flags & IFF_ALLMULTI) || netdev_mc_count(dev) > 3)
|
2009-08-18 20:23:40 +08:00
|
|
|
val |= ENET_RXCFG_ALLMCAST_MASK;
|
|
|
|
else
|
|
|
|
val &= ~ENET_RXCFG_ALLMCAST_MASK;
|
|
|
|
|
|
|
|
/* no need to set perfect match registers if we catch all
|
|
|
|
* multicast */
|
|
|
|
if (val & ENET_RXCFG_ALLMCAST_MASK) {
|
|
|
|
enet_writel(priv, val, ENET_RXCFG_REG);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2010-02-20 08:13:58 +08:00
|
|
|
i = 0;
|
2010-04-02 05:22:57 +08:00
|
|
|
netdev_for_each_mc_addr(ha, dev) {
|
2009-08-18 20:23:40 +08:00
|
|
|
u8 *dmi_addr;
|
|
|
|
u32 tmp;
|
|
|
|
|
2010-02-20 08:13:58 +08:00
|
|
|
if (i == 3)
|
|
|
|
break;
|
2009-08-18 20:23:40 +08:00
|
|
|
/* update perfect match registers */
|
2010-04-02 05:22:57 +08:00
|
|
|
dmi_addr = ha->addr;
|
2009-08-18 20:23:40 +08:00
|
|
|
tmp = (dmi_addr[2] << 24) | (dmi_addr[3] << 16) |
|
|
|
|
(dmi_addr[4] << 8) | dmi_addr[5];
|
|
|
|
enet_writel(priv, tmp, ENET_PML_REG(i + 1));
|
|
|
|
|
|
|
|
tmp = (dmi_addr[0] << 8 | dmi_addr[1]);
|
|
|
|
tmp |= ENET_PMH_DATAVALID_MASK;
|
2010-02-20 08:13:58 +08:00
|
|
|
enet_writel(priv, tmp, ENET_PMH_REG(i++ + 1));
|
2009-08-18 20:23:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
for (; i < 3; i++) {
|
|
|
|
enet_writel(priv, 0, ENET_PML_REG(i + 1));
|
|
|
|
enet_writel(priv, 0, ENET_PMH_REG(i + 1));
|
|
|
|
}
|
|
|
|
|
|
|
|
enet_writel(priv, val, ENET_RXCFG_REG);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* set mac duplex parameters
|
|
|
|
*/
|
|
|
|
static void bcm_enet_set_duplex(struct bcm_enet_priv *priv, int fullduplex)
|
|
|
|
{
|
|
|
|
u32 val;
|
|
|
|
|
|
|
|
val = enet_readl(priv, ENET_TXCTL_REG);
|
|
|
|
if (fullduplex)
|
|
|
|
val |= ENET_TXCTL_FD_MASK;
|
|
|
|
else
|
|
|
|
val &= ~ENET_TXCTL_FD_MASK;
|
|
|
|
enet_writel(priv, val, ENET_TXCTL_REG);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* set mac flow control parameters
|
|
|
|
*/
|
|
|
|
static void bcm_enet_set_flow(struct bcm_enet_priv *priv, int rx_en, int tx_en)
|
|
|
|
{
|
|
|
|
u32 val;
|
|
|
|
|
|
|
|
/* rx flow control (pause frame handling) */
|
|
|
|
val = enet_readl(priv, ENET_RXCFG_REG);
|
|
|
|
if (rx_en)
|
|
|
|
val |= ENET_RXCFG_ENFLOW_MASK;
|
|
|
|
else
|
|
|
|
val &= ~ENET_RXCFG_ENFLOW_MASK;
|
|
|
|
enet_writel(priv, val, ENET_RXCFG_REG);
|
|
|
|
|
2013-06-13 03:53:05 +08:00
|
|
|
if (!priv->dma_has_sram)
|
|
|
|
return;
|
|
|
|
|
2009-08-18 20:23:40 +08:00
|
|
|
/* tx flow control (pause frame generation) */
|
|
|
|
val = enet_dma_readl(priv, ENETDMA_CFG_REG);
|
|
|
|
if (tx_en)
|
|
|
|
val |= ENETDMA_CFG_FLOWCH_MASK(priv->rx_chan);
|
|
|
|
else
|
|
|
|
val &= ~ENETDMA_CFG_FLOWCH_MASK(priv->rx_chan);
|
|
|
|
enet_dma_writel(priv, val, ENETDMA_CFG_REG);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* link changed callback (from phylib)
|
|
|
|
*/
|
|
|
|
static void bcm_enet_adjust_phy_link(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct bcm_enet_priv *priv;
|
|
|
|
struct phy_device *phydev;
|
|
|
|
int status_changed;
|
|
|
|
|
|
|
|
priv = netdev_priv(dev);
|
2016-09-18 22:59:06 +08:00
|
|
|
phydev = dev->phydev;
|
2009-08-18 20:23:40 +08:00
|
|
|
status_changed = 0;
|
|
|
|
|
|
|
|
if (priv->old_link != phydev->link) {
|
|
|
|
status_changed = 1;
|
|
|
|
priv->old_link = phydev->link;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* reflect duplex change in mac configuration */
|
|
|
|
if (phydev->link && phydev->duplex != priv->old_duplex) {
|
|
|
|
bcm_enet_set_duplex(priv,
|
|
|
|
(phydev->duplex == DUPLEX_FULL) ? 1 : 0);
|
|
|
|
status_changed = 1;
|
|
|
|
priv->old_duplex = phydev->duplex;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* enable flow control if remote advertise it (trust phylib to
|
|
|
|
* check that duplex is full */
|
|
|
|
if (phydev->link && phydev->pause != priv->old_pause) {
|
|
|
|
int rx_pause_en, tx_pause_en;
|
|
|
|
|
|
|
|
if (phydev->pause) {
|
|
|
|
/* pause was advertised by lpa and us */
|
|
|
|
rx_pause_en = 1;
|
|
|
|
tx_pause_en = 1;
|
|
|
|
} else if (!priv->pause_auto) {
|
2017-02-28 06:29:28 +08:00
|
|
|
/* pause setting overridden by user */
|
2009-08-18 20:23:40 +08:00
|
|
|
rx_pause_en = priv->pause_rx;
|
|
|
|
tx_pause_en = priv->pause_tx;
|
|
|
|
} else {
|
|
|
|
rx_pause_en = 0;
|
|
|
|
tx_pause_en = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
bcm_enet_set_flow(priv, rx_pause_en, tx_pause_en);
|
|
|
|
status_changed = 1;
|
|
|
|
priv->old_pause = phydev->pause;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (status_changed) {
|
|
|
|
pr_info("%s: link %s", dev->name, phydev->link ?
|
|
|
|
"UP" : "DOWN");
|
|
|
|
if (phydev->link)
|
|
|
|
pr_cont(" - %d/%s - flow control %s", phydev->speed,
|
|
|
|
DUPLEX_FULL == phydev->duplex ? "full" : "half",
|
|
|
|
phydev->pause == 1 ? "rx&tx" : "off");
|
|
|
|
|
|
|
|
pr_cont("\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* link changed callback (if phylib is not used)
|
|
|
|
*/
|
|
|
|
static void bcm_enet_adjust_link(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct bcm_enet_priv *priv;
|
|
|
|
|
|
|
|
priv = netdev_priv(dev);
|
|
|
|
bcm_enet_set_duplex(priv, priv->force_duplex_full);
|
|
|
|
bcm_enet_set_flow(priv, priv->pause_rx, priv->pause_tx);
|
|
|
|
netif_carrier_on(dev);
|
|
|
|
|
|
|
|
pr_info("%s: link forced UP - %d/%s - flow control %s/%s\n",
|
|
|
|
dev->name,
|
|
|
|
priv->force_speed_100 ? 100 : 10,
|
|
|
|
priv->force_duplex_full ? "full" : "half",
|
|
|
|
priv->pause_rx ? "rx" : "off",
|
|
|
|
priv->pause_tx ? "tx" : "off");
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* open callback, allocate dma rings & buffers and start rx operation
|
|
|
|
*/
|
|
|
|
static int bcm_enet_open(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct bcm_enet_priv *priv;
|
|
|
|
struct sockaddr addr;
|
|
|
|
struct device *kdev;
|
|
|
|
struct phy_device *phydev;
|
|
|
|
int i, ret;
|
|
|
|
unsigned int size;
|
|
|
|
char phy_id[MII_BUS_ID_SIZE + 3];
|
|
|
|
void *p;
|
|
|
|
u32 val;
|
|
|
|
|
|
|
|
priv = netdev_priv(dev);
|
|
|
|
kdev = &priv->pdev->dev;
|
|
|
|
|
|
|
|
if (priv->has_phy) {
|
|
|
|
/* connect to PHY */
|
|
|
|
snprintf(phy_id, sizeof(phy_id), PHY_ID_FMT,
|
2012-02-13 09:23:21 +08:00
|
|
|
priv->mii_bus->id, priv->phy_id);
|
2009-08-18 20:23:40 +08:00
|
|
|
|
2013-01-14 08:52:52 +08:00
|
|
|
phydev = phy_connect(dev, phy_id, bcm_enet_adjust_phy_link,
|
2009-08-18 20:23:40 +08:00
|
|
|
PHY_INTERFACE_MODE_MII);
|
|
|
|
|
|
|
|
if (IS_ERR(phydev)) {
|
|
|
|
dev_err(kdev, "could not attach to PHY\n");
|
|
|
|
return PTR_ERR(phydev);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* mask with MAC supported features */
|
|
|
|
phydev->supported &= (SUPPORTED_10baseT_Half |
|
|
|
|
SUPPORTED_10baseT_Full |
|
|
|
|
SUPPORTED_100baseT_Half |
|
|
|
|
SUPPORTED_100baseT_Full |
|
|
|
|
SUPPORTED_Autoneg |
|
|
|
|
SUPPORTED_Pause |
|
|
|
|
SUPPORTED_MII);
|
|
|
|
phydev->advertising = phydev->supported;
|
|
|
|
|
|
|
|
if (priv->pause_auto && priv->pause_rx && priv->pause_tx)
|
|
|
|
phydev->advertising |= SUPPORTED_Pause;
|
|
|
|
else
|
|
|
|
phydev->advertising &= ~SUPPORTED_Pause;
|
|
|
|
|
2016-01-07 03:11:13 +08:00
|
|
|
phy_attached_info(phydev);
|
2009-08-18 20:23:40 +08:00
|
|
|
|
|
|
|
priv->old_link = 0;
|
|
|
|
priv->old_duplex = -1;
|
|
|
|
priv->old_pause = -1;
|
2017-01-18 22:52:53 +08:00
|
|
|
} else {
|
|
|
|
phydev = NULL;
|
2009-08-18 20:23:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* mask all interrupts and request them */
|
|
|
|
enet_writel(priv, 0, ENET_IRMASK_REG);
|
2013-06-13 03:53:05 +08:00
|
|
|
enet_dmac_writel(priv, 0, ENETDMAC_IRMASK, priv->rx_chan);
|
|
|
|
enet_dmac_writel(priv, 0, ENETDMAC_IRMASK, priv->tx_chan);
|
2009-08-18 20:23:40 +08:00
|
|
|
|
|
|
|
ret = request_irq(dev->irq, bcm_enet_isr_mac, 0, dev->name, dev);
|
|
|
|
if (ret)
|
|
|
|
goto out_phy_disconnect;
|
|
|
|
|
2013-09-07 14:56:50 +08:00
|
|
|
ret = request_irq(priv->irq_rx, bcm_enet_isr_dma, 0,
|
2011-03-29 00:27:31 +08:00
|
|
|
dev->name, dev);
|
2009-08-18 20:23:40 +08:00
|
|
|
if (ret)
|
|
|
|
goto out_freeirq;
|
|
|
|
|
|
|
|
ret = request_irq(priv->irq_tx, bcm_enet_isr_dma,
|
2013-09-07 14:56:50 +08:00
|
|
|
0, dev->name, dev);
|
2009-08-18 20:23:40 +08:00
|
|
|
if (ret)
|
|
|
|
goto out_freeirq_rx;
|
|
|
|
|
|
|
|
/* initialize perfect match registers */
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
|
|
enet_writel(priv, 0, ENET_PML_REG(i));
|
|
|
|
enet_writel(priv, 0, ENET_PMH_REG(i));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* write device mac address */
|
|
|
|
memcpy(addr.sa_data, dev->dev_addr, ETH_ALEN);
|
|
|
|
bcm_enet_set_mac_address(dev, &addr);
|
|
|
|
|
|
|
|
/* allocate rx dma ring */
|
|
|
|
size = priv->rx_ring_size * sizeof(struct bcm_enet_desc);
|
2013-08-27 13:45:23 +08:00
|
|
|
p = dma_zalloc_coherent(kdev, size, &priv->rx_desc_dma, GFP_KERNEL);
|
2009-08-18 20:23:40 +08:00
|
|
|
if (!p) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto out_freeirq_tx;
|
|
|
|
}
|
|
|
|
|
|
|
|
priv->rx_desc_alloc_size = size;
|
|
|
|
priv->rx_desc_cpu = p;
|
|
|
|
|
|
|
|
/* allocate tx dma ring */
|
|
|
|
size = priv->tx_ring_size * sizeof(struct bcm_enet_desc);
|
2013-08-27 13:45:23 +08:00
|
|
|
p = dma_zalloc_coherent(kdev, size, &priv->tx_desc_dma, GFP_KERNEL);
|
2009-08-18 20:23:40 +08:00
|
|
|
if (!p) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto out_free_rx_ring;
|
|
|
|
}
|
|
|
|
|
|
|
|
priv->tx_desc_alloc_size = size;
|
|
|
|
priv->tx_desc_cpu = p;
|
|
|
|
|
2013-02-04 01:43:58 +08:00
|
|
|
priv->tx_skb = kcalloc(priv->tx_ring_size, sizeof(struct sk_buff *),
|
2009-08-18 20:23:40 +08:00
|
|
|
GFP_KERNEL);
|
|
|
|
if (!priv->tx_skb) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto out_free_tx_ring;
|
|
|
|
}
|
|
|
|
|
|
|
|
priv->tx_desc_count = priv->tx_ring_size;
|
|
|
|
priv->tx_dirty_desc = 0;
|
|
|
|
priv->tx_curr_desc = 0;
|
|
|
|
spin_lock_init(&priv->tx_lock);
|
|
|
|
|
|
|
|
/* init & fill rx ring with skbs */
|
2013-02-04 01:43:58 +08:00
|
|
|
priv->rx_skb = kcalloc(priv->rx_ring_size, sizeof(struct sk_buff *),
|
2009-08-18 20:23:40 +08:00
|
|
|
GFP_KERNEL);
|
|
|
|
if (!priv->rx_skb) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto out_free_tx_skb;
|
|
|
|
}
|
|
|
|
|
|
|
|
priv->rx_desc_count = 0;
|
|
|
|
priv->rx_dirty_desc = 0;
|
|
|
|
priv->rx_curr_desc = 0;
|
|
|
|
|
|
|
|
/* initialize flow control buffer allocation */
|
2013-06-13 03:53:05 +08:00
|
|
|
if (priv->dma_has_sram)
|
|
|
|
enet_dma_writel(priv, ENETDMA_BUFALLOC_FORCE_MASK | 0,
|
|
|
|
ENETDMA_BUFALLOC_REG(priv->rx_chan));
|
|
|
|
else
|
|
|
|
enet_dmac_writel(priv, ENETDMA_BUFALLOC_FORCE_MASK | 0,
|
|
|
|
ENETDMAC_BUFALLOC, priv->rx_chan);
|
2009-08-18 20:23:40 +08:00
|
|
|
|
|
|
|
if (bcm_enet_refill_rx(dev)) {
|
|
|
|
dev_err(kdev, "cannot allocate rx skb queue\n");
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* write rx & tx ring addresses */
|
2013-06-13 03:53:05 +08:00
|
|
|
if (priv->dma_has_sram) {
|
|
|
|
enet_dmas_writel(priv, priv->rx_desc_dma,
|
|
|
|
ENETDMAS_RSTART_REG, priv->rx_chan);
|
|
|
|
enet_dmas_writel(priv, priv->tx_desc_dma,
|
|
|
|
ENETDMAS_RSTART_REG, priv->tx_chan);
|
|
|
|
} else {
|
|
|
|
enet_dmac_writel(priv, priv->rx_desc_dma,
|
|
|
|
ENETDMAC_RSTART, priv->rx_chan);
|
|
|
|
enet_dmac_writel(priv, priv->tx_desc_dma,
|
|
|
|
ENETDMAC_RSTART, priv->tx_chan);
|
|
|
|
}
|
2009-08-18 20:23:40 +08:00
|
|
|
|
|
|
|
/* clear remaining state ram for rx & tx channel */
|
2013-06-13 03:53:05 +08:00
|
|
|
if (priv->dma_has_sram) {
|
|
|
|
enet_dmas_writel(priv, 0, ENETDMAS_SRAM2_REG, priv->rx_chan);
|
|
|
|
enet_dmas_writel(priv, 0, ENETDMAS_SRAM2_REG, priv->tx_chan);
|
|
|
|
enet_dmas_writel(priv, 0, ENETDMAS_SRAM3_REG, priv->rx_chan);
|
|
|
|
enet_dmas_writel(priv, 0, ENETDMAS_SRAM3_REG, priv->tx_chan);
|
|
|
|
enet_dmas_writel(priv, 0, ENETDMAS_SRAM4_REG, priv->rx_chan);
|
|
|
|
enet_dmas_writel(priv, 0, ENETDMAS_SRAM4_REG, priv->tx_chan);
|
|
|
|
} else {
|
|
|
|
enet_dmac_writel(priv, 0, ENETDMAC_FC, priv->rx_chan);
|
|
|
|
enet_dmac_writel(priv, 0, ENETDMAC_FC, priv->tx_chan);
|
|
|
|
}
|
2009-08-18 20:23:40 +08:00
|
|
|
|
|
|
|
/* set max rx/tx length */
|
|
|
|
enet_writel(priv, priv->hw_mtu, ENET_RXMAXLEN_REG);
|
|
|
|
enet_writel(priv, priv->hw_mtu, ENET_TXMAXLEN_REG);
|
|
|
|
|
|
|
|
/* set dma maximum burst len */
|
2013-06-05 05:53:35 +08:00
|
|
|
enet_dmac_writel(priv, priv->dma_maxburst,
|
2013-06-13 03:53:05 +08:00
|
|
|
ENETDMAC_MAXBURST, priv->rx_chan);
|
2013-06-05 05:53:35 +08:00
|
|
|
enet_dmac_writel(priv, priv->dma_maxburst,
|
2013-06-13 03:53:05 +08:00
|
|
|
ENETDMAC_MAXBURST, priv->tx_chan);
|
2009-08-18 20:23:40 +08:00
|
|
|
|
|
|
|
/* set correct transmit fifo watermark */
|
|
|
|
enet_writel(priv, BCMENET_TX_FIFO_TRESH, ENET_TXWMARK_REG);
|
|
|
|
|
|
|
|
/* set flow control low/high threshold to 1/3 / 2/3 */
|
2013-06-13 03:53:05 +08:00
|
|
|
if (priv->dma_has_sram) {
|
|
|
|
val = priv->rx_ring_size / 3;
|
|
|
|
enet_dma_writel(priv, val, ENETDMA_FLOWCL_REG(priv->rx_chan));
|
|
|
|
val = (priv->rx_ring_size * 2) / 3;
|
|
|
|
enet_dma_writel(priv, val, ENETDMA_FLOWCH_REG(priv->rx_chan));
|
|
|
|
} else {
|
|
|
|
enet_dmac_writel(priv, 5, ENETDMAC_FC, priv->rx_chan);
|
|
|
|
enet_dmac_writel(priv, priv->rx_ring_size, ENETDMAC_LEN, priv->rx_chan);
|
|
|
|
enet_dmac_writel(priv, priv->tx_ring_size, ENETDMAC_LEN, priv->tx_chan);
|
|
|
|
}
|
2009-08-18 20:23:40 +08:00
|
|
|
|
|
|
|
/* all set, enable mac and interrupts, start dma engine and
|
|
|
|
* kick rx dma channel */
|
|
|
|
wmb();
|
2010-04-09 09:04:52 +08:00
|
|
|
val = enet_readl(priv, ENET_CTL_REG);
|
|
|
|
val |= ENET_CTL_ENABLE_MASK;
|
|
|
|
enet_writel(priv, val, ENET_CTL_REG);
|
2017-10-01 19:02:16 +08:00
|
|
|
if (priv->dma_has_sram)
|
|
|
|
enet_dma_writel(priv, ENETDMA_CFG_EN_MASK, ENETDMA_CFG_REG);
|
2013-06-13 03:53:05 +08:00
|
|
|
enet_dmac_writel(priv, priv->dma_chan_en_mask,
|
|
|
|
ENETDMAC_CHANCFG, priv->rx_chan);
|
2009-08-18 20:23:40 +08:00
|
|
|
|
|
|
|
/* watch "mib counters about to overflow" interrupt */
|
|
|
|
enet_writel(priv, ENET_IR_MIB, ENET_IR_REG);
|
|
|
|
enet_writel(priv, ENET_IR_MIB, ENET_IRMASK_REG);
|
|
|
|
|
|
|
|
/* watch "packet transferred" interrupt in rx and tx */
|
2013-06-13 03:53:05 +08:00
|
|
|
enet_dmac_writel(priv, priv->dma_chan_int_mask,
|
|
|
|
ENETDMAC_IR, priv->rx_chan);
|
|
|
|
enet_dmac_writel(priv, priv->dma_chan_int_mask,
|
|
|
|
ENETDMAC_IR, priv->tx_chan);
|
2009-08-18 20:23:40 +08:00
|
|
|
|
|
|
|
/* make sure we enable napi before rx interrupt */
|
|
|
|
napi_enable(&priv->napi);
|
|
|
|
|
2013-06-13 03:53:05 +08:00
|
|
|
enet_dmac_writel(priv, priv->dma_chan_int_mask,
|
|
|
|
ENETDMAC_IRMASK, priv->rx_chan);
|
|
|
|
enet_dmac_writel(priv, priv->dma_chan_int_mask,
|
|
|
|
ENETDMAC_IRMASK, priv->tx_chan);
|
2009-08-18 20:23:40 +08:00
|
|
|
|
2017-01-18 22:52:53 +08:00
|
|
|
if (phydev)
|
2016-09-18 22:59:06 +08:00
|
|
|
phy_start(phydev);
|
2009-08-18 20:23:40 +08:00
|
|
|
else
|
|
|
|
bcm_enet_adjust_link(dev);
|
|
|
|
|
|
|
|
netif_start_queue(dev);
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
out:
|
|
|
|
for (i = 0; i < priv->rx_ring_size; i++) {
|
|
|
|
struct bcm_enet_desc *desc;
|
|
|
|
|
|
|
|
if (!priv->rx_skb[i])
|
|
|
|
continue;
|
|
|
|
|
|
|
|
desc = &priv->rx_desc_cpu[i];
|
|
|
|
dma_unmap_single(kdev, desc->address, priv->rx_skb_size,
|
|
|
|
DMA_FROM_DEVICE);
|
|
|
|
kfree_skb(priv->rx_skb[i]);
|
|
|
|
}
|
|
|
|
kfree(priv->rx_skb);
|
|
|
|
|
|
|
|
out_free_tx_skb:
|
|
|
|
kfree(priv->tx_skb);
|
|
|
|
|
|
|
|
out_free_tx_ring:
|
|
|
|
dma_free_coherent(kdev, priv->tx_desc_alloc_size,
|
|
|
|
priv->tx_desc_cpu, priv->tx_desc_dma);
|
|
|
|
|
|
|
|
out_free_rx_ring:
|
|
|
|
dma_free_coherent(kdev, priv->rx_desc_alloc_size,
|
|
|
|
priv->rx_desc_cpu, priv->rx_desc_dma);
|
|
|
|
|
|
|
|
out_freeirq_tx:
|
|
|
|
free_irq(priv->irq_tx, dev);
|
|
|
|
|
|
|
|
out_freeirq_rx:
|
|
|
|
free_irq(priv->irq_rx, dev);
|
|
|
|
|
|
|
|
out_freeirq:
|
|
|
|
free_irq(dev->irq, dev);
|
|
|
|
|
|
|
|
out_phy_disconnect:
|
2017-01-18 22:52:53 +08:00
|
|
|
if (phydev)
|
2016-10-18 06:16:08 +08:00
|
|
|
phy_disconnect(phydev);
|
2009-08-18 20:23:40 +08:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* disable mac
|
|
|
|
*/
|
|
|
|
static void bcm_enet_disable_mac(struct bcm_enet_priv *priv)
|
|
|
|
{
|
|
|
|
int limit;
|
|
|
|
u32 val;
|
|
|
|
|
|
|
|
val = enet_readl(priv, ENET_CTL_REG);
|
|
|
|
val |= ENET_CTL_DISABLE_MASK;
|
|
|
|
enet_writel(priv, val, ENET_CTL_REG);
|
|
|
|
|
|
|
|
limit = 1000;
|
|
|
|
do {
|
|
|
|
u32 val;
|
|
|
|
|
|
|
|
val = enet_readl(priv, ENET_CTL_REG);
|
|
|
|
if (!(val & ENET_CTL_DISABLE_MASK))
|
|
|
|
break;
|
|
|
|
udelay(1);
|
|
|
|
} while (limit--);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* disable dma in given channel
|
|
|
|
*/
|
|
|
|
static void bcm_enet_disable_dma(struct bcm_enet_priv *priv, int chan)
|
|
|
|
{
|
|
|
|
int limit;
|
|
|
|
|
2013-06-13 03:53:05 +08:00
|
|
|
enet_dmac_writel(priv, 0, ENETDMAC_CHANCFG, chan);
|
2009-08-18 20:23:40 +08:00
|
|
|
|
|
|
|
limit = 1000;
|
|
|
|
do {
|
|
|
|
u32 val;
|
|
|
|
|
2013-06-13 03:53:05 +08:00
|
|
|
val = enet_dmac_readl(priv, ENETDMAC_CHANCFG, chan);
|
2013-06-05 05:53:34 +08:00
|
|
|
if (!(val & ENETDMAC_CHANCFG_EN_MASK))
|
2009-08-18 20:23:40 +08:00
|
|
|
break;
|
|
|
|
udelay(1);
|
|
|
|
} while (limit--);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* stop callback
|
|
|
|
*/
|
|
|
|
static int bcm_enet_stop(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct bcm_enet_priv *priv;
|
|
|
|
struct device *kdev;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
priv = netdev_priv(dev);
|
|
|
|
kdev = &priv->pdev->dev;
|
|
|
|
|
|
|
|
netif_stop_queue(dev);
|
|
|
|
napi_disable(&priv->napi);
|
|
|
|
if (priv->has_phy)
|
2016-09-18 22:59:06 +08:00
|
|
|
phy_stop(dev->phydev);
|
2009-08-18 20:23:40 +08:00
|
|
|
del_timer_sync(&priv->rx_timeout);
|
|
|
|
|
|
|
|
/* mask all interrupts */
|
|
|
|
enet_writel(priv, 0, ENET_IRMASK_REG);
|
2013-06-13 03:53:05 +08:00
|
|
|
enet_dmac_writel(priv, 0, ENETDMAC_IRMASK, priv->rx_chan);
|
|
|
|
enet_dmac_writel(priv, 0, ENETDMAC_IRMASK, priv->tx_chan);
|
2009-08-18 20:23:40 +08:00
|
|
|
|
|
|
|
/* make sure no mib update is scheduled */
|
2010-12-12 23:45:14 +08:00
|
|
|
cancel_work_sync(&priv->mib_update_task);
|
2009-08-18 20:23:40 +08:00
|
|
|
|
|
|
|
/* disable dma & mac */
|
|
|
|
bcm_enet_disable_dma(priv, priv->tx_chan);
|
|
|
|
bcm_enet_disable_dma(priv, priv->rx_chan);
|
|
|
|
bcm_enet_disable_mac(priv);
|
|
|
|
|
|
|
|
/* force reclaim of all tx buffers */
|
|
|
|
bcm_enet_tx_reclaim(dev, 1);
|
|
|
|
|
|
|
|
/* free the rx skb ring */
|
|
|
|
for (i = 0; i < priv->rx_ring_size; i++) {
|
|
|
|
struct bcm_enet_desc *desc;
|
|
|
|
|
|
|
|
if (!priv->rx_skb[i])
|
|
|
|
continue;
|
|
|
|
|
|
|
|
desc = &priv->rx_desc_cpu[i];
|
|
|
|
dma_unmap_single(kdev, desc->address, priv->rx_skb_size,
|
|
|
|
DMA_FROM_DEVICE);
|
|
|
|
kfree_skb(priv->rx_skb[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* free remaining allocated memory */
|
|
|
|
kfree(priv->rx_skb);
|
|
|
|
kfree(priv->tx_skb);
|
|
|
|
dma_free_coherent(kdev, priv->rx_desc_alloc_size,
|
|
|
|
priv->rx_desc_cpu, priv->rx_desc_dma);
|
|
|
|
dma_free_coherent(kdev, priv->tx_desc_alloc_size,
|
|
|
|
priv->tx_desc_cpu, priv->tx_desc_dma);
|
|
|
|
free_irq(priv->irq_tx, dev);
|
|
|
|
free_irq(priv->irq_rx, dev);
|
|
|
|
free_irq(dev->irq, dev);
|
|
|
|
|
|
|
|
/* release phy */
|
2016-09-18 22:59:06 +08:00
|
|
|
if (priv->has_phy)
|
|
|
|
phy_disconnect(dev->phydev);
|
2009-08-18 20:23:40 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* ethtool callbacks
|
|
|
|
*/
|
|
|
|
struct bcm_enet_stats {
|
|
|
|
char stat_string[ETH_GSTRING_LEN];
|
|
|
|
int sizeof_stat;
|
|
|
|
int stat_offset;
|
|
|
|
int mib_reg;
|
|
|
|
};
|
|
|
|
|
|
|
|
#define GEN_STAT(m) sizeof(((struct bcm_enet_priv *)0)->m), \
|
|
|
|
offsetof(struct bcm_enet_priv, m)
|
2010-08-25 03:24:07 +08:00
|
|
|
#define DEV_STAT(m) sizeof(((struct net_device_stats *)0)->m), \
|
|
|
|
offsetof(struct net_device_stats, m)
|
2009-08-18 20:23:40 +08:00
|
|
|
|
|
|
|
static const struct bcm_enet_stats bcm_enet_gstrings_stats[] = {
|
2010-08-25 03:24:07 +08:00
|
|
|
{ "rx_packets", DEV_STAT(rx_packets), -1 },
|
|
|
|
{ "tx_packets", DEV_STAT(tx_packets), -1 },
|
|
|
|
{ "rx_bytes", DEV_STAT(rx_bytes), -1 },
|
|
|
|
{ "tx_bytes", DEV_STAT(tx_bytes), -1 },
|
|
|
|
{ "rx_errors", DEV_STAT(rx_errors), -1 },
|
|
|
|
{ "tx_errors", DEV_STAT(tx_errors), -1 },
|
|
|
|
{ "rx_dropped", DEV_STAT(rx_dropped), -1 },
|
|
|
|
{ "tx_dropped", DEV_STAT(tx_dropped), -1 },
|
2009-08-18 20:23:40 +08:00
|
|
|
|
|
|
|
{ "rx_good_octets", GEN_STAT(mib.rx_gd_octets), ETH_MIB_RX_GD_OCTETS},
|
|
|
|
{ "rx_good_pkts", GEN_STAT(mib.rx_gd_pkts), ETH_MIB_RX_GD_PKTS },
|
|
|
|
{ "rx_broadcast", GEN_STAT(mib.rx_brdcast), ETH_MIB_RX_BRDCAST },
|
|
|
|
{ "rx_multicast", GEN_STAT(mib.rx_mult), ETH_MIB_RX_MULT },
|
|
|
|
{ "rx_64_octets", GEN_STAT(mib.rx_64), ETH_MIB_RX_64 },
|
|
|
|
{ "rx_65_127_oct", GEN_STAT(mib.rx_65_127), ETH_MIB_RX_65_127 },
|
|
|
|
{ "rx_128_255_oct", GEN_STAT(mib.rx_128_255), ETH_MIB_RX_128_255 },
|
|
|
|
{ "rx_256_511_oct", GEN_STAT(mib.rx_256_511), ETH_MIB_RX_256_511 },
|
|
|
|
{ "rx_512_1023_oct", GEN_STAT(mib.rx_512_1023), ETH_MIB_RX_512_1023 },
|
|
|
|
{ "rx_1024_max_oct", GEN_STAT(mib.rx_1024_max), ETH_MIB_RX_1024_MAX },
|
|
|
|
{ "rx_jabber", GEN_STAT(mib.rx_jab), ETH_MIB_RX_JAB },
|
|
|
|
{ "rx_oversize", GEN_STAT(mib.rx_ovr), ETH_MIB_RX_OVR },
|
|
|
|
{ "rx_fragment", GEN_STAT(mib.rx_frag), ETH_MIB_RX_FRAG },
|
|
|
|
{ "rx_dropped", GEN_STAT(mib.rx_drop), ETH_MIB_RX_DROP },
|
|
|
|
{ "rx_crc_align", GEN_STAT(mib.rx_crc_align), ETH_MIB_RX_CRC_ALIGN },
|
|
|
|
{ "rx_undersize", GEN_STAT(mib.rx_und), ETH_MIB_RX_UND },
|
|
|
|
{ "rx_crc", GEN_STAT(mib.rx_crc), ETH_MIB_RX_CRC },
|
|
|
|
{ "rx_align", GEN_STAT(mib.rx_align), ETH_MIB_RX_ALIGN },
|
|
|
|
{ "rx_symbol_error", GEN_STAT(mib.rx_sym), ETH_MIB_RX_SYM },
|
|
|
|
{ "rx_pause", GEN_STAT(mib.rx_pause), ETH_MIB_RX_PAUSE },
|
|
|
|
{ "rx_control", GEN_STAT(mib.rx_cntrl), ETH_MIB_RX_CNTRL },
|
|
|
|
|
|
|
|
{ "tx_good_octets", GEN_STAT(mib.tx_gd_octets), ETH_MIB_TX_GD_OCTETS },
|
|
|
|
{ "tx_good_pkts", GEN_STAT(mib.tx_gd_pkts), ETH_MIB_TX_GD_PKTS },
|
|
|
|
{ "tx_broadcast", GEN_STAT(mib.tx_brdcast), ETH_MIB_TX_BRDCAST },
|
|
|
|
{ "tx_multicast", GEN_STAT(mib.tx_mult), ETH_MIB_TX_MULT },
|
|
|
|
{ "tx_64_oct", GEN_STAT(mib.tx_64), ETH_MIB_TX_64 },
|
|
|
|
{ "tx_65_127_oct", GEN_STAT(mib.tx_65_127), ETH_MIB_TX_65_127 },
|
|
|
|
{ "tx_128_255_oct", GEN_STAT(mib.tx_128_255), ETH_MIB_TX_128_255 },
|
|
|
|
{ "tx_256_511_oct", GEN_STAT(mib.tx_256_511), ETH_MIB_TX_256_511 },
|
|
|
|
{ "tx_512_1023_oct", GEN_STAT(mib.tx_512_1023), ETH_MIB_TX_512_1023},
|
|
|
|
{ "tx_1024_max_oct", GEN_STAT(mib.tx_1024_max), ETH_MIB_TX_1024_MAX },
|
|
|
|
{ "tx_jabber", GEN_STAT(mib.tx_jab), ETH_MIB_TX_JAB },
|
|
|
|
{ "tx_oversize", GEN_STAT(mib.tx_ovr), ETH_MIB_TX_OVR },
|
|
|
|
{ "tx_fragment", GEN_STAT(mib.tx_frag), ETH_MIB_TX_FRAG },
|
|
|
|
{ "tx_underrun", GEN_STAT(mib.tx_underrun), ETH_MIB_TX_UNDERRUN },
|
|
|
|
{ "tx_collisions", GEN_STAT(mib.tx_col), ETH_MIB_TX_COL },
|
|
|
|
{ "tx_single_collision", GEN_STAT(mib.tx_1_col), ETH_MIB_TX_1_COL },
|
|
|
|
{ "tx_multiple_collision", GEN_STAT(mib.tx_m_col), ETH_MIB_TX_M_COL },
|
|
|
|
{ "tx_excess_collision", GEN_STAT(mib.tx_ex_col), ETH_MIB_TX_EX_COL },
|
|
|
|
{ "tx_late_collision", GEN_STAT(mib.tx_late), ETH_MIB_TX_LATE },
|
|
|
|
{ "tx_deferred", GEN_STAT(mib.tx_def), ETH_MIB_TX_DEF },
|
|
|
|
{ "tx_carrier_sense", GEN_STAT(mib.tx_crs), ETH_MIB_TX_CRS },
|
|
|
|
{ "tx_pause", GEN_STAT(mib.tx_pause), ETH_MIB_TX_PAUSE },
|
|
|
|
|
|
|
|
};
|
|
|
|
|
2014-04-24 01:42:50 +08:00
|
|
|
#define BCM_ENET_STATS_LEN ARRAY_SIZE(bcm_enet_gstrings_stats)
|
2009-08-18 20:23:40 +08:00
|
|
|
|
|
|
|
static const u32 unused_mib_regs[] = {
|
|
|
|
ETH_MIB_TX_ALL_OCTETS,
|
|
|
|
ETH_MIB_TX_ALL_PKTS,
|
|
|
|
ETH_MIB_RX_ALL_OCTETS,
|
|
|
|
ETH_MIB_RX_ALL_PKTS,
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static void bcm_enet_get_drvinfo(struct net_device *netdev,
|
|
|
|
struct ethtool_drvinfo *drvinfo)
|
|
|
|
{
|
2013-01-06 08:44:26 +08:00
|
|
|
strlcpy(drvinfo->driver, bcm_enet_driver_name, sizeof(drvinfo->driver));
|
|
|
|
strlcpy(drvinfo->version, bcm_enet_driver_version,
|
|
|
|
sizeof(drvinfo->version));
|
|
|
|
strlcpy(drvinfo->fw_version, "N/A", sizeof(drvinfo->fw_version));
|
|
|
|
strlcpy(drvinfo->bus_info, "bcm63xx", sizeof(drvinfo->bus_info));
|
2009-08-18 20:23:40 +08:00
|
|
|
}
|
|
|
|
|
2009-12-15 14:45:06 +08:00
|
|
|
static int bcm_enet_get_sset_count(struct net_device *netdev,
|
|
|
|
int string_set)
|
2009-08-18 20:23:40 +08:00
|
|
|
{
|
2009-12-15 14:45:06 +08:00
|
|
|
switch (string_set) {
|
|
|
|
case ETH_SS_STATS:
|
|
|
|
return BCM_ENET_STATS_LEN;
|
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
2009-08-18 20:23:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void bcm_enet_get_strings(struct net_device *netdev,
|
|
|
|
u32 stringset, u8 *data)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
switch (stringset) {
|
|
|
|
case ETH_SS_STATS:
|
|
|
|
for (i = 0; i < BCM_ENET_STATS_LEN; i++) {
|
|
|
|
memcpy(data + i * ETH_GSTRING_LEN,
|
|
|
|
bcm_enet_gstrings_stats[i].stat_string,
|
|
|
|
ETH_GSTRING_LEN);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void update_mib_counters(struct bcm_enet_priv *priv)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < BCM_ENET_STATS_LEN; i++) {
|
|
|
|
const struct bcm_enet_stats *s;
|
|
|
|
u32 val;
|
|
|
|
char *p;
|
|
|
|
|
|
|
|
s = &bcm_enet_gstrings_stats[i];
|
|
|
|
if (s->mib_reg == -1)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
val = enet_readl(priv, ENET_MIB_REG(s->mib_reg));
|
|
|
|
p = (char *)priv + s->stat_offset;
|
|
|
|
|
|
|
|
if (s->sizeof_stat == sizeof(u64))
|
|
|
|
*(u64 *)p += val;
|
|
|
|
else
|
|
|
|
*(u32 *)p += val;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* also empty unused mib counters to make sure mib counter
|
|
|
|
* overflow interrupt is cleared */
|
|
|
|
for (i = 0; i < ARRAY_SIZE(unused_mib_regs); i++)
|
|
|
|
(void)enet_readl(priv, ENET_MIB_REG(unused_mib_regs[i]));
|
|
|
|
}
|
|
|
|
|
|
|
|
static void bcm_enet_update_mib_counters_defer(struct work_struct *t)
|
|
|
|
{
|
|
|
|
struct bcm_enet_priv *priv;
|
|
|
|
|
|
|
|
priv = container_of(t, struct bcm_enet_priv, mib_update_task);
|
|
|
|
mutex_lock(&priv->mib_update_lock);
|
|
|
|
update_mib_counters(priv);
|
|
|
|
mutex_unlock(&priv->mib_update_lock);
|
|
|
|
|
|
|
|
/* reenable mib interrupt */
|
|
|
|
if (netif_running(priv->net_dev))
|
|
|
|
enet_writel(priv, ENET_IR_MIB, ENET_IRMASK_REG);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void bcm_enet_get_ethtool_stats(struct net_device *netdev,
|
|
|
|
struct ethtool_stats *stats,
|
|
|
|
u64 *data)
|
|
|
|
{
|
|
|
|
struct bcm_enet_priv *priv;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
priv = netdev_priv(netdev);
|
|
|
|
|
|
|
|
mutex_lock(&priv->mib_update_lock);
|
|
|
|
update_mib_counters(priv);
|
|
|
|
|
|
|
|
for (i = 0; i < BCM_ENET_STATS_LEN; i++) {
|
|
|
|
const struct bcm_enet_stats *s;
|
|
|
|
char *p;
|
|
|
|
|
|
|
|
s = &bcm_enet_gstrings_stats[i];
|
2010-08-25 03:24:07 +08:00
|
|
|
if (s->mib_reg == -1)
|
|
|
|
p = (char *)&netdev->stats;
|
|
|
|
else
|
|
|
|
p = (char *)priv;
|
|
|
|
p += s->stat_offset;
|
2009-08-18 20:23:40 +08:00
|
|
|
data[i] = (s->sizeof_stat == sizeof(u64)) ?
|
|
|
|
*(u64 *)p : *(u32 *)p;
|
|
|
|
}
|
|
|
|
mutex_unlock(&priv->mib_update_lock);
|
|
|
|
}
|
|
|
|
|
2013-06-05 05:53:33 +08:00
|
|
|
static int bcm_enet_nway_reset(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct bcm_enet_priv *priv;
|
|
|
|
|
|
|
|
priv = netdev_priv(dev);
|
2016-11-16 02:06:32 +08:00
|
|
|
if (priv->has_phy)
|
2016-11-16 10:21:09 +08:00
|
|
|
return phy_ethtool_nway_reset(dev);
|
2013-06-05 05:53:33 +08:00
|
|
|
|
|
|
|
return -EOPNOTSUPP;
|
|
|
|
}
|
|
|
|
|
2016-09-18 22:59:07 +08:00
|
|
|
static int bcm_enet_get_link_ksettings(struct net_device *dev,
|
|
|
|
struct ethtool_link_ksettings *cmd)
|
2009-08-18 20:23:40 +08:00
|
|
|
{
|
|
|
|
struct bcm_enet_priv *priv;
|
2016-09-18 22:59:07 +08:00
|
|
|
u32 supported, advertising;
|
2009-08-18 20:23:40 +08:00
|
|
|
|
|
|
|
priv = netdev_priv(dev);
|
|
|
|
|
|
|
|
if (priv->has_phy) {
|
2016-09-18 22:59:06 +08:00
|
|
|
if (!dev->phydev)
|
2009-08-18 20:23:40 +08:00
|
|
|
return -ENODEV;
|
2017-06-13 15:09:46 +08:00
|
|
|
|
|
|
|
phy_ethtool_ksettings_get(dev->phydev, cmd);
|
|
|
|
|
|
|
|
return 0;
|
2009-08-18 20:23:40 +08:00
|
|
|
} else {
|
2016-09-18 22:59:07 +08:00
|
|
|
cmd->base.autoneg = 0;
|
|
|
|
cmd->base.speed = (priv->force_speed_100) ?
|
|
|
|
SPEED_100 : SPEED_10;
|
|
|
|
cmd->base.duplex = (priv->force_duplex_full) ?
|
2009-08-18 20:23:40 +08:00
|
|
|
DUPLEX_FULL : DUPLEX_HALF;
|
2016-09-18 22:59:07 +08:00
|
|
|
supported = ADVERTISED_10baseT_Half |
|
2009-08-18 20:23:40 +08:00
|
|
|
ADVERTISED_10baseT_Full |
|
|
|
|
ADVERTISED_100baseT_Half |
|
|
|
|
ADVERTISED_100baseT_Full;
|
2016-09-18 22:59:07 +08:00
|
|
|
advertising = 0;
|
|
|
|
ethtool_convert_legacy_u32_to_link_mode(
|
|
|
|
cmd->link_modes.supported, supported);
|
|
|
|
ethtool_convert_legacy_u32_to_link_mode(
|
|
|
|
cmd->link_modes.advertising, advertising);
|
|
|
|
cmd->base.port = PORT_MII;
|
2009-08-18 20:23:40 +08:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-09-18 22:59:07 +08:00
|
|
|
static int bcm_enet_set_link_ksettings(struct net_device *dev,
|
|
|
|
const struct ethtool_link_ksettings *cmd)
|
2009-08-18 20:23:40 +08:00
|
|
|
{
|
|
|
|
struct bcm_enet_priv *priv;
|
|
|
|
|
|
|
|
priv = netdev_priv(dev);
|
|
|
|
if (priv->has_phy) {
|
2016-09-18 22:59:06 +08:00
|
|
|
if (!dev->phydev)
|
2009-08-18 20:23:40 +08:00
|
|
|
return -ENODEV;
|
2016-09-18 22:59:07 +08:00
|
|
|
return phy_ethtool_ksettings_set(dev->phydev, cmd);
|
2009-08-18 20:23:40 +08:00
|
|
|
} else {
|
|
|
|
|
2016-09-18 22:59:07 +08:00
|
|
|
if (cmd->base.autoneg ||
|
|
|
|
(cmd->base.speed != SPEED_100 &&
|
|
|
|
cmd->base.speed != SPEED_10) ||
|
|
|
|
cmd->base.port != PORT_MII)
|
2009-08-18 20:23:40 +08:00
|
|
|
return -EINVAL;
|
|
|
|
|
2016-09-18 22:59:07 +08:00
|
|
|
priv->force_speed_100 =
|
|
|
|
(cmd->base.speed == SPEED_100) ? 1 : 0;
|
|
|
|
priv->force_duplex_full =
|
|
|
|
(cmd->base.duplex == DUPLEX_FULL) ? 1 : 0;
|
2009-08-18 20:23:40 +08:00
|
|
|
|
|
|
|
if (netif_running(dev))
|
|
|
|
bcm_enet_adjust_link(dev);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void bcm_enet_get_ringparam(struct net_device *dev,
|
|
|
|
struct ethtool_ringparam *ering)
|
|
|
|
{
|
|
|
|
struct bcm_enet_priv *priv;
|
|
|
|
|
|
|
|
priv = netdev_priv(dev);
|
|
|
|
|
|
|
|
/* rx/tx ring is actually only limited by memory */
|
|
|
|
ering->rx_max_pending = 8192;
|
|
|
|
ering->tx_max_pending = 8192;
|
|
|
|
ering->rx_pending = priv->rx_ring_size;
|
|
|
|
ering->tx_pending = priv->tx_ring_size;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int bcm_enet_set_ringparam(struct net_device *dev,
|
|
|
|
struct ethtool_ringparam *ering)
|
|
|
|
{
|
|
|
|
struct bcm_enet_priv *priv;
|
|
|
|
int was_running;
|
|
|
|
|
|
|
|
priv = netdev_priv(dev);
|
|
|
|
|
|
|
|
was_running = 0;
|
|
|
|
if (netif_running(dev)) {
|
|
|
|
bcm_enet_stop(dev);
|
|
|
|
was_running = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
priv->rx_ring_size = ering->rx_pending;
|
|
|
|
priv->tx_ring_size = ering->tx_pending;
|
|
|
|
|
|
|
|
if (was_running) {
|
|
|
|
int err;
|
|
|
|
|
|
|
|
err = bcm_enet_open(dev);
|
|
|
|
if (err)
|
|
|
|
dev_close(dev);
|
|
|
|
else
|
|
|
|
bcm_enet_set_multicast_list(dev);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void bcm_enet_get_pauseparam(struct net_device *dev,
|
|
|
|
struct ethtool_pauseparam *ecmd)
|
|
|
|
{
|
|
|
|
struct bcm_enet_priv *priv;
|
|
|
|
|
|
|
|
priv = netdev_priv(dev);
|
|
|
|
ecmd->autoneg = priv->pause_auto;
|
|
|
|
ecmd->rx_pause = priv->pause_rx;
|
|
|
|
ecmd->tx_pause = priv->pause_tx;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int bcm_enet_set_pauseparam(struct net_device *dev,
|
|
|
|
struct ethtool_pauseparam *ecmd)
|
|
|
|
{
|
|
|
|
struct bcm_enet_priv *priv;
|
|
|
|
|
|
|
|
priv = netdev_priv(dev);
|
|
|
|
|
|
|
|
if (priv->has_phy) {
|
|
|
|
if (ecmd->autoneg && (ecmd->rx_pause != ecmd->tx_pause)) {
|
|
|
|
/* asymetric pause mode not supported,
|
|
|
|
* actually possible but integrated PHY has RO
|
|
|
|
* asym_pause bit */
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/* no pause autoneg on direct mii connection */
|
|
|
|
if (ecmd->autoneg)
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
priv->pause_auto = ecmd->autoneg;
|
|
|
|
priv->pause_rx = ecmd->rx_pause;
|
|
|
|
priv->pause_tx = ecmd->tx_pause;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-01-06 03:10:24 +08:00
|
|
|
static const struct ethtool_ops bcm_enet_ethtool_ops = {
|
2009-08-18 20:23:40 +08:00
|
|
|
.get_strings = bcm_enet_get_strings,
|
2009-12-15 14:45:06 +08:00
|
|
|
.get_sset_count = bcm_enet_get_sset_count,
|
2009-08-18 20:23:40 +08:00
|
|
|
.get_ethtool_stats = bcm_enet_get_ethtool_stats,
|
2013-06-05 05:53:33 +08:00
|
|
|
.nway_reset = bcm_enet_nway_reset,
|
2009-08-18 20:23:40 +08:00
|
|
|
.get_drvinfo = bcm_enet_get_drvinfo,
|
|
|
|
.get_link = ethtool_op_get_link,
|
|
|
|
.get_ringparam = bcm_enet_get_ringparam,
|
|
|
|
.set_ringparam = bcm_enet_set_ringparam,
|
|
|
|
.get_pauseparam = bcm_enet_get_pauseparam,
|
|
|
|
.set_pauseparam = bcm_enet_set_pauseparam,
|
2016-09-18 22:59:07 +08:00
|
|
|
.get_link_ksettings = bcm_enet_get_link_ksettings,
|
|
|
|
.set_link_ksettings = bcm_enet_set_link_ksettings,
|
2009-08-18 20:23:40 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
static int bcm_enet_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
|
|
|
|
{
|
|
|
|
struct bcm_enet_priv *priv;
|
|
|
|
|
|
|
|
priv = netdev_priv(dev);
|
|
|
|
if (priv->has_phy) {
|
2016-09-18 22:59:06 +08:00
|
|
|
if (!dev->phydev)
|
2009-08-18 20:23:40 +08:00
|
|
|
return -ENODEV;
|
2016-09-18 22:59:06 +08:00
|
|
|
return phy_mii_ioctl(dev->phydev, rq, cmd);
|
2009-08-18 20:23:40 +08:00
|
|
|
} else {
|
|
|
|
struct mii_if_info mii;
|
|
|
|
|
|
|
|
mii.dev = dev;
|
|
|
|
mii.mdio_read = bcm_enet_mdio_read_mii;
|
|
|
|
mii.mdio_write = bcm_enet_mdio_write_mii;
|
|
|
|
mii.phy_id = 0;
|
|
|
|
mii.phy_id_mask = 0x3f;
|
|
|
|
mii.reg_num_mask = 0x1f;
|
|
|
|
return generic_mii_ioctl(&mii, if_mii(rq), cmd, NULL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2016-10-18 03:54:04 +08:00
|
|
|
* adjust mtu, can't be called while device is running
|
2009-08-18 20:23:40 +08:00
|
|
|
*/
|
2016-10-18 03:54:04 +08:00
|
|
|
static int bcm_enet_change_mtu(struct net_device *dev, int new_mtu)
|
2009-08-18 20:23:40 +08:00
|
|
|
{
|
2016-10-18 03:54:04 +08:00
|
|
|
struct bcm_enet_priv *priv = netdev_priv(dev);
|
|
|
|
int actual_mtu = new_mtu;
|
2009-08-18 20:23:40 +08:00
|
|
|
|
2016-10-18 03:54:04 +08:00
|
|
|
if (netif_running(dev))
|
|
|
|
return -EBUSY;
|
2009-08-18 20:23:40 +08:00
|
|
|
|
|
|
|
/* add ethernet header + vlan tag size */
|
|
|
|
actual_mtu += VLAN_ETH_HLEN;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* setup maximum size before we get overflow mark in
|
|
|
|
* descriptor, note that this will not prevent reception of
|
|
|
|
* big frames, they will be split into multiple buffers
|
|
|
|
* anyway
|
|
|
|
*/
|
|
|
|
priv->hw_mtu = actual_mtu;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* align rx buffer size to dma burst len, account FCS since
|
|
|
|
* it's appended
|
|
|
|
*/
|
|
|
|
priv->rx_skb_size = ALIGN(actual_mtu + ETH_FCS_LEN,
|
2013-06-05 05:53:35 +08:00
|
|
|
priv->dma_maxburst * 4);
|
2009-08-18 20:23:40 +08:00
|
|
|
|
|
|
|
dev->mtu = new_mtu;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* preinit hardware to allow mii operation while device is down
|
|
|
|
*/
|
|
|
|
static void bcm_enet_hw_preinit(struct bcm_enet_priv *priv)
|
|
|
|
{
|
|
|
|
u32 val;
|
|
|
|
int limit;
|
|
|
|
|
|
|
|
/* make sure mac is disabled */
|
|
|
|
bcm_enet_disable_mac(priv);
|
|
|
|
|
|
|
|
/* soft reset mac */
|
|
|
|
val = ENET_CTL_SRESET_MASK;
|
|
|
|
enet_writel(priv, val, ENET_CTL_REG);
|
|
|
|
wmb();
|
|
|
|
|
|
|
|
limit = 1000;
|
|
|
|
do {
|
|
|
|
val = enet_readl(priv, ENET_CTL_REG);
|
|
|
|
if (!(val & ENET_CTL_SRESET_MASK))
|
|
|
|
break;
|
|
|
|
udelay(1);
|
|
|
|
} while (limit--);
|
|
|
|
|
|
|
|
/* select correct mii interface */
|
|
|
|
val = enet_readl(priv, ENET_CTL_REG);
|
|
|
|
if (priv->use_external_mii)
|
|
|
|
val |= ENET_CTL_EPHYSEL_MASK;
|
|
|
|
else
|
|
|
|
val &= ~ENET_CTL_EPHYSEL_MASK;
|
|
|
|
enet_writel(priv, val, ENET_CTL_REG);
|
|
|
|
|
|
|
|
/* turn on mdc clock */
|
|
|
|
enet_writel(priv, (0x1f << ENET_MIISC_MDCFREQDIV_SHIFT) |
|
|
|
|
ENET_MIISC_PREAMBLEEN_MASK, ENET_MIISC_REG);
|
|
|
|
|
|
|
|
/* set mib counters to self-clear when read */
|
|
|
|
val = enet_readl(priv, ENET_MIBCTL_REG);
|
|
|
|
val |= ENET_MIBCTL_RDCLEAR_MASK;
|
|
|
|
enet_writel(priv, val, ENET_MIBCTL_REG);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct net_device_ops bcm_enet_ops = {
|
|
|
|
.ndo_open = bcm_enet_open,
|
|
|
|
.ndo_stop = bcm_enet_stop,
|
|
|
|
.ndo_start_xmit = bcm_enet_start_xmit,
|
|
|
|
.ndo_set_mac_address = bcm_enet_set_mac_address,
|
2011-08-16 14:29:01 +08:00
|
|
|
.ndo_set_rx_mode = bcm_enet_set_multicast_list,
|
2009-08-18 20:23:40 +08:00
|
|
|
.ndo_do_ioctl = bcm_enet_ioctl,
|
|
|
|
.ndo_change_mtu = bcm_enet_change_mtu,
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* allocate netdevice, request register memory and register device.
|
|
|
|
*/
|
2012-12-03 22:24:23 +08:00
|
|
|
static int bcm_enet_probe(struct platform_device *pdev)
|
2009-08-18 20:23:40 +08:00
|
|
|
{
|
|
|
|
struct bcm_enet_priv *priv;
|
|
|
|
struct net_device *dev;
|
|
|
|
struct bcm63xx_enet_platform_data *pd;
|
|
|
|
struct resource *res_mem, *res_irq, *res_irq_rx, *res_irq_tx;
|
|
|
|
struct mii_bus *bus;
|
|
|
|
int i, ret;
|
|
|
|
|
2013-06-05 05:53:34 +08:00
|
|
|
if (!bcm_enet_shared_base[0])
|
2017-10-01 19:02:17 +08:00
|
|
|
return -EPROBE_DEFER;
|
2009-08-18 20:23:40 +08:00
|
|
|
|
|
|
|
res_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
|
|
|
|
res_irq_rx = platform_get_resource(pdev, IORESOURCE_IRQ, 1);
|
|
|
|
res_irq_tx = platform_get_resource(pdev, IORESOURCE_IRQ, 2);
|
2013-08-19 19:20:39 +08:00
|
|
|
if (!res_irq || !res_irq_rx || !res_irq_tx)
|
2009-08-18 20:23:40 +08:00
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
dev = alloc_etherdev(sizeof(*priv));
|
|
|
|
if (!dev)
|
|
|
|
return -ENOMEM;
|
|
|
|
priv = netdev_priv(dev);
|
|
|
|
|
2013-06-05 05:53:35 +08:00
|
|
|
priv->enet_is_sw = false;
|
|
|
|
priv->dma_maxburst = BCMENET_DMA_MAXBURST;
|
|
|
|
|
2016-10-18 03:54:04 +08:00
|
|
|
ret = bcm_enet_change_mtu(dev, dev->mtu);
|
2009-08-18 20:23:40 +08:00
|
|
|
if (ret)
|
|
|
|
goto out;
|
|
|
|
|
2013-08-19 19:20:39 +08:00
|
|
|
res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
|
|
priv->base = devm_ioremap_resource(&pdev->dev, res_mem);
|
|
|
|
if (IS_ERR(priv->base)) {
|
|
|
|
ret = PTR_ERR(priv->base);
|
2013-03-10 11:57:47 +08:00
|
|
|
goto out;
|
2009-08-18 20:23:40 +08:00
|
|
|
}
|
2013-03-10 11:57:47 +08:00
|
|
|
|
2009-08-18 20:23:40 +08:00
|
|
|
dev->irq = priv->irq = res_irq->start;
|
|
|
|
priv->irq_rx = res_irq_rx->start;
|
|
|
|
priv->irq_tx = res_irq_tx->start;
|
|
|
|
|
2017-12-18 00:02:52 +08:00
|
|
|
priv->mac_clk = devm_clk_get(&pdev->dev, "enet");
|
2009-08-18 20:23:40 +08:00
|
|
|
if (IS_ERR(priv->mac_clk)) {
|
|
|
|
ret = PTR_ERR(priv->mac_clk);
|
2013-03-10 11:57:47 +08:00
|
|
|
goto out;
|
2009-08-18 20:23:40 +08:00
|
|
|
}
|
2017-10-01 19:02:15 +08:00
|
|
|
ret = clk_prepare_enable(priv->mac_clk);
|
|
|
|
if (ret)
|
2017-10-01 19:02:18 +08:00
|
|
|
goto out;
|
2009-08-18 20:23:40 +08:00
|
|
|
|
|
|
|
/* initialize default and fetch platform data */
|
|
|
|
priv->rx_ring_size = BCMENET_DEF_RX_DESC;
|
|
|
|
priv->tx_ring_size = BCMENET_DEF_TX_DESC;
|
|
|
|
|
2013-08-30 12:52:21 +08:00
|
|
|
pd = dev_get_platdata(&pdev->dev);
|
2009-08-18 20:23:40 +08:00
|
|
|
if (pd) {
|
|
|
|
memcpy(dev->dev_addr, pd->mac_addr, ETH_ALEN);
|
|
|
|
priv->has_phy = pd->has_phy;
|
|
|
|
priv->phy_id = pd->phy_id;
|
|
|
|
priv->has_phy_interrupt = pd->has_phy_interrupt;
|
|
|
|
priv->phy_interrupt = pd->phy_interrupt;
|
|
|
|
priv->use_external_mii = !pd->use_internal_phy;
|
|
|
|
priv->pause_auto = pd->pause_auto;
|
|
|
|
priv->pause_rx = pd->pause_rx;
|
|
|
|
priv->pause_tx = pd->pause_tx;
|
|
|
|
priv->force_duplex_full = pd->force_duplex_full;
|
|
|
|
priv->force_speed_100 = pd->force_speed_100;
|
2013-06-13 03:53:05 +08:00
|
|
|
priv->dma_chan_en_mask = pd->dma_chan_en_mask;
|
|
|
|
priv->dma_chan_int_mask = pd->dma_chan_int_mask;
|
|
|
|
priv->dma_chan_width = pd->dma_chan_width;
|
|
|
|
priv->dma_has_sram = pd->dma_has_sram;
|
|
|
|
priv->dma_desc_shift = pd->dma_desc_shift;
|
2017-12-18 00:02:53 +08:00
|
|
|
priv->rx_chan = pd->rx_chan;
|
|
|
|
priv->tx_chan = pd->tx_chan;
|
2009-08-18 20:23:40 +08:00
|
|
|
}
|
|
|
|
|
2017-12-18 00:02:54 +08:00
|
|
|
if (priv->has_phy && !priv->use_external_mii) {
|
2009-08-18 20:23:40 +08:00
|
|
|
/* using internal PHY, enable clock */
|
2017-10-01 19:02:18 +08:00
|
|
|
priv->phy_clk = devm_clk_get(&pdev->dev, "ephy");
|
2009-08-18 20:23:40 +08:00
|
|
|
if (IS_ERR(priv->phy_clk)) {
|
|
|
|
ret = PTR_ERR(priv->phy_clk);
|
|
|
|
priv->phy_clk = NULL;
|
2017-10-01 19:02:15 +08:00
|
|
|
goto out_disable_clk_mac;
|
2009-08-18 20:23:40 +08:00
|
|
|
}
|
2017-10-01 19:02:15 +08:00
|
|
|
ret = clk_prepare_enable(priv->phy_clk);
|
|
|
|
if (ret)
|
2017-10-01 19:02:18 +08:00
|
|
|
goto out_disable_clk_mac;
|
2009-08-18 20:23:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* do minimal hardware init to be able to probe mii bus */
|
|
|
|
bcm_enet_hw_preinit(priv);
|
|
|
|
|
|
|
|
/* MII bus registration */
|
|
|
|
if (priv->has_phy) {
|
|
|
|
|
|
|
|
priv->mii_bus = mdiobus_alloc();
|
|
|
|
if (!priv->mii_bus) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto out_uninit_hw;
|
|
|
|
}
|
|
|
|
|
|
|
|
bus = priv->mii_bus;
|
|
|
|
bus->name = "bcm63xx_enet MII bus";
|
|
|
|
bus->parent = &pdev->dev;
|
|
|
|
bus->priv = priv;
|
|
|
|
bus->read = bcm_enet_mdio_read_phylib;
|
|
|
|
bus->write = bcm_enet_mdio_write_phylib;
|
2017-12-18 00:02:55 +08:00
|
|
|
sprintf(bus->id, "%s-%d", pdev->name, pdev->id);
|
2009-08-18 20:23:40 +08:00
|
|
|
|
|
|
|
/* only probe bus where we think the PHY is, because
|
|
|
|
* the mdio read operation return 0 instead of 0xffff
|
|
|
|
* if a slave is not present on hw */
|
|
|
|
bus->phy_mask = ~(1 << priv->phy_id);
|
|
|
|
|
|
|
|
if (priv->has_phy_interrupt)
|
|
|
|
bus->irq[priv->phy_id] = priv->phy_interrupt;
|
|
|
|
|
|
|
|
ret = mdiobus_register(bus);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(&pdev->dev, "unable to register mdio bus\n");
|
|
|
|
goto out_free_mdio;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
|
|
|
|
/* run platform code to initialize PHY device */
|
2016-07-31 16:24:29 +08:00
|
|
|
if (pd && pd->mii_config &&
|
2009-08-18 20:23:40 +08:00
|
|
|
pd->mii_config(dev, 1, bcm_enet_mdio_read_mii,
|
|
|
|
bcm_enet_mdio_write_mii)) {
|
|
|
|
dev_err(&pdev->dev, "unable to configure mdio bus\n");
|
|
|
|
goto out_uninit_hw;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
spin_lock_init(&priv->rx_lock);
|
|
|
|
|
|
|
|
/* init rx timeout (used for oom) */
|
2017-10-17 08:28:57 +08:00
|
|
|
timer_setup(&priv->rx_timeout, bcm_enet_refill_rx_timer, 0);
|
2009-08-18 20:23:40 +08:00
|
|
|
|
|
|
|
/* init the mib update lock&work */
|
|
|
|
mutex_init(&priv->mib_update_lock);
|
|
|
|
INIT_WORK(&priv->mib_update_task, bcm_enet_update_mib_counters_defer);
|
|
|
|
|
|
|
|
/* zero mib counters */
|
|
|
|
for (i = 0; i < ENET_MIB_REG_COUNT; i++)
|
|
|
|
enet_writel(priv, 0, ENET_MIB_REG(i));
|
|
|
|
|
|
|
|
/* register netdevice */
|
|
|
|
dev->netdev_ops = &bcm_enet_ops;
|
|
|
|
netif_napi_add(dev, &priv->napi, bcm_enet_poll, 16);
|
|
|
|
|
2014-05-11 08:12:32 +08:00
|
|
|
dev->ethtool_ops = &bcm_enet_ethtool_ops;
|
2016-10-18 03:54:04 +08:00
|
|
|
/* MTU range: 46 - 2028 */
|
|
|
|
dev->min_mtu = ETH_ZLEN - ETH_HLEN;
|
|
|
|
dev->max_mtu = BCMENET_MAX_MTU - VLAN_ETH_HLEN;
|
2009-08-18 20:23:40 +08:00
|
|
|
SET_NETDEV_DEV(dev, &pdev->dev);
|
|
|
|
|
|
|
|
ret = register_netdev(dev);
|
|
|
|
if (ret)
|
|
|
|
goto out_unregister_mdio;
|
|
|
|
|
|
|
|
netif_carrier_off(dev);
|
|
|
|
platform_set_drvdata(pdev, dev);
|
|
|
|
priv->pdev = pdev;
|
|
|
|
priv->net_dev = dev;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
out_unregister_mdio:
|
2013-03-10 11:57:48 +08:00
|
|
|
if (priv->mii_bus)
|
2009-08-18 20:23:40 +08:00
|
|
|
mdiobus_unregister(priv->mii_bus);
|
|
|
|
|
|
|
|
out_free_mdio:
|
|
|
|
if (priv->mii_bus)
|
|
|
|
mdiobus_free(priv->mii_bus);
|
|
|
|
|
|
|
|
out_uninit_hw:
|
|
|
|
/* turn off mdc clock */
|
|
|
|
enet_writel(priv, 0, ENET_MIISC_REG);
|
2017-10-01 19:02:19 +08:00
|
|
|
clk_disable_unprepare(priv->phy_clk);
|
2017-10-01 19:02:15 +08:00
|
|
|
|
|
|
|
out_disable_clk_mac:
|
2013-03-10 11:57:49 +08:00
|
|
|
clk_disable_unprepare(priv->mac_clk);
|
2009-08-18 20:23:40 +08:00
|
|
|
out:
|
|
|
|
free_netdev(dev);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* exit func, stops hardware and unregisters netdevice
|
|
|
|
*/
|
2012-12-03 22:24:23 +08:00
|
|
|
static int bcm_enet_remove(struct platform_device *pdev)
|
2009-08-18 20:23:40 +08:00
|
|
|
{
|
|
|
|
struct bcm_enet_priv *priv;
|
|
|
|
struct net_device *dev;
|
|
|
|
|
|
|
|
/* stop netdevice */
|
|
|
|
dev = platform_get_drvdata(pdev);
|
|
|
|
priv = netdev_priv(dev);
|
|
|
|
unregister_netdev(dev);
|
|
|
|
|
|
|
|
/* turn off mdc clock */
|
|
|
|
enet_writel(priv, 0, ENET_MIISC_REG);
|
|
|
|
|
|
|
|
if (priv->has_phy) {
|
|
|
|
mdiobus_unregister(priv->mii_bus);
|
|
|
|
mdiobus_free(priv->mii_bus);
|
|
|
|
} else {
|
|
|
|
struct bcm63xx_enet_platform_data *pd;
|
|
|
|
|
2013-08-30 12:52:21 +08:00
|
|
|
pd = dev_get_platdata(&pdev->dev);
|
2009-08-18 20:23:40 +08:00
|
|
|
if (pd && pd->mii_config)
|
|
|
|
pd->mii_config(dev, 0, bcm_enet_mdio_read_mii,
|
|
|
|
bcm_enet_mdio_write_mii);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* disable hw block clocks */
|
2017-10-01 19:02:19 +08:00
|
|
|
clk_disable_unprepare(priv->phy_clk);
|
2013-03-10 11:57:49 +08:00
|
|
|
clk_disable_unprepare(priv->mac_clk);
|
2009-08-18 20:23:40 +08:00
|
|
|
|
|
|
|
free_netdev(dev);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct platform_driver bcm63xx_enet_driver = {
|
|
|
|
.probe = bcm_enet_probe,
|
2012-12-03 22:24:23 +08:00
|
|
|
.remove = bcm_enet_remove,
|
2009-08-18 20:23:40 +08:00
|
|
|
.driver = {
|
|
|
|
.name = "bcm63xx_enet",
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
2013-06-05 05:53:35 +08:00
|
|
|
* switch mii access callbacks
|
2009-08-18 20:23:40 +08:00
|
|
|
*/
|
2013-06-05 05:53:35 +08:00
|
|
|
static int bcmenet_sw_mdio_read(struct bcm_enet_priv *priv,
|
|
|
|
int ext, int phy_id, int location)
|
2009-08-18 20:23:40 +08:00
|
|
|
{
|
2013-06-05 05:53:35 +08:00
|
|
|
u32 reg;
|
|
|
|
int ret;
|
2009-08-18 20:23:40 +08:00
|
|
|
|
2013-06-05 05:53:35 +08:00
|
|
|
spin_lock_bh(&priv->enetsw_mdio_lock);
|
|
|
|
enetsw_writel(priv, 0, ENETSW_MDIOC_REG);
|
2009-08-18 20:23:40 +08:00
|
|
|
|
2013-06-05 05:53:35 +08:00
|
|
|
reg = ENETSW_MDIOC_RD_MASK |
|
|
|
|
(phy_id << ENETSW_MDIOC_PHYID_SHIFT) |
|
|
|
|
(location << ENETSW_MDIOC_REG_SHIFT);
|
2013-06-05 05:53:34 +08:00
|
|
|
|
2013-06-05 05:53:35 +08:00
|
|
|
if (ext)
|
|
|
|
reg |= ENETSW_MDIOC_EXT_MASK;
|
2013-03-10 11:57:47 +08:00
|
|
|
|
2013-06-05 05:53:35 +08:00
|
|
|
enetsw_writel(priv, reg, ENETSW_MDIOC_REG);
|
|
|
|
udelay(50);
|
|
|
|
ret = enetsw_readw(priv, ENETSW_MDIOD_REG);
|
|
|
|
spin_unlock_bh(&priv->enetsw_mdio_lock);
|
|
|
|
return ret;
|
2009-08-18 20:23:40 +08:00
|
|
|
}
|
|
|
|
|
2013-06-05 05:53:35 +08:00
|
|
|
static void bcmenet_sw_mdio_write(struct bcm_enet_priv *priv,
|
|
|
|
int ext, int phy_id, int location,
|
|
|
|
uint16_t data)
|
2009-08-18 20:23:40 +08:00
|
|
|
{
|
2013-06-05 05:53:35 +08:00
|
|
|
u32 reg;
|
|
|
|
|
|
|
|
spin_lock_bh(&priv->enetsw_mdio_lock);
|
|
|
|
enetsw_writel(priv, 0, ENETSW_MDIOC_REG);
|
|
|
|
|
|
|
|
reg = ENETSW_MDIOC_WR_MASK |
|
|
|
|
(phy_id << ENETSW_MDIOC_PHYID_SHIFT) |
|
|
|
|
(location << ENETSW_MDIOC_REG_SHIFT);
|
|
|
|
|
|
|
|
if (ext)
|
|
|
|
reg |= ENETSW_MDIOC_EXT_MASK;
|
|
|
|
|
|
|
|
reg |= data;
|
|
|
|
|
|
|
|
enetsw_writel(priv, reg, ENETSW_MDIOC_REG);
|
|
|
|
udelay(50);
|
|
|
|
spin_unlock_bh(&priv->enetsw_mdio_lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int bcm_enet_port_is_rgmii(int portid)
|
|
|
|
{
|
|
|
|
return portid >= ENETSW_RGMII_PORT0;
|
2009-08-18 20:23:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2013-06-05 05:53:35 +08:00
|
|
|
* enet sw PHY polling
|
2009-08-18 20:23:40 +08:00
|
|
|
*/
|
2017-10-17 08:28:57 +08:00
|
|
|
static void swphy_poll_timer(struct timer_list *t)
|
2013-06-05 05:53:35 +08:00
|
|
|
{
|
2017-10-17 08:28:57 +08:00
|
|
|
struct bcm_enet_priv *priv = from_timer(priv, t, swphy_poll);
|
2013-06-05 05:53:35 +08:00
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
for (i = 0; i < priv->num_ports; i++) {
|
|
|
|
struct bcm63xx_enetsw_port *port;
|
2015-10-16 04:00:22 +08:00
|
|
|
int val, j, up, advertise, lpa, speed, duplex, media;
|
2013-06-05 05:53:35 +08:00
|
|
|
int external_phy = bcm_enet_port_is_rgmii(i);
|
|
|
|
u8 override;
|
|
|
|
|
|
|
|
port = &priv->used_ports[i];
|
|
|
|
if (!port->used)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (port->bypass_link)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* dummy read to clear */
|
|
|
|
for (j = 0; j < 2; j++)
|
|
|
|
val = bcmenet_sw_mdio_read(priv, external_phy,
|
|
|
|
port->phy_id, MII_BMSR);
|
|
|
|
|
|
|
|
if (val == 0xffff)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
up = (val & BMSR_LSTATUS) ? 1 : 0;
|
|
|
|
if (!(up ^ priv->sw_port_link[i]))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
priv->sw_port_link[i] = up;
|
|
|
|
|
|
|
|
/* link changed */
|
|
|
|
if (!up) {
|
|
|
|
dev_info(&priv->pdev->dev, "link DOWN on %s\n",
|
|
|
|
port->name);
|
|
|
|
enetsw_writeb(priv, ENETSW_PORTOV_ENABLE_MASK,
|
|
|
|
ENETSW_PORTOV_REG(i));
|
|
|
|
enetsw_writeb(priv, ENETSW_PTCTRL_RXDIS_MASK |
|
|
|
|
ENETSW_PTCTRL_TXDIS_MASK,
|
|
|
|
ENETSW_PTCTRL_REG(i));
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
advertise = bcmenet_sw_mdio_read(priv, external_phy,
|
|
|
|
port->phy_id, MII_ADVERTISE);
|
|
|
|
|
|
|
|
lpa = bcmenet_sw_mdio_read(priv, external_phy, port->phy_id,
|
|
|
|
MII_LPA);
|
|
|
|
|
|
|
|
/* figure out media and duplex from advertise and LPA values */
|
|
|
|
media = mii_nway_result(lpa & advertise);
|
|
|
|
duplex = (media & ADVERTISE_FULL) ? 1 : 0;
|
2015-10-16 04:00:22 +08:00
|
|
|
|
|
|
|
if (media & (ADVERTISE_100FULL | ADVERTISE_100HALF))
|
|
|
|
speed = 100;
|
|
|
|
else
|
|
|
|
speed = 10;
|
|
|
|
|
|
|
|
if (val & BMSR_ESTATEN) {
|
|
|
|
advertise = bcmenet_sw_mdio_read(priv, external_phy,
|
|
|
|
port->phy_id, MII_CTRL1000);
|
|
|
|
|
|
|
|
lpa = bcmenet_sw_mdio_read(priv, external_phy,
|
|
|
|
port->phy_id, MII_STAT1000);
|
|
|
|
|
|
|
|
if (advertise & (ADVERTISE_1000FULL | ADVERTISE_1000HALF)
|
|
|
|
&& lpa & (LPA_1000FULL | LPA_1000HALF)) {
|
|
|
|
speed = 1000;
|
|
|
|
duplex = (lpa & LPA_1000FULL);
|
|
|
|
}
|
2013-06-05 05:53:35 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
dev_info(&priv->pdev->dev,
|
|
|
|
"link UP on %s, %dMbps, %s-duplex\n",
|
|
|
|
port->name, speed, duplex ? "full" : "half");
|
|
|
|
|
|
|
|
override = ENETSW_PORTOV_ENABLE_MASK |
|
|
|
|
ENETSW_PORTOV_LINKUP_MASK;
|
|
|
|
|
|
|
|
if (speed == 1000)
|
|
|
|
override |= ENETSW_IMPOV_1000_MASK;
|
|
|
|
else if (speed == 100)
|
|
|
|
override |= ENETSW_IMPOV_100_MASK;
|
|
|
|
if (duplex)
|
|
|
|
override |= ENETSW_IMPOV_FDX_MASK;
|
|
|
|
|
|
|
|
enetsw_writeb(priv, override, ENETSW_PORTOV_REG(i));
|
|
|
|
enetsw_writeb(priv, 0, ENETSW_PTCTRL_REG(i));
|
|
|
|
}
|
|
|
|
|
|
|
|
priv->swphy_poll.expires = jiffies + HZ;
|
|
|
|
add_timer(&priv->swphy_poll);
|
|
|
|
}
|
2009-08-18 20:23:40 +08:00
|
|
|
|
|
|
|
/*
|
2013-06-05 05:53:35 +08:00
|
|
|
* open callback, allocate dma rings & buffers and start rx operation
|
2009-08-18 20:23:40 +08:00
|
|
|
*/
|
2013-06-05 05:53:35 +08:00
|
|
|
static int bcm_enetsw_open(struct net_device *dev)
|
2009-08-18 20:23:40 +08:00
|
|
|
{
|
2013-06-05 05:53:35 +08:00
|
|
|
struct bcm_enet_priv *priv;
|
|
|
|
struct device *kdev;
|
|
|
|
int i, ret;
|
|
|
|
unsigned int size;
|
|
|
|
void *p;
|
|
|
|
u32 val;
|
2009-08-18 20:23:40 +08:00
|
|
|
|
2013-06-05 05:53:35 +08:00
|
|
|
priv = netdev_priv(dev);
|
|
|
|
kdev = &priv->pdev->dev;
|
2009-08-18 20:23:40 +08:00
|
|
|
|
2013-06-05 05:53:35 +08:00
|
|
|
/* mask all interrupts and request them */
|
2013-06-13 03:53:05 +08:00
|
|
|
enet_dmac_writel(priv, 0, ENETDMAC_IRMASK, priv->rx_chan);
|
|
|
|
enet_dmac_writel(priv, 0, ENETDMAC_IRMASK, priv->tx_chan);
|
2013-06-05 05:53:35 +08:00
|
|
|
|
|
|
|
ret = request_irq(priv->irq_rx, bcm_enet_isr_dma,
|
2013-09-07 14:56:50 +08:00
|
|
|
0, dev->name, dev);
|
2009-08-18 20:23:40 +08:00
|
|
|
if (ret)
|
2013-06-05 05:53:35 +08:00
|
|
|
goto out_freeirq;
|
|
|
|
|
|
|
|
if (priv->irq_tx != -1) {
|
|
|
|
ret = request_irq(priv->irq_tx, bcm_enet_isr_dma,
|
2013-09-07 14:56:50 +08:00
|
|
|
0, dev->name, dev);
|
2013-06-05 05:53:35 +08:00
|
|
|
if (ret)
|
|
|
|
goto out_freeirq_rx;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* allocate rx dma ring */
|
|
|
|
size = priv->rx_ring_size * sizeof(struct bcm_enet_desc);
|
2017-12-30 23:44:57 +08:00
|
|
|
p = dma_zalloc_coherent(kdev, size, &priv->rx_desc_dma, GFP_KERNEL);
|
2013-06-05 05:53:35 +08:00
|
|
|
if (!p) {
|
|
|
|
dev_err(kdev, "cannot allocate rx ring %u\n", size);
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto out_freeirq_tx;
|
|
|
|
}
|
|
|
|
|
|
|
|
priv->rx_desc_alloc_size = size;
|
|
|
|
priv->rx_desc_cpu = p;
|
|
|
|
|
|
|
|
/* allocate tx dma ring */
|
|
|
|
size = priv->tx_ring_size * sizeof(struct bcm_enet_desc);
|
2017-12-30 23:44:57 +08:00
|
|
|
p = dma_zalloc_coherent(kdev, size, &priv->tx_desc_dma, GFP_KERNEL);
|
2013-06-05 05:53:35 +08:00
|
|
|
if (!p) {
|
|
|
|
dev_err(kdev, "cannot allocate tx ring\n");
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto out_free_rx_ring;
|
|
|
|
}
|
|
|
|
|
|
|
|
priv->tx_desc_alloc_size = size;
|
|
|
|
priv->tx_desc_cpu = p;
|
|
|
|
|
treewide: kzalloc() -> kcalloc()
The kzalloc() function has a 2-factor argument form, kcalloc(). This
patch replaces cases of:
kzalloc(a * b, gfp)
with:
kcalloc(a * b, gfp)
as well as handling cases of:
kzalloc(a * b * c, gfp)
with:
kzalloc(array3_size(a, b, c), gfp)
as it's slightly less ugly than:
kzalloc_array(array_size(a, b), c, gfp)
This does, however, attempt to ignore constant size factors like:
kzalloc(4 * 1024, gfp)
though any constants defined via macros get caught up in the conversion.
Any factors with a sizeof() of "unsigned char", "char", and "u8" were
dropped, since they're redundant.
The Coccinelle script used for this was:
// Fix redundant parens around sizeof().
@@
type TYPE;
expression THING, E;
@@
(
kzalloc(
- (sizeof(TYPE)) * E
+ sizeof(TYPE) * E
, ...)
|
kzalloc(
- (sizeof(THING)) * E
+ sizeof(THING) * E
, ...)
)
// Drop single-byte sizes and redundant parens.
@@
expression COUNT;
typedef u8;
typedef __u8;
@@
(
kzalloc(
- sizeof(u8) * (COUNT)
+ COUNT
, ...)
|
kzalloc(
- sizeof(__u8) * (COUNT)
+ COUNT
, ...)
|
kzalloc(
- sizeof(char) * (COUNT)
+ COUNT
, ...)
|
kzalloc(
- sizeof(unsigned char) * (COUNT)
+ COUNT
, ...)
|
kzalloc(
- sizeof(u8) * COUNT
+ COUNT
, ...)
|
kzalloc(
- sizeof(__u8) * COUNT
+ COUNT
, ...)
|
kzalloc(
- sizeof(char) * COUNT
+ COUNT
, ...)
|
kzalloc(
- sizeof(unsigned char) * COUNT
+ COUNT
, ...)
)
// 2-factor product with sizeof(type/expression) and identifier or constant.
@@
type TYPE;
expression THING;
identifier COUNT_ID;
constant COUNT_CONST;
@@
(
- kzalloc
+ kcalloc
(
- sizeof(TYPE) * (COUNT_ID)
+ COUNT_ID, sizeof(TYPE)
, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(TYPE) * COUNT_ID
+ COUNT_ID, sizeof(TYPE)
, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(TYPE) * (COUNT_CONST)
+ COUNT_CONST, sizeof(TYPE)
, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(TYPE) * COUNT_CONST
+ COUNT_CONST, sizeof(TYPE)
, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(THING) * (COUNT_ID)
+ COUNT_ID, sizeof(THING)
, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(THING) * COUNT_ID
+ COUNT_ID, sizeof(THING)
, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(THING) * (COUNT_CONST)
+ COUNT_CONST, sizeof(THING)
, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(THING) * COUNT_CONST
+ COUNT_CONST, sizeof(THING)
, ...)
)
// 2-factor product, only identifiers.
@@
identifier SIZE, COUNT;
@@
- kzalloc
+ kcalloc
(
- SIZE * COUNT
+ COUNT, SIZE
, ...)
// 3-factor product with 1 sizeof(type) or sizeof(expression), with
// redundant parens removed.
@@
expression THING;
identifier STRIDE, COUNT;
type TYPE;
@@
(
kzalloc(
- sizeof(TYPE) * (COUNT) * (STRIDE)
+ array3_size(COUNT, STRIDE, sizeof(TYPE))
, ...)
|
kzalloc(
- sizeof(TYPE) * (COUNT) * STRIDE
+ array3_size(COUNT, STRIDE, sizeof(TYPE))
, ...)
|
kzalloc(
- sizeof(TYPE) * COUNT * (STRIDE)
+ array3_size(COUNT, STRIDE, sizeof(TYPE))
, ...)
|
kzalloc(
- sizeof(TYPE) * COUNT * STRIDE
+ array3_size(COUNT, STRIDE, sizeof(TYPE))
, ...)
|
kzalloc(
- sizeof(THING) * (COUNT) * (STRIDE)
+ array3_size(COUNT, STRIDE, sizeof(THING))
, ...)
|
kzalloc(
- sizeof(THING) * (COUNT) * STRIDE
+ array3_size(COUNT, STRIDE, sizeof(THING))
, ...)
|
kzalloc(
- sizeof(THING) * COUNT * (STRIDE)
+ array3_size(COUNT, STRIDE, sizeof(THING))
, ...)
|
kzalloc(
- sizeof(THING) * COUNT * STRIDE
+ array3_size(COUNT, STRIDE, sizeof(THING))
, ...)
)
// 3-factor product with 2 sizeof(variable), with redundant parens removed.
@@
expression THING1, THING2;
identifier COUNT;
type TYPE1, TYPE2;
@@
(
kzalloc(
- sizeof(TYPE1) * sizeof(TYPE2) * COUNT
+ array3_size(COUNT, sizeof(TYPE1), sizeof(TYPE2))
, ...)
|
kzalloc(
- sizeof(TYPE1) * sizeof(THING2) * (COUNT)
+ array3_size(COUNT, sizeof(TYPE1), sizeof(TYPE2))
, ...)
|
kzalloc(
- sizeof(THING1) * sizeof(THING2) * COUNT
+ array3_size(COUNT, sizeof(THING1), sizeof(THING2))
, ...)
|
kzalloc(
- sizeof(THING1) * sizeof(THING2) * (COUNT)
+ array3_size(COUNT, sizeof(THING1), sizeof(THING2))
, ...)
|
kzalloc(
- sizeof(TYPE1) * sizeof(THING2) * COUNT
+ array3_size(COUNT, sizeof(TYPE1), sizeof(THING2))
, ...)
|
kzalloc(
- sizeof(TYPE1) * sizeof(THING2) * (COUNT)
+ array3_size(COUNT, sizeof(TYPE1), sizeof(THING2))
, ...)
)
// 3-factor product, only identifiers, with redundant parens removed.
@@
identifier STRIDE, SIZE, COUNT;
@@
(
kzalloc(
- (COUNT) * STRIDE * SIZE
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kzalloc(
- COUNT * (STRIDE) * SIZE
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kzalloc(
- COUNT * STRIDE * (SIZE)
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kzalloc(
- (COUNT) * (STRIDE) * SIZE
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kzalloc(
- COUNT * (STRIDE) * (SIZE)
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kzalloc(
- (COUNT) * STRIDE * (SIZE)
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kzalloc(
- (COUNT) * (STRIDE) * (SIZE)
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kzalloc(
- COUNT * STRIDE * SIZE
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
)
// Any remaining multi-factor products, first at least 3-factor products,
// when they're not all constants...
@@
expression E1, E2, E3;
constant C1, C2, C3;
@@
(
kzalloc(C1 * C2 * C3, ...)
|
kzalloc(
- (E1) * E2 * E3
+ array3_size(E1, E2, E3)
, ...)
|
kzalloc(
- (E1) * (E2) * E3
+ array3_size(E1, E2, E3)
, ...)
|
kzalloc(
- (E1) * (E2) * (E3)
+ array3_size(E1, E2, E3)
, ...)
|
kzalloc(
- E1 * E2 * E3
+ array3_size(E1, E2, E3)
, ...)
)
// And then all remaining 2 factors products when they're not all constants,
// keeping sizeof() as the second factor argument.
@@
expression THING, E1, E2;
type TYPE;
constant C1, C2, C3;
@@
(
kzalloc(sizeof(THING) * C2, ...)
|
kzalloc(sizeof(TYPE) * C2, ...)
|
kzalloc(C1 * C2 * C3, ...)
|
kzalloc(C1 * C2, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(TYPE) * (E2)
+ E2, sizeof(TYPE)
, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(TYPE) * E2
+ E2, sizeof(TYPE)
, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(THING) * (E2)
+ E2, sizeof(THING)
, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(THING) * E2
+ E2, sizeof(THING)
, ...)
|
- kzalloc
+ kcalloc
(
- (E1) * E2
+ E1, E2
, ...)
|
- kzalloc
+ kcalloc
(
- (E1) * (E2)
+ E1, E2
, ...)
|
- kzalloc
+ kcalloc
(
- E1 * E2
+ E1, E2
, ...)
)
Signed-off-by: Kees Cook <keescook@chromium.org>
2018-06-13 05:03:40 +08:00
|
|
|
priv->tx_skb = kcalloc(priv->tx_ring_size, sizeof(struct sk_buff *),
|
2013-06-05 05:53:35 +08:00
|
|
|
GFP_KERNEL);
|
|
|
|
if (!priv->tx_skb) {
|
|
|
|
dev_err(kdev, "cannot allocate rx skb queue\n");
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto out_free_tx_ring;
|
|
|
|
}
|
|
|
|
|
|
|
|
priv->tx_desc_count = priv->tx_ring_size;
|
|
|
|
priv->tx_dirty_desc = 0;
|
|
|
|
priv->tx_curr_desc = 0;
|
|
|
|
spin_lock_init(&priv->tx_lock);
|
|
|
|
|
|
|
|
/* init & fill rx ring with skbs */
|
treewide: kzalloc() -> kcalloc()
The kzalloc() function has a 2-factor argument form, kcalloc(). This
patch replaces cases of:
kzalloc(a * b, gfp)
with:
kcalloc(a * b, gfp)
as well as handling cases of:
kzalloc(a * b * c, gfp)
with:
kzalloc(array3_size(a, b, c), gfp)
as it's slightly less ugly than:
kzalloc_array(array_size(a, b), c, gfp)
This does, however, attempt to ignore constant size factors like:
kzalloc(4 * 1024, gfp)
though any constants defined via macros get caught up in the conversion.
Any factors with a sizeof() of "unsigned char", "char", and "u8" were
dropped, since they're redundant.
The Coccinelle script used for this was:
// Fix redundant parens around sizeof().
@@
type TYPE;
expression THING, E;
@@
(
kzalloc(
- (sizeof(TYPE)) * E
+ sizeof(TYPE) * E
, ...)
|
kzalloc(
- (sizeof(THING)) * E
+ sizeof(THING) * E
, ...)
)
// Drop single-byte sizes and redundant parens.
@@
expression COUNT;
typedef u8;
typedef __u8;
@@
(
kzalloc(
- sizeof(u8) * (COUNT)
+ COUNT
, ...)
|
kzalloc(
- sizeof(__u8) * (COUNT)
+ COUNT
, ...)
|
kzalloc(
- sizeof(char) * (COUNT)
+ COUNT
, ...)
|
kzalloc(
- sizeof(unsigned char) * (COUNT)
+ COUNT
, ...)
|
kzalloc(
- sizeof(u8) * COUNT
+ COUNT
, ...)
|
kzalloc(
- sizeof(__u8) * COUNT
+ COUNT
, ...)
|
kzalloc(
- sizeof(char) * COUNT
+ COUNT
, ...)
|
kzalloc(
- sizeof(unsigned char) * COUNT
+ COUNT
, ...)
)
// 2-factor product with sizeof(type/expression) and identifier or constant.
@@
type TYPE;
expression THING;
identifier COUNT_ID;
constant COUNT_CONST;
@@
(
- kzalloc
+ kcalloc
(
- sizeof(TYPE) * (COUNT_ID)
+ COUNT_ID, sizeof(TYPE)
, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(TYPE) * COUNT_ID
+ COUNT_ID, sizeof(TYPE)
, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(TYPE) * (COUNT_CONST)
+ COUNT_CONST, sizeof(TYPE)
, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(TYPE) * COUNT_CONST
+ COUNT_CONST, sizeof(TYPE)
, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(THING) * (COUNT_ID)
+ COUNT_ID, sizeof(THING)
, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(THING) * COUNT_ID
+ COUNT_ID, sizeof(THING)
, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(THING) * (COUNT_CONST)
+ COUNT_CONST, sizeof(THING)
, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(THING) * COUNT_CONST
+ COUNT_CONST, sizeof(THING)
, ...)
)
// 2-factor product, only identifiers.
@@
identifier SIZE, COUNT;
@@
- kzalloc
+ kcalloc
(
- SIZE * COUNT
+ COUNT, SIZE
, ...)
// 3-factor product with 1 sizeof(type) or sizeof(expression), with
// redundant parens removed.
@@
expression THING;
identifier STRIDE, COUNT;
type TYPE;
@@
(
kzalloc(
- sizeof(TYPE) * (COUNT) * (STRIDE)
+ array3_size(COUNT, STRIDE, sizeof(TYPE))
, ...)
|
kzalloc(
- sizeof(TYPE) * (COUNT) * STRIDE
+ array3_size(COUNT, STRIDE, sizeof(TYPE))
, ...)
|
kzalloc(
- sizeof(TYPE) * COUNT * (STRIDE)
+ array3_size(COUNT, STRIDE, sizeof(TYPE))
, ...)
|
kzalloc(
- sizeof(TYPE) * COUNT * STRIDE
+ array3_size(COUNT, STRIDE, sizeof(TYPE))
, ...)
|
kzalloc(
- sizeof(THING) * (COUNT) * (STRIDE)
+ array3_size(COUNT, STRIDE, sizeof(THING))
, ...)
|
kzalloc(
- sizeof(THING) * (COUNT) * STRIDE
+ array3_size(COUNT, STRIDE, sizeof(THING))
, ...)
|
kzalloc(
- sizeof(THING) * COUNT * (STRIDE)
+ array3_size(COUNT, STRIDE, sizeof(THING))
, ...)
|
kzalloc(
- sizeof(THING) * COUNT * STRIDE
+ array3_size(COUNT, STRIDE, sizeof(THING))
, ...)
)
// 3-factor product with 2 sizeof(variable), with redundant parens removed.
@@
expression THING1, THING2;
identifier COUNT;
type TYPE1, TYPE2;
@@
(
kzalloc(
- sizeof(TYPE1) * sizeof(TYPE2) * COUNT
+ array3_size(COUNT, sizeof(TYPE1), sizeof(TYPE2))
, ...)
|
kzalloc(
- sizeof(TYPE1) * sizeof(THING2) * (COUNT)
+ array3_size(COUNT, sizeof(TYPE1), sizeof(TYPE2))
, ...)
|
kzalloc(
- sizeof(THING1) * sizeof(THING2) * COUNT
+ array3_size(COUNT, sizeof(THING1), sizeof(THING2))
, ...)
|
kzalloc(
- sizeof(THING1) * sizeof(THING2) * (COUNT)
+ array3_size(COUNT, sizeof(THING1), sizeof(THING2))
, ...)
|
kzalloc(
- sizeof(TYPE1) * sizeof(THING2) * COUNT
+ array3_size(COUNT, sizeof(TYPE1), sizeof(THING2))
, ...)
|
kzalloc(
- sizeof(TYPE1) * sizeof(THING2) * (COUNT)
+ array3_size(COUNT, sizeof(TYPE1), sizeof(THING2))
, ...)
)
// 3-factor product, only identifiers, with redundant parens removed.
@@
identifier STRIDE, SIZE, COUNT;
@@
(
kzalloc(
- (COUNT) * STRIDE * SIZE
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kzalloc(
- COUNT * (STRIDE) * SIZE
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kzalloc(
- COUNT * STRIDE * (SIZE)
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kzalloc(
- (COUNT) * (STRIDE) * SIZE
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kzalloc(
- COUNT * (STRIDE) * (SIZE)
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kzalloc(
- (COUNT) * STRIDE * (SIZE)
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kzalloc(
- (COUNT) * (STRIDE) * (SIZE)
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kzalloc(
- COUNT * STRIDE * SIZE
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
)
// Any remaining multi-factor products, first at least 3-factor products,
// when they're not all constants...
@@
expression E1, E2, E3;
constant C1, C2, C3;
@@
(
kzalloc(C1 * C2 * C3, ...)
|
kzalloc(
- (E1) * E2 * E3
+ array3_size(E1, E2, E3)
, ...)
|
kzalloc(
- (E1) * (E2) * E3
+ array3_size(E1, E2, E3)
, ...)
|
kzalloc(
- (E1) * (E2) * (E3)
+ array3_size(E1, E2, E3)
, ...)
|
kzalloc(
- E1 * E2 * E3
+ array3_size(E1, E2, E3)
, ...)
)
// And then all remaining 2 factors products when they're not all constants,
// keeping sizeof() as the second factor argument.
@@
expression THING, E1, E2;
type TYPE;
constant C1, C2, C3;
@@
(
kzalloc(sizeof(THING) * C2, ...)
|
kzalloc(sizeof(TYPE) * C2, ...)
|
kzalloc(C1 * C2 * C3, ...)
|
kzalloc(C1 * C2, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(TYPE) * (E2)
+ E2, sizeof(TYPE)
, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(TYPE) * E2
+ E2, sizeof(TYPE)
, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(THING) * (E2)
+ E2, sizeof(THING)
, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(THING) * E2
+ E2, sizeof(THING)
, ...)
|
- kzalloc
+ kcalloc
(
- (E1) * E2
+ E1, E2
, ...)
|
- kzalloc
+ kcalloc
(
- (E1) * (E2)
+ E1, E2
, ...)
|
- kzalloc
+ kcalloc
(
- E1 * E2
+ E1, E2
, ...)
)
Signed-off-by: Kees Cook <keescook@chromium.org>
2018-06-13 05:03:40 +08:00
|
|
|
priv->rx_skb = kcalloc(priv->rx_ring_size, sizeof(struct sk_buff *),
|
2013-06-05 05:53:35 +08:00
|
|
|
GFP_KERNEL);
|
|
|
|
if (!priv->rx_skb) {
|
|
|
|
dev_err(kdev, "cannot allocate rx skb queue\n");
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto out_free_tx_skb;
|
|
|
|
}
|
|
|
|
|
|
|
|
priv->rx_desc_count = 0;
|
|
|
|
priv->rx_dirty_desc = 0;
|
|
|
|
priv->rx_curr_desc = 0;
|
|
|
|
|
|
|
|
/* disable all ports */
|
|
|
|
for (i = 0; i < priv->num_ports; i++) {
|
|
|
|
enetsw_writeb(priv, ENETSW_PORTOV_ENABLE_MASK,
|
|
|
|
ENETSW_PORTOV_REG(i));
|
|
|
|
enetsw_writeb(priv, ENETSW_PTCTRL_RXDIS_MASK |
|
|
|
|
ENETSW_PTCTRL_TXDIS_MASK,
|
|
|
|
ENETSW_PTCTRL_REG(i));
|
|
|
|
|
|
|
|
priv->sw_port_link[i] = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* reset mib */
|
|
|
|
val = enetsw_readb(priv, ENETSW_GMCR_REG);
|
|
|
|
val |= ENETSW_GMCR_RST_MIB_MASK;
|
|
|
|
enetsw_writeb(priv, val, ENETSW_GMCR_REG);
|
|
|
|
mdelay(1);
|
|
|
|
val &= ~ENETSW_GMCR_RST_MIB_MASK;
|
|
|
|
enetsw_writeb(priv, val, ENETSW_GMCR_REG);
|
|
|
|
mdelay(1);
|
|
|
|
|
|
|
|
/* force CPU port state */
|
|
|
|
val = enetsw_readb(priv, ENETSW_IMPOV_REG);
|
|
|
|
val |= ENETSW_IMPOV_FORCE_MASK | ENETSW_IMPOV_LINKUP_MASK;
|
|
|
|
enetsw_writeb(priv, val, ENETSW_IMPOV_REG);
|
|
|
|
|
|
|
|
/* enable switch forward engine */
|
|
|
|
val = enetsw_readb(priv, ENETSW_SWMODE_REG);
|
|
|
|
val |= ENETSW_SWMODE_FWD_EN_MASK;
|
|
|
|
enetsw_writeb(priv, val, ENETSW_SWMODE_REG);
|
|
|
|
|
|
|
|
/* enable jumbo on all ports */
|
|
|
|
enetsw_writel(priv, 0x1ff, ENETSW_JMBCTL_PORT_REG);
|
|
|
|
enetsw_writew(priv, 9728, ENETSW_JMBCTL_MAXSIZE_REG);
|
|
|
|
|
|
|
|
/* initialize flow control buffer allocation */
|
|
|
|
enet_dma_writel(priv, ENETDMA_BUFALLOC_FORCE_MASK | 0,
|
|
|
|
ENETDMA_BUFALLOC_REG(priv->rx_chan));
|
|
|
|
|
|
|
|
if (bcm_enet_refill_rx(dev)) {
|
|
|
|
dev_err(kdev, "cannot allocate rx skb queue\n");
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* write rx & tx ring addresses */
|
|
|
|
enet_dmas_writel(priv, priv->rx_desc_dma,
|
2013-06-13 03:53:05 +08:00
|
|
|
ENETDMAS_RSTART_REG, priv->rx_chan);
|
2013-06-05 05:53:35 +08:00
|
|
|
enet_dmas_writel(priv, priv->tx_desc_dma,
|
2013-06-13 03:53:05 +08:00
|
|
|
ENETDMAS_RSTART_REG, priv->tx_chan);
|
2013-06-05 05:53:35 +08:00
|
|
|
|
|
|
|
/* clear remaining state ram for rx & tx channel */
|
2013-06-13 03:53:05 +08:00
|
|
|
enet_dmas_writel(priv, 0, ENETDMAS_SRAM2_REG, priv->rx_chan);
|
|
|
|
enet_dmas_writel(priv, 0, ENETDMAS_SRAM2_REG, priv->tx_chan);
|
|
|
|
enet_dmas_writel(priv, 0, ENETDMAS_SRAM3_REG, priv->rx_chan);
|
|
|
|
enet_dmas_writel(priv, 0, ENETDMAS_SRAM3_REG, priv->tx_chan);
|
|
|
|
enet_dmas_writel(priv, 0, ENETDMAS_SRAM4_REG, priv->rx_chan);
|
|
|
|
enet_dmas_writel(priv, 0, ENETDMAS_SRAM4_REG, priv->tx_chan);
|
2013-06-05 05:53:35 +08:00
|
|
|
|
|
|
|
/* set dma maximum burst len */
|
|
|
|
enet_dmac_writel(priv, priv->dma_maxburst,
|
2013-06-13 03:53:05 +08:00
|
|
|
ENETDMAC_MAXBURST, priv->rx_chan);
|
2013-06-05 05:53:35 +08:00
|
|
|
enet_dmac_writel(priv, priv->dma_maxburst,
|
2013-06-13 03:53:05 +08:00
|
|
|
ENETDMAC_MAXBURST, priv->tx_chan);
|
2013-06-05 05:53:35 +08:00
|
|
|
|
|
|
|
/* set flow control low/high threshold to 1/3 / 2/3 */
|
|
|
|
val = priv->rx_ring_size / 3;
|
|
|
|
enet_dma_writel(priv, val, ENETDMA_FLOWCL_REG(priv->rx_chan));
|
|
|
|
val = (priv->rx_ring_size * 2) / 3;
|
|
|
|
enet_dma_writel(priv, val, ENETDMA_FLOWCH_REG(priv->rx_chan));
|
|
|
|
|
|
|
|
/* all set, enable mac and interrupts, start dma engine and
|
|
|
|
* kick rx dma channel
|
|
|
|
*/
|
|
|
|
wmb();
|
|
|
|
enet_dma_writel(priv, ENETDMA_CFG_EN_MASK, ENETDMA_CFG_REG);
|
|
|
|
enet_dmac_writel(priv, ENETDMAC_CHANCFG_EN_MASK,
|
2013-06-13 03:53:05 +08:00
|
|
|
ENETDMAC_CHANCFG, priv->rx_chan);
|
2013-06-05 05:53:35 +08:00
|
|
|
|
|
|
|
/* watch "packet transferred" interrupt in rx and tx */
|
|
|
|
enet_dmac_writel(priv, ENETDMAC_IR_PKTDONE_MASK,
|
2013-06-13 03:53:05 +08:00
|
|
|
ENETDMAC_IR, priv->rx_chan);
|
2013-06-05 05:53:35 +08:00
|
|
|
enet_dmac_writel(priv, ENETDMAC_IR_PKTDONE_MASK,
|
2013-06-13 03:53:05 +08:00
|
|
|
ENETDMAC_IR, priv->tx_chan);
|
2013-06-05 05:53:35 +08:00
|
|
|
|
|
|
|
/* make sure we enable napi before rx interrupt */
|
|
|
|
napi_enable(&priv->napi);
|
|
|
|
|
|
|
|
enet_dmac_writel(priv, ENETDMAC_IR_PKTDONE_MASK,
|
2013-06-13 03:53:05 +08:00
|
|
|
ENETDMAC_IRMASK, priv->rx_chan);
|
2013-06-05 05:53:35 +08:00
|
|
|
enet_dmac_writel(priv, ENETDMAC_IR_PKTDONE_MASK,
|
2013-06-13 03:53:05 +08:00
|
|
|
ENETDMAC_IRMASK, priv->tx_chan);
|
2013-06-05 05:53:35 +08:00
|
|
|
|
|
|
|
netif_carrier_on(dev);
|
|
|
|
netif_start_queue(dev);
|
|
|
|
|
|
|
|
/* apply override config for bypass_link ports here. */
|
|
|
|
for (i = 0; i < priv->num_ports; i++) {
|
|
|
|
struct bcm63xx_enetsw_port *port;
|
|
|
|
u8 override;
|
|
|
|
port = &priv->used_ports[i];
|
|
|
|
if (!port->used)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (!port->bypass_link)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
override = ENETSW_PORTOV_ENABLE_MASK |
|
|
|
|
ENETSW_PORTOV_LINKUP_MASK;
|
|
|
|
|
|
|
|
switch (port->force_speed) {
|
|
|
|
case 1000:
|
|
|
|
override |= ENETSW_IMPOV_1000_MASK;
|
|
|
|
break;
|
|
|
|
case 100:
|
|
|
|
override |= ENETSW_IMPOV_100_MASK;
|
|
|
|
break;
|
|
|
|
case 10:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
pr_warn("invalid forced speed on port %s: assume 10\n",
|
|
|
|
port->name);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (port->force_duplex_full)
|
|
|
|
override |= ENETSW_IMPOV_FDX_MASK;
|
|
|
|
|
|
|
|
|
|
|
|
enetsw_writeb(priv, override, ENETSW_PORTOV_REG(i));
|
|
|
|
enetsw_writeb(priv, 0, ENETSW_PTCTRL_REG(i));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* start phy polling timer */
|
2017-10-17 08:28:57 +08:00
|
|
|
timer_setup(&priv->swphy_poll, swphy_poll_timer, 0);
|
2017-09-24 20:11:24 +08:00
|
|
|
mod_timer(&priv->swphy_poll, jiffies);
|
2013-06-05 05:53:35 +08:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
out:
|
|
|
|
for (i = 0; i < priv->rx_ring_size; i++) {
|
|
|
|
struct bcm_enet_desc *desc;
|
|
|
|
|
|
|
|
if (!priv->rx_skb[i])
|
|
|
|
continue;
|
|
|
|
|
|
|
|
desc = &priv->rx_desc_cpu[i];
|
|
|
|
dma_unmap_single(kdev, desc->address, priv->rx_skb_size,
|
|
|
|
DMA_FROM_DEVICE);
|
|
|
|
kfree_skb(priv->rx_skb[i]);
|
|
|
|
}
|
|
|
|
kfree(priv->rx_skb);
|
|
|
|
|
|
|
|
out_free_tx_skb:
|
|
|
|
kfree(priv->tx_skb);
|
|
|
|
|
|
|
|
out_free_tx_ring:
|
|
|
|
dma_free_coherent(kdev, priv->tx_desc_alloc_size,
|
|
|
|
priv->tx_desc_cpu, priv->tx_desc_dma);
|
|
|
|
|
|
|
|
out_free_rx_ring:
|
|
|
|
dma_free_coherent(kdev, priv->rx_desc_alloc_size,
|
|
|
|
priv->rx_desc_cpu, priv->rx_desc_dma);
|
|
|
|
|
|
|
|
out_freeirq_tx:
|
|
|
|
if (priv->irq_tx != -1)
|
|
|
|
free_irq(priv->irq_tx, dev);
|
|
|
|
|
|
|
|
out_freeirq_rx:
|
|
|
|
free_irq(priv->irq_rx, dev);
|
|
|
|
|
|
|
|
out_freeirq:
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* stop callback */
|
|
|
|
static int bcm_enetsw_stop(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct bcm_enet_priv *priv;
|
|
|
|
struct device *kdev;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
priv = netdev_priv(dev);
|
|
|
|
kdev = &priv->pdev->dev;
|
|
|
|
|
|
|
|
del_timer_sync(&priv->swphy_poll);
|
|
|
|
netif_stop_queue(dev);
|
|
|
|
napi_disable(&priv->napi);
|
|
|
|
del_timer_sync(&priv->rx_timeout);
|
|
|
|
|
|
|
|
/* mask all interrupts */
|
2013-06-13 03:53:05 +08:00
|
|
|
enet_dmac_writel(priv, 0, ENETDMAC_IRMASK, priv->rx_chan);
|
|
|
|
enet_dmac_writel(priv, 0, ENETDMAC_IRMASK, priv->tx_chan);
|
2013-06-05 05:53:35 +08:00
|
|
|
|
|
|
|
/* disable dma & mac */
|
|
|
|
bcm_enet_disable_dma(priv, priv->tx_chan);
|
|
|
|
bcm_enet_disable_dma(priv, priv->rx_chan);
|
|
|
|
|
|
|
|
/* force reclaim of all tx buffers */
|
|
|
|
bcm_enet_tx_reclaim(dev, 1);
|
|
|
|
|
|
|
|
/* free the rx skb ring */
|
|
|
|
for (i = 0; i < priv->rx_ring_size; i++) {
|
|
|
|
struct bcm_enet_desc *desc;
|
|
|
|
|
|
|
|
if (!priv->rx_skb[i])
|
|
|
|
continue;
|
|
|
|
|
|
|
|
desc = &priv->rx_desc_cpu[i];
|
|
|
|
dma_unmap_single(kdev, desc->address, priv->rx_skb_size,
|
|
|
|
DMA_FROM_DEVICE);
|
|
|
|
kfree_skb(priv->rx_skb[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* free remaining allocated memory */
|
|
|
|
kfree(priv->rx_skb);
|
|
|
|
kfree(priv->tx_skb);
|
|
|
|
dma_free_coherent(kdev, priv->rx_desc_alloc_size,
|
|
|
|
priv->rx_desc_cpu, priv->rx_desc_dma);
|
|
|
|
dma_free_coherent(kdev, priv->tx_desc_alloc_size,
|
|
|
|
priv->tx_desc_cpu, priv->tx_desc_dma);
|
|
|
|
if (priv->irq_tx != -1)
|
|
|
|
free_irq(priv->irq_tx, dev);
|
|
|
|
free_irq(priv->irq_rx, dev);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* try to sort out phy external status by walking the used_port field
|
|
|
|
* in the bcm_enet_priv structure. in case the phy address is not
|
|
|
|
* assigned to any physical port on the switch, assume it is external
|
|
|
|
* (and yell at the user).
|
|
|
|
*/
|
|
|
|
static int bcm_enetsw_phy_is_external(struct bcm_enet_priv *priv, int phy_id)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < priv->num_ports; ++i) {
|
|
|
|
if (!priv->used_ports[i].used)
|
|
|
|
continue;
|
|
|
|
if (priv->used_ports[i].phy_id == phy_id)
|
|
|
|
return bcm_enet_port_is_rgmii(i);
|
|
|
|
}
|
|
|
|
|
|
|
|
printk_once(KERN_WARNING "bcm63xx_enet: could not find a used port with phy_id %i, assuming phy is external\n",
|
|
|
|
phy_id);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* can't use bcmenet_sw_mdio_read directly as we need to sort out
|
|
|
|
* external/internal status of the given phy_id first.
|
|
|
|
*/
|
|
|
|
static int bcm_enetsw_mii_mdio_read(struct net_device *dev, int phy_id,
|
|
|
|
int location)
|
|
|
|
{
|
|
|
|
struct bcm_enet_priv *priv;
|
|
|
|
|
|
|
|
priv = netdev_priv(dev);
|
|
|
|
return bcmenet_sw_mdio_read(priv,
|
|
|
|
bcm_enetsw_phy_is_external(priv, phy_id),
|
|
|
|
phy_id, location);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* can't use bcmenet_sw_mdio_write directly as we need to sort out
|
|
|
|
* external/internal status of the given phy_id first.
|
|
|
|
*/
|
|
|
|
static void bcm_enetsw_mii_mdio_write(struct net_device *dev, int phy_id,
|
|
|
|
int location,
|
|
|
|
int val)
|
|
|
|
{
|
|
|
|
struct bcm_enet_priv *priv;
|
|
|
|
|
|
|
|
priv = netdev_priv(dev);
|
|
|
|
bcmenet_sw_mdio_write(priv, bcm_enetsw_phy_is_external(priv, phy_id),
|
|
|
|
phy_id, location, val);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int bcm_enetsw_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
|
|
|
|
{
|
|
|
|
struct mii_if_info mii;
|
|
|
|
|
|
|
|
mii.dev = dev;
|
|
|
|
mii.mdio_read = bcm_enetsw_mii_mdio_read;
|
|
|
|
mii.mdio_write = bcm_enetsw_mii_mdio_write;
|
|
|
|
mii.phy_id = 0;
|
|
|
|
mii.phy_id_mask = 0x3f;
|
|
|
|
mii.reg_num_mask = 0x1f;
|
|
|
|
return generic_mii_ioctl(&mii, if_mii(rq), cmd, NULL);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct net_device_ops bcm_enetsw_ops = {
|
|
|
|
.ndo_open = bcm_enetsw_open,
|
|
|
|
.ndo_stop = bcm_enetsw_stop,
|
|
|
|
.ndo_start_xmit = bcm_enet_start_xmit,
|
|
|
|
.ndo_change_mtu = bcm_enet_change_mtu,
|
|
|
|
.ndo_do_ioctl = bcm_enetsw_ioctl,
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static const struct bcm_enet_stats bcm_enetsw_gstrings_stats[] = {
|
|
|
|
{ "rx_packets", DEV_STAT(rx_packets), -1 },
|
|
|
|
{ "tx_packets", DEV_STAT(tx_packets), -1 },
|
|
|
|
{ "rx_bytes", DEV_STAT(rx_bytes), -1 },
|
|
|
|
{ "tx_bytes", DEV_STAT(tx_bytes), -1 },
|
|
|
|
{ "rx_errors", DEV_STAT(rx_errors), -1 },
|
|
|
|
{ "tx_errors", DEV_STAT(tx_errors), -1 },
|
|
|
|
{ "rx_dropped", DEV_STAT(rx_dropped), -1 },
|
|
|
|
{ "tx_dropped", DEV_STAT(tx_dropped), -1 },
|
|
|
|
|
|
|
|
{ "tx_good_octets", GEN_STAT(mib.tx_gd_octets), ETHSW_MIB_RX_GD_OCT },
|
|
|
|
{ "tx_unicast", GEN_STAT(mib.tx_unicast), ETHSW_MIB_RX_BRDCAST },
|
|
|
|
{ "tx_broadcast", GEN_STAT(mib.tx_brdcast), ETHSW_MIB_RX_BRDCAST },
|
|
|
|
{ "tx_multicast", GEN_STAT(mib.tx_mult), ETHSW_MIB_RX_MULT },
|
|
|
|
{ "tx_64_octets", GEN_STAT(mib.tx_64), ETHSW_MIB_RX_64 },
|
|
|
|
{ "tx_65_127_oct", GEN_STAT(mib.tx_65_127), ETHSW_MIB_RX_65_127 },
|
|
|
|
{ "tx_128_255_oct", GEN_STAT(mib.tx_128_255), ETHSW_MIB_RX_128_255 },
|
|
|
|
{ "tx_256_511_oct", GEN_STAT(mib.tx_256_511), ETHSW_MIB_RX_256_511 },
|
|
|
|
{ "tx_512_1023_oct", GEN_STAT(mib.tx_512_1023), ETHSW_MIB_RX_512_1023},
|
|
|
|
{ "tx_1024_1522_oct", GEN_STAT(mib.tx_1024_max),
|
|
|
|
ETHSW_MIB_RX_1024_1522 },
|
|
|
|
{ "tx_1523_2047_oct", GEN_STAT(mib.tx_1523_2047),
|
|
|
|
ETHSW_MIB_RX_1523_2047 },
|
|
|
|
{ "tx_2048_4095_oct", GEN_STAT(mib.tx_2048_4095),
|
|
|
|
ETHSW_MIB_RX_2048_4095 },
|
|
|
|
{ "tx_4096_8191_oct", GEN_STAT(mib.tx_4096_8191),
|
|
|
|
ETHSW_MIB_RX_4096_8191 },
|
|
|
|
{ "tx_8192_9728_oct", GEN_STAT(mib.tx_8192_9728),
|
|
|
|
ETHSW_MIB_RX_8192_9728 },
|
|
|
|
{ "tx_oversize", GEN_STAT(mib.tx_ovr), ETHSW_MIB_RX_OVR },
|
|
|
|
{ "tx_oversize_drop", GEN_STAT(mib.tx_ovr), ETHSW_MIB_RX_OVR_DISC },
|
|
|
|
{ "tx_dropped", GEN_STAT(mib.tx_drop), ETHSW_MIB_RX_DROP },
|
|
|
|
{ "tx_undersize", GEN_STAT(mib.tx_underrun), ETHSW_MIB_RX_UND },
|
|
|
|
{ "tx_pause", GEN_STAT(mib.tx_pause), ETHSW_MIB_RX_PAUSE },
|
|
|
|
|
|
|
|
{ "rx_good_octets", GEN_STAT(mib.rx_gd_octets), ETHSW_MIB_TX_ALL_OCT },
|
|
|
|
{ "rx_broadcast", GEN_STAT(mib.rx_brdcast), ETHSW_MIB_TX_BRDCAST },
|
|
|
|
{ "rx_multicast", GEN_STAT(mib.rx_mult), ETHSW_MIB_TX_MULT },
|
|
|
|
{ "rx_unicast", GEN_STAT(mib.rx_unicast), ETHSW_MIB_TX_MULT },
|
|
|
|
{ "rx_pause", GEN_STAT(mib.rx_pause), ETHSW_MIB_TX_PAUSE },
|
|
|
|
{ "rx_dropped", GEN_STAT(mib.rx_drop), ETHSW_MIB_TX_DROP_PKTS },
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
#define BCM_ENETSW_STATS_LEN \
|
|
|
|
(sizeof(bcm_enetsw_gstrings_stats) / sizeof(struct bcm_enet_stats))
|
|
|
|
|
|
|
|
static void bcm_enetsw_get_strings(struct net_device *netdev,
|
|
|
|
u32 stringset, u8 *data)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
switch (stringset) {
|
|
|
|
case ETH_SS_STATS:
|
|
|
|
for (i = 0; i < BCM_ENETSW_STATS_LEN; i++) {
|
|
|
|
memcpy(data + i * ETH_GSTRING_LEN,
|
|
|
|
bcm_enetsw_gstrings_stats[i].stat_string,
|
|
|
|
ETH_GSTRING_LEN);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int bcm_enetsw_get_sset_count(struct net_device *netdev,
|
|
|
|
int string_set)
|
|
|
|
{
|
|
|
|
switch (string_set) {
|
|
|
|
case ETH_SS_STATS:
|
|
|
|
return BCM_ENETSW_STATS_LEN;
|
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void bcm_enetsw_get_drvinfo(struct net_device *netdev,
|
|
|
|
struct ethtool_drvinfo *drvinfo)
|
|
|
|
{
|
|
|
|
strncpy(drvinfo->driver, bcm_enet_driver_name, 32);
|
|
|
|
strncpy(drvinfo->version, bcm_enet_driver_version, 32);
|
|
|
|
strncpy(drvinfo->fw_version, "N/A", 32);
|
|
|
|
strncpy(drvinfo->bus_info, "bcm63xx", 32);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void bcm_enetsw_get_ethtool_stats(struct net_device *netdev,
|
|
|
|
struct ethtool_stats *stats,
|
|
|
|
u64 *data)
|
|
|
|
{
|
|
|
|
struct bcm_enet_priv *priv;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
priv = netdev_priv(netdev);
|
|
|
|
|
|
|
|
for (i = 0; i < BCM_ENETSW_STATS_LEN; i++) {
|
|
|
|
const struct bcm_enet_stats *s;
|
|
|
|
u32 lo, hi;
|
|
|
|
char *p;
|
|
|
|
int reg;
|
|
|
|
|
|
|
|
s = &bcm_enetsw_gstrings_stats[i];
|
|
|
|
|
|
|
|
reg = s->mib_reg;
|
|
|
|
if (reg == -1)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
lo = enetsw_readl(priv, ENETSW_MIB_REG(reg));
|
|
|
|
p = (char *)priv + s->stat_offset;
|
|
|
|
|
|
|
|
if (s->sizeof_stat == sizeof(u64)) {
|
|
|
|
hi = enetsw_readl(priv, ENETSW_MIB_REG(reg + 1));
|
|
|
|
*(u64 *)p = ((u64)hi << 32 | lo);
|
|
|
|
} else {
|
|
|
|
*(u32 *)p = lo;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < BCM_ENETSW_STATS_LEN; i++) {
|
|
|
|
const struct bcm_enet_stats *s;
|
|
|
|
char *p;
|
|
|
|
|
|
|
|
s = &bcm_enetsw_gstrings_stats[i];
|
|
|
|
|
|
|
|
if (s->mib_reg == -1)
|
|
|
|
p = (char *)&netdev->stats + s->stat_offset;
|
|
|
|
else
|
|
|
|
p = (char *)priv + s->stat_offset;
|
|
|
|
|
|
|
|
data[i] = (s->sizeof_stat == sizeof(u64)) ?
|
|
|
|
*(u64 *)p : *(u32 *)p;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void bcm_enetsw_get_ringparam(struct net_device *dev,
|
|
|
|
struct ethtool_ringparam *ering)
|
|
|
|
{
|
|
|
|
struct bcm_enet_priv *priv;
|
|
|
|
|
|
|
|
priv = netdev_priv(dev);
|
|
|
|
|
|
|
|
/* rx/tx ring is actually only limited by memory */
|
|
|
|
ering->rx_max_pending = 8192;
|
|
|
|
ering->tx_max_pending = 8192;
|
|
|
|
ering->rx_mini_max_pending = 0;
|
|
|
|
ering->rx_jumbo_max_pending = 0;
|
|
|
|
ering->rx_pending = priv->rx_ring_size;
|
|
|
|
ering->tx_pending = priv->tx_ring_size;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int bcm_enetsw_set_ringparam(struct net_device *dev,
|
|
|
|
struct ethtool_ringparam *ering)
|
|
|
|
{
|
|
|
|
struct bcm_enet_priv *priv;
|
|
|
|
int was_running;
|
|
|
|
|
|
|
|
priv = netdev_priv(dev);
|
|
|
|
|
|
|
|
was_running = 0;
|
|
|
|
if (netif_running(dev)) {
|
|
|
|
bcm_enetsw_stop(dev);
|
|
|
|
was_running = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
priv->rx_ring_size = ering->rx_pending;
|
|
|
|
priv->tx_ring_size = ering->tx_pending;
|
|
|
|
|
|
|
|
if (was_running) {
|
|
|
|
int err;
|
|
|
|
|
|
|
|
err = bcm_enetsw_open(dev);
|
|
|
|
if (err)
|
|
|
|
dev_close(dev);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-08-30 17:25:08 +08:00
|
|
|
static const struct ethtool_ops bcm_enetsw_ethtool_ops = {
|
2013-06-05 05:53:35 +08:00
|
|
|
.get_strings = bcm_enetsw_get_strings,
|
|
|
|
.get_sset_count = bcm_enetsw_get_sset_count,
|
|
|
|
.get_ethtool_stats = bcm_enetsw_get_ethtool_stats,
|
|
|
|
.get_drvinfo = bcm_enetsw_get_drvinfo,
|
|
|
|
.get_ringparam = bcm_enetsw_get_ringparam,
|
|
|
|
.set_ringparam = bcm_enetsw_set_ringparam,
|
|
|
|
};
|
|
|
|
|
|
|
|
/* allocate netdevice, request register memory and register device. */
|
|
|
|
static int bcm_enetsw_probe(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
struct bcm_enet_priv *priv;
|
|
|
|
struct net_device *dev;
|
|
|
|
struct bcm63xx_enetsw_platform_data *pd;
|
|
|
|
struct resource *res_mem;
|
|
|
|
int ret, irq_rx, irq_tx;
|
|
|
|
|
|
|
|
if (!bcm_enet_shared_base[0])
|
2017-10-01 19:02:17 +08:00
|
|
|
return -EPROBE_DEFER;
|
2013-06-05 05:53:35 +08:00
|
|
|
|
|
|
|
res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
|
|
irq_rx = platform_get_irq(pdev, 0);
|
|
|
|
irq_tx = platform_get_irq(pdev, 1);
|
|
|
|
if (!res_mem || irq_rx < 0)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
dev = alloc_etherdev(sizeof(*priv));
|
|
|
|
if (!dev)
|
|
|
|
return -ENOMEM;
|
|
|
|
priv = netdev_priv(dev);
|
|
|
|
memset(priv, 0, sizeof(*priv));
|
|
|
|
|
|
|
|
/* initialize default and fetch platform data */
|
|
|
|
priv->enet_is_sw = true;
|
|
|
|
priv->irq_rx = irq_rx;
|
|
|
|
priv->irq_tx = irq_tx;
|
|
|
|
priv->rx_ring_size = BCMENET_DEF_RX_DESC;
|
|
|
|
priv->tx_ring_size = BCMENET_DEF_TX_DESC;
|
|
|
|
priv->dma_maxburst = BCMENETSW_DMA_MAXBURST;
|
|
|
|
|
2013-08-30 12:52:21 +08:00
|
|
|
pd = dev_get_platdata(&pdev->dev);
|
2013-06-05 05:53:35 +08:00
|
|
|
if (pd) {
|
|
|
|
memcpy(dev->dev_addr, pd->mac_addr, ETH_ALEN);
|
|
|
|
memcpy(priv->used_ports, pd->used_ports,
|
|
|
|
sizeof(pd->used_ports));
|
|
|
|
priv->num_ports = pd->num_ports;
|
2013-06-13 03:53:05 +08:00
|
|
|
priv->dma_has_sram = pd->dma_has_sram;
|
|
|
|
priv->dma_chan_en_mask = pd->dma_chan_en_mask;
|
|
|
|
priv->dma_chan_int_mask = pd->dma_chan_int_mask;
|
|
|
|
priv->dma_chan_width = pd->dma_chan_width;
|
2013-06-05 05:53:35 +08:00
|
|
|
}
|
|
|
|
|
2016-10-18 03:54:04 +08:00
|
|
|
ret = bcm_enet_change_mtu(dev, dev->mtu);
|
2013-06-05 05:53:35 +08:00
|
|
|
if (ret)
|
|
|
|
goto out;
|
|
|
|
|
2017-10-01 19:02:18 +08:00
|
|
|
priv->base = devm_ioremap_resource(&pdev->dev, res_mem);
|
|
|
|
if (IS_ERR(priv->base)) {
|
|
|
|
ret = PTR_ERR(priv->base);
|
2013-06-05 05:53:35 +08:00
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2017-10-01 19:02:18 +08:00
|
|
|
priv->mac_clk = devm_clk_get(&pdev->dev, "enetsw");
|
2013-06-05 05:53:35 +08:00
|
|
|
if (IS_ERR(priv->mac_clk)) {
|
|
|
|
ret = PTR_ERR(priv->mac_clk);
|
2017-10-01 19:02:18 +08:00
|
|
|
goto out;
|
2013-06-05 05:53:35 +08:00
|
|
|
}
|
2017-10-01 19:02:15 +08:00
|
|
|
ret = clk_prepare_enable(priv->mac_clk);
|
|
|
|
if (ret)
|
2017-10-01 19:02:18 +08:00
|
|
|
goto out;
|
2013-06-05 05:53:35 +08:00
|
|
|
|
|
|
|
priv->rx_chan = 0;
|
|
|
|
priv->tx_chan = 1;
|
|
|
|
spin_lock_init(&priv->rx_lock);
|
|
|
|
|
|
|
|
/* init rx timeout (used for oom) */
|
2017-10-17 08:28:57 +08:00
|
|
|
timer_setup(&priv->rx_timeout, bcm_enet_refill_rx_timer, 0);
|
2013-06-05 05:53:35 +08:00
|
|
|
|
|
|
|
/* register netdevice */
|
|
|
|
dev->netdev_ops = &bcm_enetsw_ops;
|
|
|
|
netif_napi_add(dev, &priv->napi, bcm_enet_poll, 16);
|
2014-05-11 08:12:32 +08:00
|
|
|
dev->ethtool_ops = &bcm_enetsw_ethtool_ops;
|
2013-06-05 05:53:35 +08:00
|
|
|
SET_NETDEV_DEV(dev, &pdev->dev);
|
|
|
|
|
|
|
|
spin_lock_init(&priv->enetsw_mdio_lock);
|
|
|
|
|
|
|
|
ret = register_netdev(dev);
|
|
|
|
if (ret)
|
2017-10-01 19:02:15 +08:00
|
|
|
goto out_disable_clk;
|
2013-06-05 05:53:35 +08:00
|
|
|
|
|
|
|
netif_carrier_off(dev);
|
|
|
|
platform_set_drvdata(pdev, dev);
|
|
|
|
priv->pdev = pdev;
|
|
|
|
priv->net_dev = dev;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
2017-10-01 19:02:15 +08:00
|
|
|
out_disable_clk:
|
|
|
|
clk_disable_unprepare(priv->mac_clk);
|
2013-06-05 05:53:35 +08:00
|
|
|
out:
|
|
|
|
free_netdev(dev);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* exit func, stops hardware and unregisters netdevice */
|
|
|
|
static int bcm_enetsw_remove(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
struct bcm_enet_priv *priv;
|
|
|
|
struct net_device *dev;
|
|
|
|
|
|
|
|
/* stop netdevice */
|
|
|
|
dev = platform_get_drvdata(pdev);
|
|
|
|
priv = netdev_priv(dev);
|
|
|
|
unregister_netdev(dev);
|
|
|
|
|
2017-10-01 19:02:15 +08:00
|
|
|
clk_disable_unprepare(priv->mac_clk);
|
|
|
|
|
2013-06-05 05:53:35 +08:00
|
|
|
free_netdev(dev);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct platform_driver bcm63xx_enetsw_driver = {
|
|
|
|
.probe = bcm_enetsw_probe,
|
|
|
|
.remove = bcm_enetsw_remove,
|
|
|
|
.driver = {
|
|
|
|
.name = "bcm63xx_enetsw",
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
/* reserve & remap memory space shared between all macs */
|
|
|
|
static int bcm_enet_shared_probe(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
struct resource *res;
|
|
|
|
void __iomem *p[3];
|
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
memset(bcm_enet_shared_base, 0, sizeof(bcm_enet_shared_base));
|
|
|
|
|
|
|
|
for (i = 0; i < 3; i++) {
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, i);
|
|
|
|
p[i] = devm_ioremap_resource(&pdev->dev, res);
|
2013-06-19 10:32:32 +08:00
|
|
|
if (IS_ERR(p[i]))
|
|
|
|
return PTR_ERR(p[i]);
|
2013-06-05 05:53:35 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
memcpy(bcm_enet_shared_base, p, sizeof(bcm_enet_shared_base));
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int bcm_enet_shared_remove(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* this "shared" driver is needed because both macs share a single
|
|
|
|
* address space
|
|
|
|
*/
|
|
|
|
struct platform_driver bcm63xx_enet_shared_driver = {
|
|
|
|
.probe = bcm_enet_shared_probe,
|
|
|
|
.remove = bcm_enet_shared_remove,
|
|
|
|
.driver = {
|
|
|
|
.name = "bcm63xx_enet_shared",
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2015-12-03 00:30:27 +08:00
|
|
|
static struct platform_driver * const drivers[] = {
|
|
|
|
&bcm63xx_enet_shared_driver,
|
|
|
|
&bcm63xx_enet_driver,
|
|
|
|
&bcm63xx_enetsw_driver,
|
|
|
|
};
|
|
|
|
|
2013-06-05 05:53:35 +08:00
|
|
|
/* entry point */
|
|
|
|
static int __init bcm_enet_init(void)
|
|
|
|
{
|
2015-12-03 00:30:27 +08:00
|
|
|
return platform_register_drivers(drivers, ARRAY_SIZE(drivers));
|
2009-08-18 20:23:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void __exit bcm_enet_exit(void)
|
|
|
|
{
|
2015-12-03 00:30:27 +08:00
|
|
|
platform_unregister_drivers(drivers, ARRAY_SIZE(drivers));
|
2009-08-18 20:23:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
module_init(bcm_enet_init);
|
|
|
|
module_exit(bcm_enet_exit);
|
|
|
|
|
|
|
|
MODULE_DESCRIPTION("BCM63xx internal ethernet mac driver");
|
|
|
|
MODULE_AUTHOR("Maxime Bizon <mbizon@freebox.fr>");
|
|
|
|
MODULE_LICENSE("GPL");
|