[openwrt/openwrt] bmips: add basic BCM6358 ethernet support

LEDE Commits lede-commits at lists.infradead.org
Tue Mar 28 12:06:23 PDT 2023


noltari pushed a commit to openwrt/openwrt.git, branch master:
https://git.openwrt.org/1c552eb44ddba4d8630eb3453b4f6dd8ef83b13a

commit 1c552eb44ddba4d8630eb3453b4f6dd8ef83b13a
Author: Álvaro Fernández Rojas <noltari at gmail.com>
AuthorDate: Tue May 24 11:14:21 2022 +0200

    bmips: add basic BCM6358 ethernet support
    
    The MDIO bus is supported but there are errors when trying to probe and
    configure the external BCM5325E switch through B53 DSA.
    Therefore, let's add basic ethernet (but working) support for now.
    
    Signed-off-by: Álvaro Fernández Rojas <noltari at gmail.com>
---
 target/linux/bmips/config-5.15                     |    1 +
 target/linux/bmips/dts/bcm6358-huawei-hg556a-b.dts |   11 +
 target/linux/bmips/dts/bcm6358.dtsi                |   79 +
 .../drivers/net/ethernet/broadcom/bcm6348-enet.c   | 1594 ++++++++++++++++++++
 .../generic/base-files/etc/board.d/02_network      |    3 +
 ...oadcom-add-BCM6348-enet-controller-driver.patch |   44 +
 6 files changed, 1732 insertions(+)

diff --git a/target/linux/bmips/config-5.15 b/target/linux/bmips/config-5.15
index f1d7752bd8..c38a4450ab 100644
--- a/target/linux/bmips/config-5.15
+++ b/target/linux/bmips/config-5.15
@@ -10,6 +10,7 @@ CONFIG_B53_MMAP_DRIVER=y
 CONFIG_B53_SPI_DRIVER=y
 CONFIG_BCM6345_EXT_IRQ=y
 CONFIG_BCM6345_L1_IRQ=y
+CONFIG_BCM6348_ENET=y
 CONFIG_BCM6368_ENETSW=y
 CONFIG_BCM63XX_POWER=y
 CONFIG_BCM7038_WDT=y
diff --git a/target/linux/bmips/dts/bcm6358-huawei-hg556a-b.dts b/target/linux/bmips/dts/bcm6358-huawei-hg556a-b.dts
index 6c8b093edf..2ad9460a9c 100644
--- a/target/linux/bmips/dts/bcm6358-huawei-hg556a-b.dts
+++ b/target/linux/bmips/dts/bcm6358-huawei-hg556a-b.dts
@@ -140,6 +140,17 @@
 	status = "okay";
 };
 
+&ethernet1 {
+	status = "okay";
+
+	nvmem-cells = <&macaddr_cfe_6a0>;
+	nvmem-cell-names = "mac-address";
+};
+
+&iudma {
+	status = "okay";
+};
+
 &ohci {
 	status = "okay";
 };
diff --git a/target/linux/bmips/dts/bcm6358.dtsi b/target/linux/bmips/dts/bcm6358.dtsi
index e08ead0623..606e096d2e 100644
--- a/target/linux/bmips/dts/bcm6358.dtsi
+++ b/target/linux/bmips/dts/bcm6358.dtsi
@@ -346,5 +346,84 @@
 
 			status = "disabled";
 		};
+
+		ethernet0: ethernet at fffe4000 {
+			compatible = "brcm,bcm6358-emac";
+			reg = <0xfffe4000 0x2dc>;
+
+			clocks = <&periph_clk BCM6358_CLK_ENET0>;
+
+			interrupt-parent = <&periph_intc>;
+			interrupts = <BCM6358_IRQ_EMAC0>,
+					<BCM6358_IRQ_EMAC0_RX_DMA>,
+					<BCM6358_IRQ_EMAC0_TX_DMA>;
+			interrupt-names = "emac",
+					  "rx",
+					  "tx";
+
+			brcm,iudma = <&iudma>;
+
+			dma-rx = <0>;
+			dma-tx = <1>;
+
+			status = "disabled";
+
+			mdio0: mdio {
+				#address-cells = <1>;
+				#size-cells = <0>;
+			};
+		};
+
+		ethernet1: ethernet at fffe4800 {
+			compatible = "brcm,bcm6358-emac";
+			reg = <0xfffe4800 0x2dc>;
+
+			clocks = <&periph_clk BCM6358_CLK_ENET1>;
+
+			interrupt-parent = <&periph_intc>;
+			interrupts = <BCM6358_IRQ_EMAC1>,
+				     <BCM6358_IRQ_EMAC1_RX_DMA>,
+				     <BCM6358_IRQ_EMAC1_TX_DMA>;
+			interrupt-names = "emac",
+					  "rx",
+					  "tx";
+
+			brcm,iudma = <&iudma>;
+			brcm,external-mii;
+
+			dma-rx = <2>;
+			dma-tx = <3>;
+
+			status = "disabled";
+
+			mdio1: mdio {
+				#address-cells = <1>;
+				#size-cells = <0>;
+			};
+		};
+
+		iudma: dma at fffe5000 {
+			#address-cells = <1>;
+			#size-cells = <1>;
+			compatible = "brcm,bcm6358-iudma";
+			reg = <0xfffe5000 0x24>,
+			      <0xfffe5100 0x80>,
+			      <0xfffe5200 0x80>;
+			reg-names = "dma",
+				    "dma-channels",
+				    "dma-sram";
+
+			dma-channels = <8>;
+
+			clocks = <&periph_clk BCM6358_CLK_EMUSB>,
+				 <&periph_clk BCM6358_CLK_USBSU>,
+				 <&periph_clk BCM6358_CLK_EPHY>,
+				 <&periph_clk BCM6358_CLK_ENET>;
+
+			resets = <&periph_rst BCM6358_RST_ENET>,
+				 <&periph_rst BCM6358_RST_EPHY>;
+
+			status = "disabled";
+		};
 	};
 };
diff --git a/target/linux/bmips/files/drivers/net/ethernet/broadcom/bcm6348-enet.c b/target/linux/bmips/files/drivers/net/ethernet/broadcom/bcm6348-enet.c
new file mode 100644
index 0000000000..06a9df1c0a
--- /dev/null
+++ b/target/linux/bmips/files/drivers/net/ethernet/broadcom/bcm6348-enet.c
@@ -0,0 +1,1594 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * BCM6348 Ethernet Controller Driver
+ *
+ * Copyright (C) 2020 Álvaro Fernández Rojas <noltari at gmail.com>
+ * Copyright (C) 2015 Jonas Gorski <jonas.gorski at gmail.com>
+ * Copyright (C) 2008 Maxime Bizon <mbizon at freebox.fr>
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/etherdevice.h>
+#include <linux/if_vlan.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/mtd/mtd.h>
+#include <linux/of_address.h>
+#include <linux/of_clk.h>
+#include <linux/of_irq.h>
+#include <linux/of_mdio.h>
+#include <linux/of_net.h>
+#include <linux/of_platform.h>
+#include <linux/phy.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+
+/* DMA channels */
+#define DMA_CHAN_WIDTH			0x10
+
+/* Controller Configuration Register */
+#define DMA_CFG_REG			0x0
+#define DMA_CFG_EN_SHIFT		0
+#define DMA_CFG_EN_MASK			(1 << DMA_CFG_EN_SHIFT)
+#define DMA_CFG_FLOWCH_MASK(x)		(1 << ((x >> 1) + 1))
+
+/* Flow Control Descriptor Low Threshold register */
+#define DMA_FLOWCL_REG(x)		(0x4 + (x) * 6)
+
+/* Flow Control Descriptor High Threshold register */
+#define DMA_FLOWCH_REG(x)		(0x8 + (x) * 6)
+
+/* Flow Control Descriptor Buffer Alloca Threshold register */
+#define DMA_BUFALLOC_REG(x)		(0xc + (x) * 6)
+#define DMA_BUFALLOC_FORCE_SHIFT	31
+#define DMA_BUFALLOC_FORCE_MASK		(1 << DMA_BUFALLOC_FORCE_SHIFT)
+
+/* Channel Configuration register */
+#define DMAC_CHANCFG_REG		0x0
+#define DMAC_CHANCFG_EN_SHIFT		0
+#define DMAC_CHANCFG_EN_MASK		(1 << DMAC_CHANCFG_EN_SHIFT)
+#define DMAC_CHANCFG_PKTHALT_SHIFT	1
+#define DMAC_CHANCFG_PKTHALT_MASK	(1 << DMAC_CHANCFG_PKTHALT_SHIFT)
+#define DMAC_CHANCFG_BUFHALT_SHIFT	2
+#define DMAC_CHANCFG_BUFHALT_MASK	(1 << DMAC_CHANCFG_BUFHALT_SHIFT)
+#define DMAC_CHANCFG_CHAINING_SHIFT	2
+#define DMAC_CHANCFG_CHAINING_MASK	(1 << DMAC_CHANCFG_CHAINING_SHIFT)
+#define DMAC_CHANCFG_WRAP_EN_SHIFT	3
+#define DMAC_CHANCFG_WRAP_EN_MASK	(1 << DMAC_CHANCFG_WRAP_EN_SHIFT)
+#define DMAC_CHANCFG_FLOWC_EN_SHIFT	4
+#define DMAC_CHANCFG_FLOWC_EN_MASK	(1 << DMAC_CHANCFG_FLOWC_EN_SHIFT)
+
+/* Interrupt Control/Status register */
+#define DMAC_IR_REG			0x4
+#define DMAC_IR_BUFDONE_MASK		(1 << 0)
+#define DMAC_IR_PKTDONE_MASK		(1 << 1)
+#define DMAC_IR_NOTOWNER_MASK		(1 << 2)
+
+/* Interrupt Mask register */
+#define DMAC_IRMASK_REG			0x8
+
+/* Maximum Burst Length */
+#define DMAC_MAXBURST_REG		0xc
+
+/* Ring Start Address register */
+#define DMAS_RSTART_REG			0x0
+
+/* State Ram Word 2 */
+#define DMAS_SRAM2_REG			0x4
+
+/* State Ram Word 3 */
+#define DMAS_SRAM3_REG			0x8
+
+/* State Ram Word 4 */
+#define DMAS_SRAM4_REG			0xc
+
+struct bcm6348_iudma_desc {
+	u32 len_stat;
+	u32 address;
+};
+
+/* control */
+#define DMADESC_LENGTH_SHIFT		16
+#define DMADESC_LENGTH_MASK		(0xfff << DMADESC_LENGTH_SHIFT)
+#define DMADESC_OWNER_MASK		(1 << 15)
+#define DMADESC_EOP_MASK		(1 << 14)
+#define DMADESC_SOP_MASK		(1 << 13)
+#define DMADESC_ESOP_MASK		(DMADESC_EOP_MASK | DMADESC_SOP_MASK)
+#define DMADESC_WRAP_MASK		(1 << 12)
+
+/* status */
+#define DMADESC_UNDER_MASK		(1 << 9)
+#define DMADESC_APPEND_CRC		(1 << 8)
+#define DMADESC_OVSIZE_MASK		(1 << 4)
+#define DMADESC_RXER_MASK		(1 << 2)
+#define DMADESC_CRC_MASK		(1 << 1)
+#define DMADESC_OV_MASK			(1 << 0)
+#define DMADESC_ERR_MASK		(DMADESC_UNDER_MASK | \
+					 DMADESC_OVSIZE_MASK | \
+					 DMADESC_RXER_MASK | \
+					 DMADESC_CRC_MASK | \
+					 DMADESC_OV_MASK)
+
+struct bcm6348_iudma {
+	void __iomem *dma_base;
+	void __iomem *dma_chan;
+	void __iomem *dma_sram;
+
+	spinlock_t dma_base_lock;
+
+	struct clk **clock;
+	unsigned int num_clocks;
+
+	struct reset_control **reset;
+	unsigned int num_resets;
+
+	unsigned int dma_channels;
+};
+
+static inline u32 dma_readl(struct bcm6348_iudma *iudma, u32 off)
+{
+	u32 val;
+
+	spin_lock(&iudma->dma_base_lock);
+	val = __raw_readl(iudma->dma_base + off);
+	spin_unlock(&iudma->dma_base_lock);
+
+	return val;
+}
+
+static inline void dma_writel(struct bcm6348_iudma *iudma, u32 val, u32 off)
+{
+	spin_lock(&iudma->dma_base_lock);
+	__raw_writel(val, iudma->dma_base + off);
+	spin_unlock(&iudma->dma_base_lock);
+}
+
+static inline u32 dmac_readl(struct bcm6348_iudma *iudma, u32 off, int chan)
+{
+	return __raw_readl(iudma->dma_chan + chan * DMA_CHAN_WIDTH + off);
+}
+
+static inline void dmac_writel(struct bcm6348_iudma *iudma, u32 val, u32 off,
+			       int chan)
+{
+	__raw_writel(val, iudma->dma_chan + chan * DMA_CHAN_WIDTH + off);
+}
+
+static inline void dmas_writel(struct bcm6348_iudma *iudma, u32 val, u32 off,
+			       int chan)
+{
+	__raw_writel(val, iudma->dma_sram + chan * DMA_CHAN_WIDTH + off);
+}
+
+static void bcm6348_iudma_chan_stop(struct bcm6348_iudma *iudma, int chan)
+{
+	int limit = 1000;
+
+	dmac_writel(iudma, 0, DMAC_CHANCFG_REG, chan);
+
+	do {
+		u32 val;
+
+		val = dmac_readl(iudma, DMAC_CHANCFG_REG, chan);
+		if (!(val & DMAC_CHANCFG_EN_MASK))
+			break;
+
+		udelay(1);
+	} while (limit--);
+}
+
+static int bcm6348_iudma_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *node = dev->of_node;
+	struct bcm6348_iudma *iudma;
+	unsigned i;
+	int num_resets;
+	int ret;
+
+	iudma = devm_kzalloc(dev, sizeof(*iudma), GFP_KERNEL);
+	if (!iudma)
+		return -ENOMEM;
+
+	if (of_property_read_u32(node, "dma-channels", &iudma->dma_channels))
+		return -ENODEV;
+
+	iudma->dma_base = devm_platform_ioremap_resource_byname(pdev, "dma");
+	if (IS_ERR_OR_NULL(iudma->dma_base))
+		return PTR_ERR(iudma->dma_base);
+
+	iudma->dma_chan = devm_platform_ioremap_resource_byname(pdev,
+								"dma-channels");
+	if (IS_ERR_OR_NULL(iudma->dma_chan))
+		return PTR_ERR(iudma->dma_chan);
+
+	iudma->dma_sram = devm_platform_ioremap_resource_byname(pdev,
+								"dma-sram");
+	if (IS_ERR_OR_NULL(iudma->dma_sram))
+		return PTR_ERR(iudma->dma_sram);
+
+	iudma->num_clocks = of_clk_get_parent_count(node);
+	if (iudma->num_clocks) {
+		iudma->clock = devm_kcalloc(dev, iudma->num_clocks,
+					    sizeof(struct clk *), GFP_KERNEL);
+		if (IS_ERR_OR_NULL(iudma->clock))
+			return PTR_ERR(iudma->clock);
+	}
+	for (i = 0; i < iudma->num_clocks; i++) {
+		iudma->clock[i] = of_clk_get(node, i);
+		if (IS_ERR_OR_NULL(iudma->clock[i])) {
+			dev_err(dev, "error getting iudma clock %d\n", i);
+			return PTR_ERR(iudma->clock[i]);
+		}
+
+		ret = clk_prepare_enable(iudma->clock[i]);
+		if (ret) {
+			dev_err(dev, "error enabling iudma clock %d\n", i);
+			return ret;
+		}
+	}
+
+	num_resets = of_count_phandle_with_args(node, "resets",
+						"#reset-cells");
+	if (num_resets > 0)
+		iudma->num_resets = num_resets;
+	else
+		iudma->num_resets = 0;
+	if (iudma->num_resets) {
+		iudma->reset = devm_kcalloc(dev, iudma->num_resets,
+					    sizeof(struct reset_control *),
+					    GFP_KERNEL);
+		if (IS_ERR_OR_NULL(iudma->reset))
+			return PTR_ERR(iudma->reset);
+	}
+	for (i = 0; i < iudma->num_resets; i++) {
+		iudma->reset[i] = devm_reset_control_get_by_index(dev, i);
+		if (IS_ERR_OR_NULL(iudma->reset[i])) {
+			dev_err(dev, "error getting iudma reset %d\n", i);
+			return PTR_ERR(iudma->reset[i]);
+		}
+
+		ret = reset_control_reset(iudma->reset[i]);
+		if (ret) {
+			dev_err(dev, "error performing iudma reset %d\n", i);
+			return ret;
+		}
+	}
+
+	dma_writel(iudma, 0, DMA_CFG_REG);
+	for (i = 0; i < iudma->dma_channels; i++)
+		bcm6348_iudma_chan_stop(iudma, i);
+	dma_writel(iudma, DMA_CFG_EN_MASK, DMA_CFG_REG);
+
+	spin_lock_init(&iudma->dma_base_lock);
+
+	dev_info(dev, "bcm6348-iudma @ 0x%px\n", iudma->dma_base);
+
+	platform_set_drvdata(pdev, iudma);
+
+	return 0;
+}
+
+static const struct of_device_id bcm6348_iudma_of_match[] = {
+	{ .compatible = "brcm,bcm6338-iudma", },
+	{ .compatible = "brcm,bcm6348-iudma", },
+	{ .compatible = "brcm,bcm6358-iudma", },
+	{ /* sentinel */ },
+};
+
+static struct platform_driver bcm6348_iudma_driver = {
+	.driver = {
+		.name = "bcm6348-iudma",
+		.of_match_table = of_match_ptr(bcm6348_iudma_of_match),
+	},
+	.probe	= bcm6348_iudma_probe,
+};
+builtin_platform_driver(bcm6348_iudma_driver);
+
+/*
+ * BCM6348 Eternet MACs
+ */
+
+/* MTU */
+#define ENET_MAX_MTU			2046
+
+#define ENET_TAG_SIZE			6
+#define ENET_MTU_OVERHEAD		(VLAN_ETH_HLEN + VLAN_HLEN + \
+					 ENET_TAG_SIZE)
+
+/* Default number of descriptor */
+#define ENET_DEF_RX_DESC		64
+#define ENET_DEF_TX_DESC		32
+#define ENET_DEF_CPY_BREAK		128
+
+/* Maximum burst len for dma (4 bytes unit) */
+#define ENET_DMA_MAXBURST		8
+
+/* Receiver Configuration register */
+#define ENET_RXCFG_REG			0x0
+#define ENET_RXCFG_ALLMCAST_SHIFT	1
+#define ENET_RXCFG_ALLMCAST_MASK	(1 << ENET_RXCFG_ALLMCAST_SHIFT)
+#define ENET_RXCFG_PROMISC_SHIFT	3
+#define ENET_RXCFG_PROMISC_MASK		(1 << ENET_RXCFG_PROMISC_SHIFT)
+#define ENET_RXCFG_LOOPBACK_SHIFT	4
+#define ENET_RXCFG_LOOPBACK_MASK	(1 << ENET_RXCFG_LOOPBACK_SHIFT)
+#define ENET_RXCFG_ENFLOW_SHIFT		5
+#define ENET_RXCFG_ENFLOW_MASK		(1 << ENET_RXCFG_ENFLOW_SHIFT)
+
+/* Receive Maximum Length register */
+#define ENET_RXMAXLEN_REG		0x4
+#define ENET_RXMAXLEN_SHIFT		0
+#define ENET_RXMAXLEN_MASK		(0x7ff << ENET_RXMAXLEN_SHIFT)
+
+/* Transmit Maximum Length register */
+#define ENET_TXMAXLEN_REG		0x8
+#define ENET_TXMAXLEN_SHIFT		0
+#define ENET_TXMAXLEN_MASK		(0x7ff << ENET_TXMAXLEN_SHIFT)
+
+/* MII Status/Control register */
+#define ENET_MIISC_REG			0x10
+#define ENET_MIISC_MDCFREQDIV_SHIFT	0
+#define ENET_MIISC_MDCFREQDIV_MASK	(0x7f << ENET_MIISC_MDCFREQDIV_SHIFT)
+#define ENET_MIISC_PREAMBLEEN_SHIFT	7
+#define ENET_MIISC_PREAMBLEEN_MASK	(1 << ENET_MIISC_PREAMBLEEN_SHIFT)
+
+/* MII Data register */
+#define ENET_MIID_REG			0x14
+#define ENET_MIID_DATA_SHIFT		0
+#define ENET_MIID_DATA_MASK		(0xffff << ENET_MIID_DATA_SHIFT)
+#define ENET_MIID_TA_SHIFT		16
+#define ENET_MIID_TA_MASK		(0x3 << ENET_MIID_TA_SHIFT)
+#define ENET_MIID_REG_SHIFT		18
+#define ENET_MIID_REG_MASK		(0x1f << ENET_MIID_REG_SHIFT)
+#define ENET_MIID_PHY_SHIFT		23
+#define ENET_MIID_PHY_MASK		(0x1f << ENET_MIID_PHY_SHIFT)
+#define ENET_MIID_OP_SHIFT		28
+#define ENET_MIID_OP_WRITE		(0x5 << ENET_MIID_OP_SHIFT)
+#define ENET_MIID_OP_READ		(0x6 << ENET_MIID_OP_SHIFT)
+
+/* Ethernet Interrupt Mask register */
+#define ENET_IRMASK_REG			0x18
+
+/* Ethernet Interrupt register */
+#define ENET_IR_REG			0x1c
+#define ENET_IR_MII			BIT(0)
+#define ENET_IR_MIB			BIT(1)
+#define ENET_IR_FLOWC			BIT(2)
+
+/* Ethernet Control register */
+#define ENET_CTL_REG			0x2c
+#define ENET_CTL_ENABLE_SHIFT		0
+#define ENET_CTL_ENABLE_MASK		(1 << ENET_CTL_ENABLE_SHIFT)
+#define ENET_CTL_DISABLE_SHIFT		1
+#define ENET_CTL_DISABLE_MASK		(1 << ENET_CTL_DISABLE_SHIFT)
+#define ENET_CTL_SRESET_SHIFT		2
+#define ENET_CTL_SRESET_MASK		(1 << ENET_CTL_SRESET_SHIFT)
+#define ENET_CTL_EPHYSEL_SHIFT		3
+#define ENET_CTL_EPHYSEL_MASK		(1 << ENET_CTL_EPHYSEL_SHIFT)
+
+/* Transmit Control register */
+#define ENET_TXCTL_REG			0x30
+#define ENET_TXCTL_FD_SHIFT		0
+#define ENET_TXCTL_FD_MASK		(1 << ENET_TXCTL_FD_SHIFT)
+
+/* Transmit Watermask register */
+#define ENET_TXWMARK_REG		0x34
+#define ENET_TXWMARK_WM_SHIFT		0
+#define ENET_TXWMARK_WM_MASK		(0x3f << ENET_TXWMARK_WM_SHIFT)
+
+/* MIB Control register */
+#define ENET_MIBCTL_REG			0x38
+#define ENET_MIBCTL_RDCLEAR_SHIFT	0
+#define ENET_MIBCTL_RDCLEAR_MASK	(1 << ENET_MIBCTL_RDCLEAR_SHIFT)
+
+/* Perfect Match Data Low register */
+#define ENET_PML_REG(x)			(0x58 + (x) * 8)
+#define ENET_PMH_REG(x)			(0x5c + (x) * 8)
+#define ENET_PMH_DATAVALID_SHIFT	16
+#define ENET_PMH_DATAVALID_MASK		(1 << ENET_PMH_DATAVALID_SHIFT)
+
+/* MIB register */
+#define ENET_MIB_REG(x)			(0x200 + (x) * 4)
+#define ENET_MIB_REG_COUNT		55
+
+/*
+ * TX transmit threshold (4 bytes unit), FIFO is 256 bytes, the value
+ * must be low enough so that a DMA transfer of above burst length can
+ * not overflow the fifo
+ */
+#define ENET_TX_FIFO_TRESH		32
+
+struct bcm6348_emac {
+	struct bcm6348_iudma *iudma;
+	void __iomem *base;
+
+	struct clk **clock;
+	unsigned int num_clocks;
+
+	struct reset_control **reset;
+	unsigned int num_resets;
+
+	int copybreak;
+
+	int irq_rx;
+	int irq_tx;
+
+	/* hw view of rx & tx dma ring */
+	dma_addr_t rx_desc_dma;
+	dma_addr_t tx_desc_dma;
+
+	/* allocated size (in bytes) for rx & tx dma ring */
+	unsigned int rx_desc_alloc_size;
+	unsigned int tx_desc_alloc_size;
+
+	struct napi_struct napi;
+
+	/* dma channel id for rx */
+	int rx_chan;
+
+	/* number of dma desc in rx ring */
+	int rx_ring_size;
+
+	/* cpu view of rx dma ring */
+	struct bcm6348_iudma_desc *rx_desc_cpu;
+
+	/* current number of armed descriptor given to hardware for rx */
+	int rx_desc_count;
+
+	/* next rx descriptor to fetch from hardware */
+	int rx_curr_desc;
+
+	/* next dirty rx descriptor to refill */
+	int rx_dirty_desc;
+
+	/* size of allocated rx skbs */
+	unsigned int rx_skb_size;
+
+	/* list of skb given to hw for rx */
+	struct sk_buff **rx_skb;
+
+	/* used when rx skb allocation failed, so we defer rx queue
+	 * refill */
+	struct timer_list rx_timeout;
+
+	/* lock rx_timeout against rx normal operation */
+	spinlock_t rx_lock;
+
+	/* dma channel id for tx */
+	int tx_chan;
+
+	/* number of dma desc in tx ring */
+	int tx_ring_size;
+
+	/* cpu view of tx dma ring */
+	struct bcm6348_iudma_desc *tx_desc_cpu;
+
+	/* number of available descriptor for tx */
+	int tx_desc_count;
+
+	/* next tx descriptor avaiable */
+	int tx_curr_desc;
+
+	/* next dirty tx descriptor to reclaim */
+	int tx_dirty_desc;
+
+	/* list of skb given to hw for tx */
+	struct sk_buff **tx_skb;
+
+	/* lock used by tx reclaim and xmit */
+	spinlock_t tx_lock;
+
+	/* network device reference */
+	struct net_device *net_dev;
+
+	/* platform device reference */
+	struct platform_device *pdev;
+
+	/* external mii bus */
+	bool ext_mii;
+};
+
+static inline void emac_writel(struct bcm6348_emac *emac, u32 val, u32 off)
+{
+	__raw_writel(val, emac->base + off);
+}
+
+static inline u32 emac_readl(struct bcm6348_emac *emac, u32 off)
+{
+	return __raw_readl(emac->base + off);
+}
+
+/*
+ * refill rx queue
+ */
+static int bcm6348_emac_refill_rx(struct net_device *ndev)
+{
+	struct bcm6348_emac *emac = netdev_priv(ndev);
+	struct bcm6348_iudma *iudma = emac->iudma;
+	struct platform_device *pdev = emac->pdev;
+	struct device *dev = &pdev->dev;
+
+	while (emac->rx_desc_count < emac->rx_ring_size) {
+		struct bcm6348_iudma_desc *desc;
+		struct sk_buff *skb;
+		dma_addr_t p;
+		int desc_idx;
+		u32 len_stat;
+
+		desc_idx = emac->rx_dirty_desc;
+		desc = &emac->rx_desc_cpu[desc_idx];
+
+		if (!emac->rx_skb[desc_idx]) {
+			skb = netdev_alloc_skb(ndev, emac->rx_skb_size);
+			if (!skb)
+				break;
+			emac->rx_skb[desc_idx] = skb;
+			p = dma_map_single(dev, skb->data, emac->rx_skb_size,
+					   DMA_FROM_DEVICE);
+			desc->address = p;
+		}
+
+		len_stat = emac->rx_skb_size << DMADESC_LENGTH_SHIFT;
+		len_stat |= DMADESC_OWNER_MASK;
+		if (emac->rx_dirty_desc == emac->rx_ring_size - 1) {
+			len_stat |= DMADESC_WRAP_MASK;
+			emac->rx_dirty_desc = 0;
+		} else {
+			emac->rx_dirty_desc++;
+		}
+		wmb();
+		desc->len_stat = len_stat;
+
+		emac->rx_desc_count++;
+
+		/* tell dma engine we allocated one buffer */
+		dma_writel(iudma, 1, DMA_BUFALLOC_REG(emac->rx_chan));
+	}
+
+	/* If rx ring is still empty, set a timer to try allocating
+	 * again at a later time. */
+	if (emac->rx_desc_count == 0 && netif_running(ndev)) {
+		dev_warn(dev, "unable to refill rx ring\n");
+		emac->rx_timeout.expires = jiffies + HZ;
+		add_timer(&emac->rx_timeout);
+	}
+
+	return 0;
+}
+
+/*
+ * timer callback to defer refill rx queue in case we're OOM
+ */
+static void bcm6348_emac_refill_rx_timer(struct timer_list *t)
+{
+	struct bcm6348_emac *emac = from_timer(emac, t, rx_timeout);
+	struct net_device *ndev = emac->net_dev;
+
+	spin_lock(&emac->rx_lock);
+	bcm6348_emac_refill_rx(ndev);
+	spin_unlock(&emac->rx_lock);
+}
+
+/*
+ * extract packet from rx queue
+ */
+static int bcm6348_emac_receive_queue(struct net_device *ndev, int budget)
+{
+	struct bcm6348_emac *emac = netdev_priv(ndev);
+	struct bcm6348_iudma *iudma = emac->iudma;
+	struct platform_device *pdev = emac->pdev;
+	struct device *dev = &pdev->dev;
+	int processed = 0;
+
+	/* don't scan ring further than number of refilled
+	 * descriptor */
+	if (budget > emac->rx_desc_count)
+		budget = emac->rx_desc_count;
+
+	do {
+		struct bcm6348_iudma_desc *desc;
+		struct sk_buff *skb;
+		int desc_idx;
+		u32 len_stat;
+		unsigned int len;
+
+		desc_idx = emac->rx_curr_desc;
+		desc = &emac->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++;
+		emac->rx_curr_desc++;
+		if (emac->rx_curr_desc == emac->rx_ring_size)
+			emac->rx_curr_desc = 0;
+		emac->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) {
+			ndev->stats.rx_dropped++;
+			continue;
+		}
+
+		/* valid packet */
+		skb = emac->rx_skb[desc_idx];
+		len = (len_stat & DMADESC_LENGTH_MASK)
+		      >> DMADESC_LENGTH_SHIFT;
+		/* don't include FCS */
+		len -= 4;
+
+		if (len < emac->copybreak) {
+			struct sk_buff *nskb;
+
+			nskb = napi_alloc_skb(&emac->napi, len);
+			if (!nskb) {
+				/* forget packet, just rearm desc */
+				ndev->stats.rx_dropped++;
+				continue;
+			}
+
+			dma_sync_single_for_cpu(dev, desc->address,
+						len, DMA_FROM_DEVICE);
+			memcpy(nskb->data, skb->data, len);
+			dma_sync_single_for_device(dev, desc->address,
+						   len, DMA_FROM_DEVICE);
+			skb = nskb;
+		} else {
+			dma_unmap_single(dev, desc->address,
+					 emac->rx_skb_size, DMA_FROM_DEVICE);
+			emac->rx_skb[desc_idx] = NULL;
+		}
+
+		skb_put(skb, len);
+		skb->protocol = eth_type_trans(skb, ndev);
+		ndev->stats.rx_packets++;
+		ndev->stats.rx_bytes += len;
+		netif_receive_skb(skb);
+	} while (--budget > 0);
+
+	if (processed || !emac->rx_desc_count) {
+		bcm6348_emac_refill_rx(ndev);
+
+		/* kick rx dma */
+		dmac_writel(iudma, DMAC_CHANCFG_EN_MASK, DMAC_CHANCFG_REG,
+			    emac->rx_chan);
+	}
+
+	return processed;
+}
+
+/*
+ * try to or force reclaim of transmitted buffers
+ */
+static int bcm6348_emac_tx_reclaim(struct net_device *ndev, int force)
+{
+	struct bcm6348_emac *emac = netdev_priv(ndev);
+	struct platform_device *pdev = emac->pdev;
+	struct device *dev = &pdev->dev;
+	int released = 0;
+
+	while (emac->tx_desc_count < emac->tx_ring_size) {
+		struct bcm6348_iudma_desc *desc;
+		struct sk_buff *skb;
+
+		/* We run in a bh and fight against start_xmit, which
+		 * is called with bh disabled */
+		spin_lock(&emac->tx_lock);
+
+		desc = &emac->tx_desc_cpu[emac->tx_dirty_desc];
+
+		if (!force && (desc->len_stat & DMADESC_OWNER_MASK)) {
+			spin_unlock(&emac->tx_lock);
+			break;
+		}
+
+		/* ensure other field of the descriptor were not read
+		 * before we checked ownership */
+		rmb();
+
+		skb = emac->tx_skb[emac->tx_dirty_desc];
+		emac->tx_skb[emac->tx_dirty_desc] = NULL;
+		dma_unmap_single(dev, desc->address, skb->len, DMA_TO_DEVICE);
+
+		emac->tx_dirty_desc++;
+		if (emac->tx_dirty_desc == emac->tx_ring_size)
+			emac->tx_dirty_desc = 0;
+		emac->tx_desc_count++;
+
+		spin_unlock(&emac->tx_lock);
+
+		if (desc->len_stat & DMADESC_UNDER_MASK)
+			ndev->stats.tx_errors++;
+
+		dev_kfree_skb(skb);
+		released++;
+	}
+
+	if (netif_queue_stopped(ndev) && released)
+		netif_wake_queue(ndev);
+
+	return released;
+}
+
+static int bcm6348_emac_poll(struct napi_struct *napi, int budget)
+{
+	struct bcm6348_emac *emac = container_of(napi, struct bcm6348_emac,
+						 napi);
+	struct bcm6348_iudma *iudma = emac->iudma;
+	struct net_device *ndev = emac->net_dev;
+	int rx_work_done;
+
+	/* ack interrupts */
+	dmac_writel(iudma, DMAC_IR_PKTDONE_MASK, DMAC_IR_REG,
+		    emac->rx_chan);
+	dmac_writel(iudma, DMAC_IR_PKTDONE_MASK, DMAC_IR_REG,
+		    emac->tx_chan);
+
+	/* reclaim sent skb */
+	bcm6348_emac_tx_reclaim(ndev, 0);
+
+	spin_lock(&emac->rx_lock);
+	rx_work_done = bcm6348_emac_receive_queue(ndev, budget);
+	spin_unlock(&emac->rx_lock);
+
+	if (rx_work_done >= budget) {
+		/* rx queue is not yet empty/clean */
+		return rx_work_done;
+	}
+
+	/* no more packet in rx/tx queue, remove device from poll
+	 * queue */
+	napi_complete_done(napi, rx_work_done);
+
+	/* restore rx/tx interrupt */
+	dmac_writel(iudma, DMAC_IR_PKTDONE_MASK, DMAC_IRMASK_REG,
+		    emac->rx_chan);
+	dmac_writel(iudma, DMAC_IR_PKTDONE_MASK, DMAC_IRMASK_REG,
+		    emac->tx_chan);
+
+	return rx_work_done;
+}
+
+/*
+ * emac interrupt handler
+ */
+static irqreturn_t bcm6348_emac_isr_mac(int irq, void *dev_id)
+{
+	struct net_device *ndev = dev_id;
+	struct bcm6348_emac *emac = netdev_priv(ndev);
+	u32 stat;
+
+	stat = emac_readl(emac, ENET_IR_REG);
+	if (!(stat & ENET_IR_MIB))
+		return IRQ_NONE;
+
+	/* clear & mask interrupt */
+	emac_writel(emac, ENET_IR_MIB, ENET_IR_REG);
+	emac_writel(emac, 0, ENET_IRMASK_REG);
+
+	return IRQ_HANDLED;
+}
+
+/*
+ * rx/tx dma interrupt handler
+ */
+static irqreturn_t bcm6348_emac_isr_dma(int irq, void *dev_id)
+{
+	struct net_device *ndev = dev_id;
+	struct bcm6348_emac *emac = netdev_priv(ndev);
+	struct bcm6348_iudma *iudma = emac->iudma;
+
+	/* mask rx/tx interrupts */
+	dmac_writel(iudma, 0, DMAC_IRMASK_REG, emac->rx_chan);
+	dmac_writel(iudma, 0, DMAC_IRMASK_REG, emac->tx_chan);
+
+	napi_schedule(&emac->napi);
+
+	return IRQ_HANDLED;
+}
+
+/*
+ * tx request callback
+ */
+static netdev_tx_t bcm6348_emac_start_xmit(struct sk_buff *skb,
+					   struct net_device *ndev)
+{
+	struct bcm6348_emac *emac = netdev_priv(ndev);
+	struct bcm6348_iudma *iudma = emac->iudma;
+	struct platform_device *pdev = emac->pdev;
+	struct device *dev = &pdev->dev;
+	struct bcm6348_iudma_desc *desc;
+	u32 len_stat;
+	netdev_tx_t ret;
+
+	/* lock against tx reclaim */
+	spin_lock(&emac->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(!emac->tx_desc_count)) {
+		netif_stop_queue(ndev);
+		dev_err(dev, "xmit called with no tx desc available?\n");
+		ret = NETDEV_TX_BUSY;
+		goto out_unlock;
+	}
+
+	/* point to the next available desc */
+	desc = &emac->tx_desc_cpu[emac->tx_curr_desc];
+	emac->tx_skb[emac->tx_curr_desc] = skb;
+
+	/* fill descriptor */
+	desc->address = dma_map_single(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;
+
+	emac->tx_curr_desc++;
+	if (emac->tx_curr_desc == emac->tx_ring_size) {
+		emac->tx_curr_desc = 0;
+		len_stat |= DMADESC_WRAP_MASK;
+	}
+	emac->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 */
+	dmac_writel(iudma, DMAC_CHANCFG_EN_MASK, DMAC_CHANCFG_REG,
+		    emac->tx_chan);
+
+	/* stop queue if no more desc available */
+	if (!emac->tx_desc_count)
+		netif_stop_queue(ndev);
+
+	ndev->stats.tx_bytes += skb->len;
+	ndev->stats.tx_packets++;
+	ret = NETDEV_TX_OK;
+
+out_unlock:
+	spin_unlock(&emac->tx_lock);
+	return ret;
+}
+
+/*
+ * Change the interface's emac address.
+ */
+static int bcm6348_emac_set_mac_address(struct net_device *ndev, void *p)
+{
+	struct bcm6348_emac *emac = netdev_priv(ndev);
+	struct sockaddr *addr = p;
+	u32 val;
+
+	eth_hw_addr_set(ndev, addr->sa_data);
+
+	/* use perfect match register 0 to store my emac address */
+	val = (ndev->dev_addr[2] << 24) | (ndev->dev_addr[3] << 16) |
+		(ndev->dev_addr[4] << 8) | ndev->dev_addr[5];
+	emac_writel(emac, val, ENET_PML_REG(0));
+
+	val = (ndev->dev_addr[0] << 8 | ndev->dev_addr[1]);
+	val |= ENET_PMH_DATAVALID_MASK;
+	emac_writel(emac, val, ENET_PMH_REG(0));
+
+	return 0;
+}
+
+/*
+ * Change rx mode (promiscuous/allmulti) and update multicast list
+ */
+static void bcm6348_emac_set_multicast_list(struct net_device *ndev)
+{
+	struct bcm6348_emac *emac = netdev_priv(ndev);
+	struct netdev_hw_addr *ha;
+	u32 val;
+	unsigned int i;
+
+	val = emac_readl(emac, ENET_RXCFG_REG);
+
+	if (ndev->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 ((ndev->flags & IFF_ALLMULTI) || netdev_mc_count(ndev) > 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) {
+		emac_writel(emac, val, ENET_RXCFG_REG);
+		return;
+	}
+
+	i = 0;
+	netdev_for_each_mc_addr(ha, ndev) {
+		u8 *dmi_addr;
+		u32 tmp;
+
+		if (i == 3)
+			break;
+
+		/* update perfect match registers */
+		dmi_addr = ha->addr;
+		tmp = (dmi_addr[2] << 24) | (dmi_addr[3] << 16) |
+			(dmi_addr[4] << 8) | dmi_addr[5];
+		emac_writel(emac, tmp, ENET_PML_REG(i + 1));
+
+		tmp = (dmi_addr[0] << 8 | dmi_addr[1]);
+		tmp |= ENET_PMH_DATAVALID_MASK;
+		emac_writel(emac, tmp, ENET_PMH_REG(i++ + 1));
+	}
+
+	for (; i < 3; i++) {
+		emac_writel(emac, 0, ENET_PML_REG(i + 1));
+		emac_writel(emac, 0, ENET_PMH_REG(i + 1));
+	}
+
+	emac_writel(emac, val, ENET_RXCFG_REG);
+}
+
+/*
+ * disable emac
+ */
+static void bcm6348_emac_disable_mac(struct bcm6348_emac *emac)
+{
+	int limit;
+	u32 val;
+
+	val = emac_readl(emac, ENET_CTL_REG);
+	val |= ENET_CTL_DISABLE_MASK;
+	emac_writel(emac, val, ENET_CTL_REG);
+
+	limit = 1000;
+	do {
+		val = emac_readl(emac, ENET_CTL_REG);
+		if (!(val & ENET_CTL_DISABLE_MASK))
+			break;
+		udelay(1);
+	} while (limit--);
+}
+
+static int bcm6348_emac_open(struct net_device *ndev)
+{
+	struct bcm6348_emac *emac = netdev_priv(ndev);
+	struct bcm6348_iudma *iudma = emac->iudma;
+	struct platform_device *pdev = emac->pdev;
+	struct device *dev = &pdev->dev;
+	struct sockaddr addr;
+	unsigned int i, size;
+	int ret;
+	void *p;
+	u32 val;
+
+	/* mask all interrupts and request them */
+	emac_writel(emac, 0, ENET_IRMASK_REG);
+	dmac_writel(iudma, 0, DMAC_IRMASK_REG, emac->rx_chan);
+	dmac_writel(iudma, 0, DMAC_IRMASK_REG, emac->tx_chan);
+
+	ret = request_irq(ndev->irq, bcm6348_emac_isr_mac, 0, ndev->name,
+			  ndev);
+	if (ret)
+		return ret;
+
+	ret = request_irq(emac->irq_rx, bcm6348_emac_isr_dma,
+			  0, ndev->name, ndev);
+	if (ret)
+		goto out_freeirq;
+
+	ret = request_irq(emac->irq_tx, bcm6348_emac_isr_dma,
+			  0, ndev->name, ndev);
+	if (ret)
+		goto out_freeirq_rx;
+
+	/* initialize perfect match registers */
+	for (i = 0; i < 4; i++) {
+		emac_writel(emac, 0, ENET_PML_REG(i));
+		emac_writel(emac, 0, ENET_PMH_REG(i));
+	}
+
+	/* write device mac address */
+	memcpy(addr.sa_data, ndev->dev_addr, ETH_ALEN);
+	bcm6348_emac_set_mac_address(ndev, &addr);
+
+	/* allocate rx dma ring */
+	size = emac->rx_ring_size * sizeof(struct bcm6348_iudma_desc);
+	p = dma_alloc_coherent(dev, size, &emac->rx_desc_dma, GFP_KERNEL);
+	if (!p) {
+		dev_err(dev, "cannot allocate rx ring %u\n", size);
+		ret = -ENOMEM;
+		goto out_freeirq_tx;
+	}
+
+	memset(p, 0, size);
+	emac->rx_desc_alloc_size = size;
+	emac->rx_desc_cpu = p;
+
+	/* allocate tx dma ring */
+	size = emac->tx_ring_size * sizeof(struct bcm6348_iudma_desc);
+	p = dma_alloc_coherent(dev, size, &emac->tx_desc_dma, GFP_KERNEL);
+	if (!p) {
+		dev_err(dev, "cannot allocate tx ring\n");
+		ret = -ENOMEM;
+		goto out_free_rx_ring;
+	}
+
+	memset(p, 0, size);
+	emac->tx_desc_alloc_size = size;
+	emac->tx_desc_cpu = p;
+
+	emac->tx_skb = kzalloc(sizeof(struct sk_buff *) * emac->tx_ring_size,
+			       GFP_KERNEL);
+	if (!emac->tx_skb) {
+		dev_err(dev, "cannot allocate rx skb queue\n");
+		ret = -ENOMEM;
+		goto out_free_tx_ring;
+	}
+
+	emac->tx_desc_count = emac->tx_ring_size;
+	emac->tx_dirty_desc = 0;
+	emac->tx_curr_desc = 0;
+	spin_lock_init(&emac->tx_lock);
+
+	/* init & fill rx ring with skbs */
+	emac->rx_skb = kzalloc(sizeof(struct sk_buff *) * emac->rx_ring_size,
+			       GFP_KERNEL);
+	if (!emac->rx_skb) {
+		dev_err(dev, "cannot allocate rx skb queue\n");
+		ret = -ENOMEM;
+		goto out_free_tx_skb;
+	}
+
+	emac->rx_desc_count = 0;
+	emac->rx_dirty_desc = 0;
+	emac->rx_curr_desc = 0;
+
+	/* initialize flow control buffer allocation */
+	dma_writel(iudma, DMA_BUFALLOC_FORCE_MASK | 0,
+		   DMA_BUFALLOC_REG(emac->rx_chan));
+
+	if (bcm6348_emac_refill_rx(ndev)) {
+		dev_err(dev, "cannot allocate rx skb queue\n");
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	/* write rx & tx ring addresses */
+	dmas_writel(iudma, emac->rx_desc_dma,
+		    DMAS_RSTART_REG, emac->rx_chan);
+	dmas_writel(iudma, emac->tx_desc_dma,
+		    DMAS_RSTART_REG, emac->tx_chan);
+
+	/* clear remaining state ram for rx & tx channel */
+	dmas_writel(iudma, 0, DMAS_SRAM2_REG, emac->rx_chan);
+	dmas_writel(iudma, 0, DMAS_SRAM2_REG, emac->tx_chan);
+	dmas_writel(iudma, 0, DMAS_SRAM3_REG, emac->rx_chan);
+	dmas_writel(iudma, 0, DMAS_SRAM3_REG, emac->tx_chan);
+	dmas_writel(iudma, 0, DMAS_SRAM4_REG, emac->rx_chan);
+	dmas_writel(iudma, 0, DMAS_SRAM4_REG, emac->tx_chan);
+
+	/* set max rx/tx length */
+	emac_writel(emac, ndev->mtu, ENET_RXMAXLEN_REG);
+	emac_writel(emac, ndev->mtu, ENET_TXMAXLEN_REG);
+
+	/* set dma maximum burst len */
+	dmac_writel(iudma, ENET_DMA_MAXBURST,
+		    DMAC_MAXBURST_REG, emac->rx_chan);
+	dmac_writel(iudma, ENET_DMA_MAXBURST,
+		    DMAC_MAXBURST_REG, emac->tx_chan);
+
+	/* set correct transmit fifo watermark */
+	emac_writel(emac, ENET_TX_FIFO_TRESH, ENET_TXWMARK_REG);
+
+	/* set flow control low/high threshold to 1/3 / 2/3 */
+	val = emac->rx_ring_size / 3;
+	dma_writel(iudma, val, DMA_FLOWCL_REG(emac->rx_chan));
+	val = (emac->rx_ring_size * 2) / 3;
+	dma_writel(iudma, val, DMA_FLOWCH_REG(emac->rx_chan));
+
+	/* all set, enable emac and interrupts, start dma engine and
+	 * kick rx dma channel
+	 */
+	wmb();
+	val = emac_readl(emac, ENET_CTL_REG);
+	val |= ENET_CTL_ENABLE_MASK;
+	emac_writel(emac, val, ENET_CTL_REG);
+	dmac_writel(iudma, DMAC_CHANCFG_EN_MASK,
+		    DMAC_CHANCFG_REG, emac->rx_chan);
+
+	/* watch "mib counters about to overflow" interrupt */
+	emac_writel(emac, ENET_IR_MIB, ENET_IR_REG);
+	emac_writel(emac, ENET_IR_MIB, ENET_IRMASK_REG);
+
+	/* watch "packet transferred" interrupt in rx and tx */
+	dmac_writel(iudma, DMAC_IR_PKTDONE_MASK,
+		    DMAC_IR_REG, emac->rx_chan);
+	dmac_writel(iudma, DMAC_IR_PKTDONE_MASK,
+		    DMAC_IR_REG, emac->tx_chan);
+
+	/* make sure we enable napi before rx interrupt  */
+	napi_enable(&emac->napi);
+
+	dmac_writel(iudma, DMAC_IR_PKTDONE_MASK,
+		    DMAC_IRMASK_REG, emac->rx_chan);
+	dmac_writel(iudma, DMAC_IR_PKTDONE_MASK,
+		    DMAC_IRMASK_REG, emac->tx_chan);
+
+	netif_carrier_on(ndev);
+	netif_start_queue(ndev);
+
+	return 0;
+
+out:
+	for (i = 0; i < emac->rx_ring_size; i++) {
+		struct bcm6348_iudma_desc *desc;
+
+		if (!emac->rx_skb[i])
+			continue;
+
+		desc = &emac->rx_desc_cpu[i];
+		dma_unmap_single(dev, desc->address, emac->rx_skb_size,
+				 DMA_FROM_DEVICE);
+		kfree_skb(emac->rx_skb[i]);
+	}
+	kfree(emac->rx_skb);
+
+out_free_tx_skb:
+	kfree(emac->tx_skb);
+
+out_free_tx_ring:
+	dma_free_coherent(dev, emac->tx_desc_alloc_size,
+			  emac->tx_desc_cpu, emac->tx_desc_dma);
+
+out_free_rx_ring:
+	dma_free_coherent(dev, emac->rx_desc_alloc_size,
+			  emac->rx_desc_cpu, emac->rx_desc_dma);
+
+out_freeirq_tx:
+	if (emac->irq_tx != -1)
+		free_irq(emac->irq_tx, ndev);
+
+out_freeirq_rx:
+	free_irq(emac->irq_rx, ndev);
+
+out_freeirq:
+	return ret;
+}
+
+static int bcm6348_emac_stop(struct net_device *ndev)
+{
+	struct bcm6348_emac *emac = netdev_priv(ndev);
+	struct bcm6348_iudma *iudma = emac->iudma;
+	struct device *dev = &emac->pdev->dev;
+	unsigned int i;
+
+	netif_stop_queue(ndev);
+	napi_disable(&emac->napi);
+	del_timer_sync(&emac->rx_timeout);
+
+	/* mask all interrupts */
+	emac_writel(emac, 0, ENET_IRMASK_REG);
+	dmac_writel(iudma, 0, DMAC_IRMASK_REG, emac->rx_chan);
+	dmac_writel(iudma, 0, DMAC_IRMASK_REG, emac->tx_chan);
+
+	/* disable dma & emac */
+	bcm6348_iudma_chan_stop(iudma, emac->tx_chan);
+	bcm6348_iudma_chan_stop(iudma, emac->rx_chan);
+	bcm6348_emac_disable_mac(emac);
+
+	/* force reclaim of all tx buffers */
+	bcm6348_emac_tx_reclaim(ndev, 1);
+
+	/* free the rx skb ring */
+	for (i = 0; i < emac->rx_ring_size; i++) {
+		struct bcm6348_iudma_desc *desc;
+
+		if (!emac->rx_skb[i])
+			continue;
+
+		desc = &emac->rx_desc_cpu[i];
+		dma_unmap_single_attrs(dev, desc->address, emac->rx_skb_size,
+				       DMA_FROM_DEVICE,
+				       DMA_ATTR_SKIP_CPU_SYNC);
+		kfree_skb(emac->rx_skb[i]);
+	}
+
+	/* free remaining allocated memory */
+	kfree(emac->rx_skb);
+	kfree(emac->tx_skb);
+	dma_free_coherent(dev, emac->rx_desc_alloc_size, emac->rx_desc_cpu,
+			  emac->rx_desc_dma);
+	dma_free_coherent(dev, emac->tx_desc_alloc_size, emac->tx_desc_cpu,
+			  emac->tx_desc_dma);
+	free_irq(emac->irq_tx, ndev);
+	free_irq(emac->irq_rx, ndev);
+	free_irq(ndev->irq, ndev);
+
+	netdev_reset_queue(ndev);
+
+	return 0;
+}
+
+static const struct net_device_ops bcm6348_emac_ops = {
+	.ndo_open = bcm6348_emac_open,
+	.ndo_stop = bcm6348_emac_stop,
+	.ndo_start_xmit = bcm6348_emac_start_xmit,
+	.ndo_set_mac_address = bcm6348_emac_set_mac_address,
+	.ndo_set_rx_mode = bcm6348_emac_set_multicast_list,
+};
+
+static int bcm6348_emac_mdio_op(struct bcm6348_emac *emac, uint32_t data)
+{
+	int limit;
+
+	/* Make sure mii interrupt status is cleared */
+	emac_writel(emac, ENET_IR_MII, ENET_IR_REG);
+
+	/* Issue mii op */
+	emac_writel(emac, data, ENET_MIID_REG);
+	wmb();
+
+	/* busy wait on mii interrupt bit, with timeout */
+	limit = 1000;
+	do {
+		if (emac_readl(emac, ENET_IR_REG) & ENET_IR_MII)
+			break;
+		udelay(1);
+	} while (limit-- > 0);
+
+	return (limit < 0) ? 1 : 0;
+}
+
+static int bcm6348_emac_mdio_read(struct mii_bus *bus, int phy_id, int loc)
+{
+	struct bcm6348_emac *emac = bus->priv;
+	struct platform_device *pdev = emac->pdev;
+	struct device *dev = &pdev->dev;
+	uint32_t reg;
+
+	reg = 0x2 << ENET_MIID_TA_SHIFT;
+	reg |= loc << ENET_MIID_REG_SHIFT;
+	reg |= phy_id << ENET_MIID_PHY_SHIFT;
+	reg |= ENET_MIID_OP_READ;
+
+	if (bcm6348_emac_mdio_op(emac, reg)) {
+		dev_err(dev, "mdio_read: phy=%d loc=%x timeout!\n",
+			phy_id, loc);
+		return -EINVAL;
+	}
+
+	reg = emac_readl(emac, ENET_MIID_REG);
+	reg = (reg >> ENET_MIID_DATA_SHIFT) & ENET_MIID_DATA_MASK;
+
+	return (int) reg;
+}
+
+static int bcm6348_emac_mdio_write(struct mii_bus *bus, int phy_id,
+				   int loc, uint16_t val)
+{
+	struct bcm6348_emac *emac = bus->priv;
+	struct platform_device *pdev = emac->pdev;
+	struct device *dev = &pdev->dev;
+	uint32_t reg;
+
+	reg = (val << ENET_MIID_DATA_SHIFT) & ENET_MIID_DATA_MASK;
+	reg |= 0x2 << ENET_MIID_TA_SHIFT;
+	reg |= loc << ENET_MIID_REG_SHIFT;
+	reg |= phy_id << ENET_MIID_PHY_SHIFT;
+	reg |= ENET_MIID_OP_WRITE;
+
+	if (bcm6348_emac_mdio_op(emac, reg)) {
+		dev_err(dev, "mdio_write: phy=%d loc=%x timeout!\n",
+			phy_id, loc);
+		return -EINVAL;
+	}
+
+	bcm6348_emac_mdio_op(emac, reg);
+
+	return 0;
+}
+
+static int bcm6348_emac_mdio_init(struct bcm6348_emac *emac,
+				  struct device_node *np)
+{
+	struct platform_device *pdev = emac->pdev;
+	struct device *dev = &pdev->dev;
+	struct device_node *mnp;
+	struct mii_bus *mii_bus;
+	int ret;
+
+	mnp = of_get_child_by_name(np, "mdio");
+	if (!mnp)
+		return -ENODEV;
+
+	mii_bus = devm_mdiobus_alloc(dev);
+	if (!mii_bus) {
+		of_node_put(mnp);
+		return -ENOMEM;
+	}
+
+	mii_bus->priv = emac;
+	mii_bus->name = np->full_name;
+	snprintf(mii_bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(dev));
+	mii_bus->parent = dev;
+	mii_bus->read = bcm6348_emac_mdio_read;
+	mii_bus->write = bcm6348_emac_mdio_write;
+	mii_bus->phy_mask = 0x3f;
+
+	ret = devm_of_mdiobus_register(dev, mii_bus, mnp);
+	of_node_put(mnp);
+	if (ret) {
+		dev_err(dev, "MDIO bus registration failed\n");
+		return ret;
+	}
+
+	dev_info(dev, "MDIO bus init\n");
+
+	return 0;
+}
+
+/*
+ * preinit hardware to allow mii operation while device is down
+ */
+static void bcm6348_emac_hw_preinit(struct bcm6348_emac *emac)
+{
+	u32 val;
+	int limit;
+
+	/* make sure emac is disabled */
+	bcm6348_emac_disable_mac(emac);
+
+	/* soft reset emac */
+	val = ENET_CTL_SRESET_MASK;
+	emac_writel(emac, val, ENET_CTL_REG);
+	wmb();
+
+	limit = 1000;
+	do {
+		val = emac_readl(emac, ENET_CTL_REG);
+		if (!(val & ENET_CTL_SRESET_MASK))
+			break;
+		udelay(1);
+	} while (limit--);
+
+	/* select correct mii interface */
+	val = emac_readl(emac, ENET_CTL_REG);
+	if (emac->ext_mii)
+		val |= ENET_CTL_EPHYSEL_MASK;
+	else
+		val &= ~ENET_CTL_EPHYSEL_MASK;
+	emac_writel(emac, val, ENET_CTL_REG);
+
+	/* turn on mdc clock */
+	emac_writel(emac, (0x1f << ENET_MIISC_MDCFREQDIV_SHIFT) |
+		    ENET_MIISC_PREAMBLEEN_MASK, ENET_MIISC_REG);
+
+	/* set mib counters to self-clear when read */
+	val = emac_readl(emac, ENET_MIBCTL_REG);
+	val |= ENET_MIBCTL_RDCLEAR_MASK;
+	emac_writel(emac, val, ENET_MIBCTL_REG);
+}
+
+static int bcm6348_emac_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *node = dev->of_node;
+	struct device_node *dma_node;
+	struct platform_device *dma_pdev;
+	struct bcm6348_emac *emac;
+	struct bcm6348_iudma *iudma;
+	struct net_device *ndev;
+	unsigned i;
+	int num_resets;
+	int ret;
+
+	dma_node = of_parse_phandle(node, "brcm,iudma", 0);
+	if (!dma_node)
+		return -EINVAL;
+
+	dma_pdev = of_find_device_by_node(dma_node);
+	of_node_put(dma_node);
+	if (!dma_pdev)
+		return -EINVAL;
+
+	iudma = platform_get_drvdata(dma_pdev);
+	if (!iudma)
+		return -EPROBE_DEFER;
+
+	ndev = devm_alloc_etherdev(dev, sizeof(*emac));
+	if (!ndev)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, ndev);
+	SET_NETDEV_DEV(ndev, dev);
+
+	emac = netdev_priv(ndev);
+	emac->iudma = iudma;
+	emac->pdev = pdev;
+	emac->net_dev = ndev;
+
+	emac->base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR_OR_NULL(emac->base))
+		return PTR_ERR(emac->base);
+
+	ndev->irq = of_irq_get_byname(node, "emac");
+	if (!ndev->irq)
+		return -ENODEV;
+
+	emac->irq_rx = of_irq_get_byname(node, "rx");
+	if (!emac->irq_rx)
+		return -ENODEV;
+
+	emac->irq_tx = of_irq_get_byname(node, "tx");
+	if (!emac->irq_tx)
+		return -ENODEV;
+
+	if (of_property_read_u32(node, "dma-rx", &emac->rx_chan))
+		return -ENODEV;
+
+	if (of_property_read_u32(node, "dma-tx", &emac->tx_chan))
+		return -ENODEV;
+
+	emac->ext_mii = of_property_read_bool(node, "brcm,external-mii");
+
+	emac->rx_ring_size = ENET_DEF_RX_DESC;
+	emac->tx_ring_size = ENET_DEF_TX_DESC;
+	emac->copybreak = ENET_DEF_CPY_BREAK;
+
+	of_get_mac_address(node, ndev->dev_addr);
+	if (is_valid_ether_addr(ndev->dev_addr)) {
+		dev_info(dev, "mtd mac %pM\n", ndev->dev_addr);
+	} else {
+		random_ether_addr(ndev->dev_addr);
+		dev_info(dev, "random mac %pM\n", ndev->dev_addr);
+	}
+
+	emac->rx_skb_size = ALIGN(ndev->mtu + ENET_MTU_OVERHEAD,
+				  ENET_DMA_MAXBURST * 4);
+
+	emac->num_clocks = of_clk_get_parent_count(node);
+	if (emac->num_clocks) {
+		emac->clock = devm_kcalloc(dev, emac->num_clocks,
+					   sizeof(struct clk *), GFP_KERNEL);
+		if (IS_ERR_OR_NULL(emac->clock))
+			return PTR_ERR(emac->clock);
+	}
+	for (i = 0; i < emac->num_clocks; i++) {
+		emac->clock[i] = of_clk_get(node, i);
+		if (IS_ERR_OR_NULL(emac->clock[i])) {
+			dev_err(dev, "error getting emac clock %d\n", i);
+			return PTR_ERR(emac->clock[i]);
+		}
+
+		ret = clk_prepare_enable(emac->clock[i]);
+		if (ret) {
+			dev_err(dev, "error enabling emac clock %d\n", i);
+			return ret;
+		}
+	}
+
+	num_resets = of_count_phandle_with_args(node, "resets",
+						"#reset-cells");
+	if (num_resets > 0)
+		emac->num_resets = num_resets;
+	else
+		emac->num_resets = 0;
+	if (emac->num_resets) {
+		emac->reset = devm_kcalloc(dev, emac->num_resets,
+					   sizeof(struct reset_control *),
+					   GFP_KERNEL);
+		if (IS_ERR_OR_NULL(emac->reset))
+			return PTR_ERR(emac->reset);
+		
+	}
+	for (i = 0; i < emac->num_resets; i++) {
+		emac->reset[i] = devm_reset_control_get_by_index(dev, i);
+		if (IS_ERR_OR_NULL(emac->reset[i])) {
+			dev_err(dev, "error getting emac reset %d\n", i);
+			return PTR_ERR(emac->reset[i]);
+		}
+
+		ret = reset_control_reset(emac->reset[i]);
+		if (ret) {
+			dev_err(dev, "error performing emac reset %d\n", i);
+			return ret;
+		}
+	}
+
+	/* do minimal hardware init to be able to probe mii bus */
+	bcm6348_emac_hw_preinit(emac);
+
+	ret = bcm6348_emac_mdio_init(emac, node);
+	if (ret)
+		return ret;
+
+	spin_lock_init(&emac->rx_lock);
+
+	timer_setup(&emac->rx_timeout, bcm6348_emac_refill_rx_timer, 0);
+
+	/* zero mib counters */
+	for (i = 0; i < ENET_MIB_REG_COUNT; i++)
+		emac_writel(emac, 0, ENET_MIB_REG(i));
+
+	/* register netdevice */
+	ndev->netdev_ops = &bcm6348_emac_ops;
+	ndev->min_mtu = ETH_ZLEN - ETH_HLEN;
+	ndev->mtu = ETH_DATA_LEN - VLAN_ETH_HLEN;
+	ndev->max_mtu = ENET_MAX_MTU - VLAN_ETH_HLEN;
+	netif_napi_add(ndev, &emac->napi, bcm6348_emac_poll, 16);
+	SET_NETDEV_DEV(ndev, dev);
+
+	ret = devm_register_netdev(dev, ndev);
+	if (ret)
+		goto out_disable_clk;
+
+	netif_carrier_off(ndev);
+
+	dev_info(dev, "%s at 0x%px, IRQ %d\n", ndev->name, emac->base,
+		 ndev->irq);
+
+	return 0;
+
+out_disable_clk:
+	for (i = 0; i < emac->num_resets; i++)
+		reset_control_assert(emac->reset[i]);
+
+	for (i = 0; i < emac->num_clocks; i++)
+		clk_disable_unprepare(emac->clock[i]);
+
+	return ret;
+}
+
+static int bcm6348_emac_remove(struct platform_device *pdev)
+{
+	struct net_device *ndev = platform_get_drvdata(pdev);
+	struct bcm6348_emac *emac = netdev_priv(ndev);
+	unsigned int i;
+
+	emac_writel(emac, 0, ENET_MIISC_REG);
+
+	for (i = 0; i < emac->num_resets; i++)
+		reset_control_assert(emac->reset[i]);
+
+	for (i = 0; i < emac->num_clocks; i++)
+		clk_disable_unprepare(emac->clock[i]);
+
+	return 0;
+}
+
+static const struct of_device_id bcm6348_emac_of_match[] = {
+	{ .compatible = "brcm,bcm6338-emac", },
+	{ .compatible = "brcm,bcm6348-emac", },
+	{ .compatible = "brcm,bcm6358-emac", },
+	{ /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, bcm6348_emac_of_match);
+
+static struct platform_driver bcm6348_emac_driver = {
+	.driver = {
+		.name = "bcm6348-emac",
+		.of_match_table = of_match_ptr(bcm6348_emac_of_match),
+	},
+	.probe	= bcm6348_emac_probe,
+	.remove	= bcm6348_emac_remove,
+};
+module_platform_driver(bcm6348_emac_driver);
diff --git a/target/linux/bmips/generic/base-files/etc/board.d/02_network b/target/linux/bmips/generic/base-files/etc/board.d/02_network
index 238dd27939..a5995849f0 100644
--- a/target/linux/bmips/generic/base-files/etc/board.d/02_network
+++ b/target/linux/bmips/generic/base-files/etc/board.d/02_network
@@ -11,6 +11,9 @@ comtrend,vr-3025u)
 	ucidef_set_bridge_device switch
 	ucidef_set_interface_lan "lan1 lan2 lan3 lan4"
 	;;
+huawei,hg556a-b)
+	ucidef_set_interface_lan "eth0"
+	;;
 esac
 
 board_config_flush
diff --git a/target/linux/bmips/patches-5.15/501-net-broadcom-add-BCM6348-enet-controller-driver.patch b/target/linux/bmips/patches-5.15/501-net-broadcom-add-BCM6348-enet-controller-driver.patch
new file mode 100644
index 0000000000..7d278b9a33
--- /dev/null
+++ b/target/linux/bmips/patches-5.15/501-net-broadcom-add-BCM6348-enet-controller-driver.patch
@@ -0,0 +1,44 @@
+From 590b60fb08cb1e70fe02d3f407c6b3dbe9ad06ff Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= <noltari at gmail.com>
+Date: Mon, 1 Mar 2021 07:34:39 +0100
+Subject: [PATCH] net: broadcom: add BCM6348 enetsw controller driver
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+This controller is present on BCM6338, BCM6348 and BCM6358 SoCs.
+
+Signed-off-by: Álvaro Fernández Rojas <noltari at gmail.com>
+---
+ drivers/net/ethernet/broadcom/Kconfig         |    8 +
+ drivers/net/ethernet/broadcom/Makefile        |    1 +
+ 3 files changed, 1120 insertions(+)
+ create mode 100644 drivers/net/ethernet/broadcom/bcm6368-enetsw.c
+
+--- a/drivers/net/ethernet/broadcom/Kconfig
++++ b/drivers/net/ethernet/broadcom/Kconfig
+@@ -68,6 +68,14 @@ config BCM63XX_ENET
+ 	  This driver supports the ethernet MACs in the Broadcom 63xx
+ 	  MIPS chipset family (BCM63XX).
+ 
++config BCM6348_ENET
++	tristate "Broadcom BCM6348 internal mac support"
++	depends on BMIPS_GENERIC || COMPILE_TEST
++	default y
++	help
++	  This driver supports Ethernet controller integrated into Broadcom
++	  BCM6348 family SoCs.
++
+ config BCM6368_ENETSW
+ 	tristate "Broadcom BCM6368 internal mac support"
+ 	depends on BMIPS_GENERIC || COMPILE_TEST
+--- a/drivers/net/ethernet/broadcom/Makefile
++++ b/drivers/net/ethernet/broadcom/Makefile
+@@ -6,6 +6,7 @@
+ obj-$(CONFIG_B44) += b44.o
+ obj-$(CONFIG_BCM4908_ENET) += bcm4908_enet.o
+ obj-$(CONFIG_BCM63XX_ENET) += bcm63xx_enet.o
++obj-$(CONFIG_BCM6348_ENET) += bcm6348-enet.o
+ obj-$(CONFIG_BCM6368_ENETSW) += bcm6368-enetsw.o
+ obj-$(CONFIG_BCMGENET) += genet/
+ obj-$(CONFIG_BNX2) += bnx2.o




More information about the lede-commits mailing list