aboutsummaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2009-09-17 20:52:32 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2009-09-17 20:52:32 -0700
commit3dc95666df0e1ae5b7381a8ec97a583bb3ce4306 (patch)
treefc1b277f507c48b8c29536947e1de5c2eeda9325 /drivers
parentb938fb6f491113880ebaabfa06c6446723c702fd (diff)
parent9b1fc55a05006523bced65f4d99f7072831ff56a (diff)
Merge branch 'upstream' of git://ftp.linux-mips.org/pub/scm/upstream-linus
* 'upstream' of git://ftp.linux-mips.org/pub/scm/upstream-linus: (51 commits) MIPS: BCM63xx: Add integrated ethernet mac support. MIPS: BCM63xx: Add support for the Broadcom BCM63xx family of SOCs. MIPS: BCM63xx: Add Broadcom 63xx CPU definitions. MIPS: Octeon: Move some platform device registration to its own file. MIPS: Don't corrupt page tables on vmalloc fault. MIPS: Shrink the size of tlb handler MIPS: Alchemy: override loops_per_jiffy detection MIPS: hw_random: Add hardware RNG for Octeon SOCs. MIPS: Octeon: Add hardware RNG platform device. MIPS: Remove useless zero initializations. MIPS: Alchemy: get rid of allow_au1k_wait MIPS: Octeon: Set kernel_uses_llsc to false on non-SMP builds. MIPS: Allow kernel use of LL/SC to be separate from the presence of LL/SC. MIPS: Get rid of CONFIG_CPU_HAS_LLSC MIPS: Malta: Remove pointless use use of CONFIG_CPU_HAS_LLSC MIPS: Rewrite clearing of ll_bit on context switch in C MIPS: Rewrite sysmips(MIPS_ATOMIC_SET, ...) in C with inline assembler MIPS: Consolidate all CONFIG_CPU_HAS_LLSC use in a single C file. MIPS: Clean up linker script using new linker script macros. MIPS: Use PAGE_SIZE in assembly instead of _PAGE_SIZE. ...
Diffstat (limited to 'drivers')
-rw-r--r--drivers/char/hw_random/Kconfig13
-rw-r--r--drivers/char/hw_random/Makefile1
-rw-r--r--drivers/char/hw_random/octeon-rng.c147
-rw-r--r--drivers/net/Kconfig9
-rw-r--r--drivers/net/Makefile1
-rw-r--r--drivers/net/bcm63xx_enet.c1971
-rw-r--r--drivers/net/bcm63xx_enet.h303
-rw-r--r--drivers/staging/octeon/cvmx-mdio.h2
8 files changed, 2446 insertions, 1 deletions
diff --git a/drivers/char/hw_random/Kconfig b/drivers/char/hw_random/Kconfig
index ce66a70184f..87060266ef9 100644
--- a/drivers/char/hw_random/Kconfig
+++ b/drivers/char/hw_random/Kconfig
@@ -126,6 +126,19 @@ config HW_RANDOM_OMAP
If unsure, say Y.
+config HW_RANDOM_OCTEON
+ tristate "Octeon Random Number Generator support"
+ depends on HW_RANDOM && CPU_CAVIUM_OCTEON
+ default HW_RANDOM
+ ---help---
+ This driver provides kernel-side support for the Random Number
+ Generator hardware found on Octeon processors.
+
+ To compile this driver as a module, choose M here: the
+ module will be called octeon-rng.
+
+ If unsure, say Y.
+
config HW_RANDOM_PASEMI
tristate "PA Semi HW Random Number Generator support"
depends on HW_RANDOM && PPC_PASEMI
diff --git a/drivers/char/hw_random/Makefile b/drivers/char/hw_random/Makefile
index 676828ba812..5eeb1303f0d 100644
--- a/drivers/char/hw_random/Makefile
+++ b/drivers/char/hw_random/Makefile
@@ -17,3 +17,4 @@ obj-$(CONFIG_HW_RANDOM_PASEMI) += pasemi-rng.o
obj-$(CONFIG_HW_RANDOM_VIRTIO) += virtio-rng.o
obj-$(CONFIG_HW_RANDOM_TX4939) += tx4939-rng.o
obj-$(CONFIG_HW_RANDOM_MXC_RNGA) += mxc-rnga.o
+obj-$(CONFIG_HW_RANDOM_OCTEON) += octeon-rng.o
diff --git a/drivers/char/hw_random/octeon-rng.c b/drivers/char/hw_random/octeon-rng.c
new file mode 100644
index 00000000000..54b0d9ba65c
--- /dev/null
+++ b/drivers/char/hw_random/octeon-rng.c
@@ -0,0 +1,147 @@
+/*
+ * Hardware Random Number Generator support for Cavium Networks
+ * Octeon processor family.
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (C) 2009 Cavium Networks
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/device.h>
+#include <linux/hw_random.h>
+#include <linux/io.h>
+
+#include <asm/octeon/octeon.h>
+#include <asm/octeon/cvmx-rnm-defs.h>
+
+struct octeon_rng {
+ struct hwrng ops;
+ void __iomem *control_status;
+ void __iomem *result;
+};
+
+static int octeon_rng_init(struct hwrng *rng)
+{
+ union cvmx_rnm_ctl_status ctl;
+ struct octeon_rng *p = container_of(rng, struct octeon_rng, ops);
+
+ ctl.u64 = 0;
+ ctl.s.ent_en = 1; /* Enable the entropy source. */
+ ctl.s.rng_en = 1; /* Enable the RNG hardware. */
+ cvmx_write_csr((u64)p->control_status, ctl.u64);
+ return 0;
+}
+
+static void octeon_rng_cleanup(struct hwrng *rng)
+{
+ union cvmx_rnm_ctl_status ctl;
+ struct octeon_rng *p = container_of(rng, struct octeon_rng, ops);
+
+ ctl.u64 = 0;
+ /* Disable everything. */
+ cvmx_write_csr((u64)p->control_status, ctl.u64);
+}
+
+static int octeon_rng_data_read(struct hwrng *rng, u32 *data)
+{
+ struct octeon_rng *p = container_of(rng, struct octeon_rng, ops);
+
+ *data = cvmx_read64_uint32((u64)p->result);
+ return sizeof(u32);
+}
+
+static int __devinit octeon_rng_probe(struct platform_device *pdev)
+{
+ struct resource *res_ports;
+ struct resource *res_result;
+ struct octeon_rng *rng;
+ int ret;
+ struct hwrng ops = {
+ .name = "octeon",
+ .init = octeon_rng_init,
+ .cleanup = octeon_rng_cleanup,
+ .data_read = octeon_rng_data_read
+ };
+
+ rng = devm_kzalloc(&pdev->dev, sizeof(*rng), GFP_KERNEL);
+ if (!rng)
+ return -ENOMEM;
+
+ res_ports = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res_ports)
+ goto err_ports;
+
+ res_result = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ if (!res_result)
+ goto err_ports;
+
+
+ rng->control_status = devm_ioremap_nocache(&pdev->dev,
+ res_ports->start,
+ sizeof(u64));
+ if (!rng->control_status)
+ goto err_ports;
+
+ rng->result = devm_ioremap_nocache(&pdev->dev,
+ res_result->start,
+ sizeof(u64));
+ if (!rng->result)
+ goto err_r;
+
+ rng->ops = ops;
+
+ dev_set_drvdata(&pdev->dev, &rng->ops);
+ ret = hwrng_register(&rng->ops);
+ if (ret)
+ goto err;
+
+ dev_info(&pdev->dev, "Octeon Random Number Generator\n");
+
+ return 0;
+err:
+ devm_iounmap(&pdev->dev, rng->control_status);
+err_r:
+ devm_iounmap(&pdev->dev, rng->result);
+err_ports:
+ devm_kfree(&pdev->dev, rng);
+ return -ENOENT;
+}
+
+static int __exit octeon_rng_remove(struct platform_device *pdev)
+{
+ struct hwrng *rng = dev_get_drvdata(&pdev->dev);
+
+ hwrng_unregister(rng);
+
+ return 0;
+}
+
+static struct platform_driver octeon_rng_driver = {
+ .driver = {
+ .name = "octeon_rng",
+ .owner = THIS_MODULE,
+ },
+ .probe = octeon_rng_probe,
+ .remove = __exit_p(octeon_rng_remove),
+};
+
+static int __init octeon_rng_mod_init(void)
+{
+ return platform_driver_register(&octeon_rng_driver);
+}
+
+static void __exit octeon_rng_mod_exit(void)
+{
+ platform_driver_unregister(&octeon_rng_driver);
+}
+
+module_init(octeon_rng_mod_init);
+module_exit(octeon_rng_mod_exit);
+
+MODULE_AUTHOR("David Daney");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index 507569a4f23..ed5741b2e70 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -1934,6 +1934,15 @@ config XILINX_EMACLITE
help
This driver supports the 10/100 Ethernet Lite from Xilinx.
+config BCM63XX_ENET
+ tristate "Broadcom 63xx internal mac support"
+ depends on BCM63XX
+ select MII
+ select PHYLIB
+ help
+ This driver supports the ethernet MACs in the Broadcom 63xx
+ MIPS chipset family (BCM63XX).
+
source "drivers/net/fs_enet/Kconfig"
endif # NET_ETHERNET
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index 99ae6d7fe6a..ae8cd30f13d 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -137,6 +137,7 @@ obj-$(CONFIG_B44) += b44.o
obj-$(CONFIG_FORCEDETH) += forcedeth.o
obj-$(CONFIG_NE_H8300) += ne-h8300.o 8390.o
obj-$(CONFIG_AX88796) += ax88796.o
+obj-$(CONFIG_BCM63XX_ENET) += bcm63xx_enet.o
obj-$(CONFIG_TSI108_ETH) += tsi108_eth.o
obj-$(CONFIG_MV643XX_ETH) += mv643xx_eth.o
diff --git a/drivers/net/bcm63xx_enet.c b/drivers/net/bcm63xx_enet.c
new file mode 100644
index 00000000000..09d270913c5
--- /dev/null
+++ b/drivers/net/bcm63xx_enet.c
@@ -0,0 +1,1971 @@
+/*
+ * 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>
+#include <linux/module.h>
+#include <linux/clk.h>
+#include <linux/etherdevice.h>
+#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");
+
+/* io memory shared between all devices */
+static void __iomem *bcm_enet_shared_base;
+
+/*
+ * 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);
+}
+
+/*
+ * io helpers to access shared registers
+ */
+static inline u32 enet_dma_readl(struct bcm_enet_priv *priv, u32 off)
+{
+ return bcm_readl(bcm_enet_shared_base + off);
+}
+
+static inline void enet_dma_writel(struct bcm_enet_priv *priv,
+ u32 val, u32 off)
+{
+ bcm_writel(val, bcm_enet_shared_base + off);
+}
+
+/*
+ * 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);
+ } while (limit-- >= 0);
+
+ 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) {
+ len_stat |= DMADESC_WRAP_MASK;
+ 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 */
+ enet_dma_writel(priv, 1, ENETDMA_BUFALLOC_REG(priv->rx_chan));
+ }
+
+ /* 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
+ */
+static void bcm_enet_refill_rx_timer(unsigned long data)
+{
+ struct net_device *dev;
+ struct bcm_enet_priv *priv;
+
+ dev = (struct net_device *)data;
+ priv = netdev_priv(dev);
+
+ spin_lock(&priv->rx_lock);
+ bcm_enet_refill_rx((struct net_device *)data);
+ 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 */
+ if ((len_stat & DMADESC_ESOP_MASK) != DMADESC_ESOP_MASK) {
+ priv->stats.rx_dropped++;
+ continue;
+ }
+
+ /* recycle packet if it's marked as bad */
+ if (unlikely(len_stat & DMADESC_ERR_MASK)) {
+ priv->stats.rx_errors++;
+
+ if (len_stat & DMADESC_OVSIZE_MASK)
+ priv->stats.rx_length_errors++;
+ if (len_stat & DMADESC_CRC_MASK)
+ priv->stats.rx_crc_errors++;
+ if (len_stat & DMADESC_UNDER_MASK)
+ priv->stats.rx_frame_errors++;
+ if (len_stat & DMADESC_OV_MASK)
+ priv->stats.rx_fifo_errors++;
+ 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;
+
+ nskb = netdev_alloc_skb(dev, len + NET_IP_ALIGN);
+ if (!nskb) {
+ /* forget packet, just rearm desc */
+ priv->stats.rx_dropped++;
+ continue;
+ }
+
+ /* since we're copying the data, we can align
+ * them properly */
+ skb_reserve(nskb, NET_IP_ALIGN);
+ 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->dev = dev;
+ skb->protocol = eth_type_trans(skb, dev);
+ priv->stats.rx_packets++;
+ priv->stats.rx_bytes += len;
+ dev->last_rx = jiffies;
+ netif_receive_skb(skb);
+
+ } while (--budget > 0);
+
+ if (processed || !priv->rx_desc_count) {
+ bcm_enet_refill_rx(dev);
+
+ /* kick rx dma */
+ enet_dma_writel(priv, ENETDMA_CHANCFG_EN_MASK,
+ ENETDMA_CHANCFG_REG(priv->rx_chan));
+ }
+
+ 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)
+ priv->stats.tx_errors++;
+
+ 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;
+ int tx_work_done, rx_work_done;
+
+ priv = container_of(napi, struct bcm_enet_priv, napi);
+ dev = priv->net_dev;
+
+ /* ack interrupts */
+ enet_dma_writel(priv, ENETDMA_IR_PKTDONE_MASK,
+ ENETDMA_IR_REG(priv->rx_chan));
+ enet_dma_writel(priv, ENETDMA_IR_PKTDONE_MASK,
+ ENETDMA_IR_REG(priv->tx_chan));
+
+ /* reclaim sent skb */
+ tx_work_done = bcm_enet_tx_reclaim(dev, 0);
+
+ spin_lock(&priv->rx_lock);
+ rx_work_done = bcm_enet_receive_queue(dev, budget);
+ spin_unlock(&priv->rx_lock);
+
+ if (rx_work_done >= budget || tx_work_done > 0) {
+ /* rx/tx queue is not yet empty/clean */
+ return rx_work_done;
+ }
+
+ /* no more packet in rx/tx queue, remove device from poll
+ * queue */
+ napi_complete(napi);
+
+ /* restore rx/tx interrupt */
+ enet_dma_writel(priv, ENETDMA_IR_PKTDONE_MASK,
+ ENETDMA_IRMASK_REG(priv->rx_chan));
+ enet_dma_writel(priv, ENETDMA_IR_PKTDONE_MASK,
+ ENETDMA_IRMASK_REG(priv->tx_chan));
+
+ 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 */
+ enet_dma_writel(priv, 0, ENETDMA_IRMASK_REG(priv->rx_chan));
+ enet_dma_writel(priv, 0, ENETDMA_IRMASK_REG(priv->tx_chan));
+
+ 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;
+ }
+
+ /* 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;
+ len_stat |= DMADESC_ESOP_MASK |
+ DMADESC_APPEND_CRC |
+ DMADESC_OWNER_MASK;
+
+ priv->tx_curr_desc++;
+ if (priv->tx_curr_desc == priv->tx_ring_size) {
+ priv->tx_curr_desc = 0;
+ len_stat |= DMADESC_WRAP_MASK;
+ }
+ 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 */
+ enet_dma_writel(priv, ENETDMA_CHANCFG_EN_MASK,
+ ENETDMA_CHANCFG_REG(priv->tx_chan));
+
+ /* stop queue if no more desc available */
+ if (!priv->tx_desc_count)
+ netif_stop_queue(dev);
+
+ priv->stats.tx_bytes += skb->len;
+ priv->stats.tx_packets++;
+ dev->trans_start = jiffies;
+ 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;
+}
+
+/*
+ * Change rx mode (promiscous/allmulti) and update multicast list
+ */
+static void bcm_enet_set_multicast_list(struct net_device *dev)
+{
+ struct bcm_enet_priv *priv;
+ struct dev_mc_list *mc_list;
+ 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 */
+ if ((dev->flags & IFF_ALLMULTI) || dev->mc_count > 3)
+ 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;
+ }
+
+ for (i = 0, mc_list = dev->mc_list;
+ (mc_list != NULL) && (i < dev->mc_count) && (i < 3);
+ i++, mc_list = mc_list->next) {
+ u8 *dmi_addr;
+ u32 tmp;
+
+ /* filter non ethernet address */
+ if (mc_list->dmi_addrlen != 6)
+ continue;
+
+ /* update perfect match registers */
+ dmi_addr = mc_list->dmi_addr;
+ 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;
+ enet_writel(priv, tmp, ENET_PMH_REG(i + 1));
+ }
+
+ 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);
+
+ /* 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);
+ phydev = priv->phydev;
+ 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) {
+ /* pause setting overrided by user */
+ 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,
+ priv->mac_id ? "1" : "0", priv->phy_id);
+
+ phydev = phy_connect(dev, phy_id, &bcm_enet_adjust_phy_link, 0,
+ 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;
+
+ dev_info(kdev, "attached PHY at address %d [%s]\n",
+ phydev->addr, phydev->drv->name);
+
+ priv->old_link = 0;
+ priv->old_duplex = -1;
+ priv->old_pause = -1;
+ priv->phydev = phydev;
+ }
+
+ /* mask all interrupts and request them */
+ enet_writel(priv, 0, ENET_IRMASK_REG);
+ enet_dma_writel(priv, 0, ENETDMA_IRMASK_REG(priv->rx_chan));
+ enet_dma_writel(priv, 0, ENETDMA_IRMASK_REG(priv->tx_chan));
+
+ ret = request_irq(dev->irq, bcm_enet_isr_mac, 0, dev->name, dev);
+ if (ret)
+ goto out_phy_disconnect;
+
+ ret = request_irq(priv->irq_rx, bcm_enet_isr_dma,
+ IRQF_SAMPLE_RANDOM | IRQF_DISABLED, dev->name, dev);
+ if (ret)
+ goto out_freeirq;
+
+ ret = request_irq(priv->irq_tx, bcm_enet_isr_dma,
+ IRQF_DISABLED, dev->name, dev);
+ 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);
+ p = dma_alloc_coherent(kdev, size, &priv->rx_desc_dma, GFP_KERNEL);
+ if (!p) {
+ dev_err(kdev, "cannot allocate rx ring %u\n", size);
+ ret = -ENOMEM;
+ goto out_freeirq_tx;
+ }
+
+ memset(p, 0, size);
+ priv->rx_desc_alloc_size = size;