[RFC PATCH v2 11/11] riscv: soc: Allwinner D1 GMAC driver only for temp use
guoren at kernel.org
guoren at kernel.org
Sun Jun 6 02:04:09 PDT 2021
From: liush <liush at allwinnertech.com>
This is a temporary driver, only guaranteed to work on allwinner
D1. In order to ensure the developer's demand for network usage.
It only could work at 1Gps mode.
The correct gmac driver should follow (I guess)
drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c
If anyone is familiar with it and can help porting, I would be
very grateful.
Signed-off-by: Liu Shaohua <liush at allwinnertech.com>
Tested-by: Guo Ren <guoren at kernel.org>
Signed-off-by: Guo Ren <guoren at kernel.org>
Cc: Maxime Ripard <mripard at kernel.org>
Cc: Corentin Labbe <clabbe at baylibre.com>
Cc: Samuel Holland <samuel at sholland.org>
Cc: Icenowy Zheng <icenowy at aosc.io>
Cc: LABBE Corentin <clabbe.montjoie at gmail.com>
Cc: Michael Walle <michael at walle.cc>
Cc: Chen-Yu Tsai <wens at csie.org>
Cc: Maxime Ripard <maxime at cerno.tech>
Cc: Wei Fu <wefu at redhat.com>
Cc: Wei Wu <lazyparser at gmail.com>
Signed-off-by: Guo Ren <guoren at linux.alibaba.com>
---
.../boot/dts/allwinner/allwinner-d1-nezha-kit.dts | 2 +-
arch/riscv/boot/dts/allwinner/allwinner-d1.dtsi | 16 +
drivers/net/ethernet/Kconfig | 1 +
drivers/net/ethernet/Makefile | 1 +
drivers/net/ethernet/allwinnertmp/Kconfig | 17 +
drivers/net/ethernet/allwinnertmp/Makefile | 7 +
drivers/net/ethernet/allwinnertmp/sunxi-gmac-ops.c | 690 ++++++
drivers/net/ethernet/allwinnertmp/sunxi-gmac.c | 2240 ++++++++++++++++++++
drivers/net/ethernet/allwinnertmp/sunxi-gmac.h | 258 +++
drivers/net/phy/realtek.c | 2 +-
10 files changed, 3232 insertions(+), 2 deletions(-)
create mode 100644 drivers/net/ethernet/allwinnertmp/Kconfig
create mode 100644 drivers/net/ethernet/allwinnertmp/Makefile
create mode 100644 drivers/net/ethernet/allwinnertmp/sunxi-gmac-ops.c
create mode 100644 drivers/net/ethernet/allwinnertmp/sunxi-gmac.c
create mode 100644 drivers/net/ethernet/allwinnertmp/sunxi-gmac.h
diff --git a/arch/riscv/boot/dts/allwinner/allwinner-d1-nezha-kit.dts b/arch/riscv/boot/dts/allwinner/allwinner-d1-nezha-kit.dts
index cd9f7c9..31b681d 100644
--- a/arch/riscv/boot/dts/allwinner/allwinner-d1-nezha-kit.dts
+++ b/arch/riscv/boot/dts/allwinner/allwinner-d1-nezha-kit.dts
@@ -11,7 +11,7 @@
compatible = "allwinner,d1-nezha-kit";
chosen {
- bootargs = "console=ttyS0,115200";
+ bootargs = "console=ttyS0,115200 rootwait init=/sbin/init root=/dev/nfs rw nfsroot=192.168.101.200:/tmp/rootfs_nfs,v3,tcp,nolock ip=192.168.101.23";
stdout-path = &serial0;
};
diff --git a/arch/riscv/boot/dts/allwinner/allwinner-d1.dtsi b/arch/riscv/boot/dts/allwinner/allwinner-d1.dtsi
index 11cd938..d317e19 100644
--- a/arch/riscv/boot/dts/allwinner/allwinner-d1.dtsi
+++ b/arch/riscv/boot/dts/allwinner/allwinner-d1.dtsi
@@ -80,5 +80,21 @@
clocks = <&dummy_apb>;
status = "disabled";
};
+
+ eth at 4500000 {
+ compatible = "allwinner,sunxi-gmac";
+ reg = <0x00 0x4500000 0x00 0x10000 0x00 0x3000030 0x00 0x04>;
+ interrupts-extended = <&plic 0x3e 0x04>;
+ interrupt-names = "gmacirq";
+ device_type = "gmac0";
+ phy-mode = "rgmii";
+ use_ephy25m = <0x01>;
+ tx-delay = <0x03>;
+ rx-delay = <0x03>;
+ gmac-power0;
+ gmac-power1;
+ gmac-power2;
+ status = "okay";
+ };
};
};
diff --git a/drivers/net/ethernet/Kconfig b/drivers/net/ethernet/Kconfig
index 1cdff1d..1f8e37c 100644
--- a/drivers/net/ethernet/Kconfig
+++ b/drivers/net/ethernet/Kconfig
@@ -18,6 +18,7 @@ config MDIO
config SUNGEM_PHY
tristate
+source "drivers/net/ethernet/allwinnertmp/Kconfig"
source "drivers/net/ethernet/3com/Kconfig"
source "drivers/net/ethernet/actions/Kconfig"
source "drivers/net/ethernet/adaptec/Kconfig"
diff --git a/drivers/net/ethernet/Makefile b/drivers/net/ethernet/Makefile
index cb3f908..3dacc0c 100644
--- a/drivers/net/ethernet/Makefile
+++ b/drivers/net/ethernet/Makefile
@@ -3,6 +3,7 @@
# Makefile for the Linux network Ethernet device drivers.
#
+obj-y += allwinnertmp/
obj-$(CONFIG_NET_VENDOR_3COM) += 3com/
obj-$(CONFIG_NET_VENDOR_8390) += 8390/
obj-$(CONFIG_NET_VENDOR_ACTIONS) += actions/
diff --git a/drivers/net/ethernet/allwinnertmp/Kconfig b/drivers/net/ethernet/allwinnertmp/Kconfig
new file mode 100644
index 00000000..4b7b378
--- /dev/null
+++ b/drivers/net/ethernet/allwinnertmp/Kconfig
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Allwinner device configuration
+#
+
+config SUNXI_GMAC
+ tristate "Allwinner GMAC support"
+ default y
+ depends on OF
+ select CRC32
+ select MII
+ select PHYLIB
+ help
+ Support for Allwinner Gigabit ethernet driver.
+
+ To compile this driver as a module, choose M here. The module
+ will be called sunxi-gmac.
diff --git a/drivers/net/ethernet/allwinnertmp/Makefile b/drivers/net/ethernet/allwinnertmp/Makefile
new file mode 100644
index 00000000..1375dea
--- /dev/null
+++ b/drivers/net/ethernet/allwinnertmp/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for the Allwinner device drivers.
+#
+
+obj-$(CONFIG_SUNXI_GMAC) += sunxi_gmac.o
+sunxi_gmac-objs := sunxi-gmac.o sunxi-gmac-ops.o
diff --git a/drivers/net/ethernet/allwinnertmp/sunxi-gmac-ops.c b/drivers/net/ethernet/allwinnertmp/sunxi-gmac-ops.c
new file mode 100644
index 00000000..26ffd7f
--- /dev/null
+++ b/drivers/net/ethernet/allwinnertmp/sunxi-gmac-ops.c
@@ -0,0 +1,690 @@
+/*
+ * linux/drivers/net/ethernet/allwinner/sunxi_gmac_ops.c
+ *
+ * Copyright © 2016-2018, fuzhaoke
+ * Author: fuzhaoke <fuzhaoke at allwinnertech.com>
+ *
+ * This file is provided under a dual BSD/GPL license. When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * 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.
+ */
+#include <linux/kernel.h>
+#include <linux/ctype.h>
+#include <linux/printk.h>
+#include <linux/io.h>
+#include "sunxi-gmac.h"
+
+/******************************************************************************
+ * sun8iw6 operations
+ *****************************************************************************/
+#define GETH_BASIC_CTL0 0x00
+#define GETH_BASIC_CTL1 0x04
+#define GETH_INT_STA 0x08
+#define GETH_INT_EN 0x0C
+#define GETH_TX_CTL0 0x10
+#define GETH_TX_CTL1 0x14
+#define GETH_TX_FLOW_CTL 0x1C
+#define GETH_TX_DESC_LIST 0x20
+#define GETH_RX_CTL0 0x24
+#define GETH_RX_CTL1 0x28
+#define GETH_RX_DESC_LIST 0x34
+#define GETH_RX_FRM_FLT 0x38
+#define GETH_RX_HASH0 0x40
+#define GETH_RX_HASH1 0x44
+#define GETH_MDIO_ADDR 0x48
+#define GETH_MDIO_DATA 0x4C
+#define GETH_ADDR_HI(reg) (0x50 + ((reg) << 3))
+#define GETH_ADDR_LO(reg) (0x54 + ((reg) << 3))
+#define GETH_TX_DMA_STA 0xB0
+#define GETH_TX_CUR_DESC 0xB4
+#define GETH_TX_CUR_BUF 0xB8
+#define GETH_RX_DMA_STA 0xC0
+#define GETH_RX_CUR_DESC 0xC4
+#define GETH_RX_CUR_BUF 0xC8
+#define GETH_RGMII_STA 0xD0
+
+#define RGMII_IRQ 0x00000001
+
+#define CTL0_LM 0x02
+#define CTL0_DM 0x01
+#define CTL0_SPEED 0x04
+
+#define BURST_LEN 0x3F000000
+#define RX_TX_PRI 0x02
+#define SOFT_RST 0x01
+
+#define TX_FLUSH 0x01
+#define TX_MD 0x02
+#define TX_NEXT_FRM 0x04
+#define TX_TH 0x0700
+
+#define RX_FLUSH 0x01
+#define RX_MD 0x02
+#define RX_RUNT_FRM 0x04
+#define RX_ERR_FRM 0x08
+#define RX_TH 0x0030
+
+#define TX_INT 0x00001
+#define TX_STOP_INT 0x00002
+#define TX_UA_INT 0x00004
+#define TX_TOUT_INT 0x00008
+#define TX_UNF_INT 0x00010
+#define TX_EARLY_INT 0x00020
+#define RX_INT 0x00100
+#define RX_UA_INT 0x00200
+#define RX_STOP_INT 0x00400
+#define RX_TOUT_INT 0x00800
+#define RX_OVF_INT 0x01000
+#define RX_EARLY_INT 0x02000
+#define LINK_STA_INT 0x10000
+
+#define DISCARD_FRAME -1
+#define GOOD_FRAME 0
+#define CSUM_NONE 2
+#define LLC_SNAP 4
+
+#define SF_DMA_MODE 1
+
+/* Flow Control defines */
+#define FLOW_OFF 0
+#define FLOW_RX 1
+#define FLOW_TX 2
+#define FLOW_AUTO (FLOW_TX | FLOW_RX)
+
+#define HASH_TABLE_SIZE 64
+#define PAUSE_TIME 0x200
+#define GMAC_MAX_UNICAST_ADDRESSES 8
+
+/* PHY address */
+#define PHY_ADDR 0x01
+#define PHY_DM 0x0010
+#define PHY_AUTO_NEG 0x0020
+#define PHY_POWERDOWN 0x0080
+#define PHY_NEG_EN 0x1000
+
+#define MII_BUSY 0x00000001
+#define MII_WRITE 0x00000002
+#define MII_PHY_MASK 0x0000FFC0
+#define MII_CR_MASK 0x0000001C
+#define MII_CLK 0x00000008
+/* bits 4 3 2 | AHB1 Clock | MDC Clock
+ * -------------------------------------------------------
+ * 0 0 0 | 60 ~ 100 MHz | div-42
+ * 0 0 1 | 100 ~ 150 MHz | div-62
+ * 0 1 0 | 20 ~ 35 MHz | div-16
+ * 0 1 1 | 35 ~ 60 MHz | div-26
+ * 1 0 0 | 150 ~ 250 MHz | div-102
+ * 1 0 1 | 250 ~ 300 MHz | div-124
+ * 1 1 x | Reserved |
+ */
+
+enum csum_insertion {
+ cic_dis = 0, /* Checksum Insertion Control */
+ cic_ip = 1, /* Only IP header */
+ cic_no_pse = 2, /* IP header but not pseudoheader */
+ cic_full = 3, /* IP header and pseudoheader */
+};
+
+struct gethdev {
+ void *iobase;
+ unsigned int ver;
+ unsigned int mdc_div;
+};
+
+static struct gethdev hwdev;
+
+/***************************************************************************
+ * External interface
+ **************************************************************************/
+/* Set a ring desc buffer */
+void desc_init_chain(struct dma_desc *desc, unsigned long addr, unsigned int size)
+{
+ /* In chained mode the desc3 points to the next element in the ring.
+ * The latest element has to point to the head.
+ */
+ int i;
+ struct dma_desc *p = desc;
+ unsigned long dma_phy = addr;
+
+ for (i = 0; i < (size - 1); i++) {
+ dma_phy += sizeof(struct dma_desc);
+ p->desc3 = (unsigned int)dma_phy;
+ /* Chain mode */
+ p->desc1.all |= (1 << 24);
+ p++;
+ }
+ p->desc1.all |= (1 << 24);
+ p->desc3 = (unsigned int)addr;
+}
+
+int sunxi_mdio_read(void *iobase, int phyaddr, int phyreg)
+{
+ unsigned int value = 0;
+
+ /* Mask the MDC_DIV_RATIO */
+ value |= ((hwdev.mdc_div & 0x07) << 20);
+ value |= (((phyaddr << 12) & (0x0001F000)) |
+ ((phyreg << 4) & (0x000007F0)) |
+ MII_BUSY);
+
+ while (((readl(iobase + GETH_MDIO_ADDR)) & MII_BUSY) == 1)
+ ;
+
+ writel(value, iobase + GETH_MDIO_ADDR);
+ while (((readl(iobase + GETH_MDIO_ADDR)) & MII_BUSY) == 1)
+ ;
+
+ return (int)readl(iobase + GETH_MDIO_DATA);
+}
+
+int sunxi_mdio_write(void *iobase, int phyaddr, int phyreg, unsigned short data)
+{
+ unsigned int value;
+
+ value = ((0x07 << 20) & readl(iobase + GETH_MDIO_ADDR)) |
+ (hwdev.mdc_div << 20);
+ value |= (((phyaddr << 12) & (0x0001F000)) |
+ ((phyreg << 4) & (0x000007F0))) |
+ MII_WRITE | MII_BUSY;
+
+ /* Wait until any existing MII operation is complete */
+ while (((readl(iobase + GETH_MDIO_ADDR)) & MII_BUSY) == 1)
+ ;
+
+ /* Set the MII address register to write */
+ writel(data, iobase + GETH_MDIO_DATA);
+ writel(value, iobase + GETH_MDIO_ADDR);
+
+ /* Wait until any existing MII operation is complete */
+ while (((readl(iobase + GETH_MDIO_ADDR)) & MII_BUSY) == 1)
+ ;
+
+ return 0;
+}
+
+int sunxi_mdio_reset(void *iobase)
+{
+ writel((4 << 2), iobase + GETH_MDIO_ADDR);
+ return 0;
+}
+
+void sunxi_set_link_mode(void *iobase, int duplex, int speed)
+{
+ unsigned int ctrl = readl(iobase + GETH_BASIC_CTL0);
+
+ if (!duplex)
+ ctrl &= ~CTL0_DM;
+ else
+ ctrl |= CTL0_DM;
+
+ switch (speed) {
+ case 1000:
+ ctrl &= ~0x0C;
+ break;
+ case 100:
+ case 10:
+ default:
+ ctrl |= 0x08;
+ if (speed == 100)
+ ctrl |= 0x04;
+ else
+ ctrl &= ~0x04;
+ break;
+ }
+
+ writel(ctrl, iobase + GETH_BASIC_CTL0);
+}
+
+void sunxi_mac_loopback(void *iobase, int enable)
+{
+ int reg;
+
+ reg = readl(iobase + GETH_BASIC_CTL0);
+ if (enable)
+ reg |= 0x02;
+ else
+ reg &= ~0x02;
+ writel(reg, iobase + GETH_BASIC_CTL0);
+}
+
+void sunxi_flow_ctrl(void *iobase, int duplex, int fc, int pause)
+{
+ unsigned int flow = 0;
+
+ if (fc & FLOW_RX) {
+ flow = readl(iobase + GETH_RX_CTL0);
+ flow |= 0x10000;
+ writel(flow, iobase + GETH_RX_CTL0);
+ }
+
+ if (fc & FLOW_TX) {
+ flow = readl(iobase + GETH_TX_FLOW_CTL);
+ flow |= 0x00001;
+ writel(flow, iobase + GETH_TX_FLOW_CTL);
+ }
+
+ if (duplex) {
+ flow = readl(iobase + GETH_TX_FLOW_CTL);
+ flow |= (pause << 4);
+ writel(flow, iobase + GETH_TX_FLOW_CTL);
+ }
+}
+
+int sunxi_int_status(void *iobase, struct geth_extra_stats *x)
+{
+ int ret = 0;
+ /* read the status register (CSR5) */
+ unsigned int intr_status;
+
+ intr_status = readl(iobase + GETH_RGMII_STA);
+ if (intr_status & RGMII_IRQ)
+ readl(iobase + GETH_RGMII_STA);
+
+ intr_status = readl(iobase + GETH_INT_STA);
+
+ /* ABNORMAL interrupts */
+ if (intr_status & TX_UNF_INT) {
+ ret = tx_hard_error_bump_tc;
+ x->tx_undeflow_irq++;
+ }
+ if (intr_status & TX_TOUT_INT) {
+ x->tx_jabber_irq++;
+ }
+ if (intr_status & RX_OVF_INT) {
+ x->rx_overflow_irq++;
+ }
+ if (intr_status & RX_UA_INT) {
+ x->rx_buf_unav_irq++;
+ }
+ if (intr_status & RX_STOP_INT) {
+ x->rx_process_stopped_irq++;
+ }
+ if (intr_status & RX_TOUT_INT) {
+ x->rx_watchdog_irq++;
+ }
+ if (intr_status & TX_EARLY_INT) {
+ x->tx_early_irq++;
+ }
+ if (intr_status & TX_STOP_INT) {
+ x->tx_process_stopped_irq++;
+ ret = tx_hard_error;
+ }
+
+ /* TX/RX NORMAL interrupts */
+ if (intr_status & (TX_INT | RX_INT | RX_EARLY_INT | TX_UA_INT)) {
+ x->normal_irq_n++;
+ if (intr_status & (TX_INT | RX_INT))
+ ret = handle_tx_rx;
+ }
+ /* Clear the interrupt by writing a logic 1 to the CSR5[15-0] */
+ writel(intr_status & 0x3FFF, iobase + GETH_INT_STA);
+
+ return ret;
+}
+
+void sunxi_start_rx(void *iobase, unsigned long rxbase)
+{
+ unsigned int value;
+
+ /* Write the base address of Rx descriptor lists into registers */
+ writel(rxbase, iobase + GETH_RX_DESC_LIST);
+
+ value = readl(iobase + GETH_RX_CTL1);
+ value |= 0x40000000;
+ writel(value, iobase + GETH_RX_CTL1);
+}
+
+void sunxi_stop_rx(void *iobase)
+{
+ unsigned int value;
+
+ value = readl(iobase + GETH_RX_CTL1);
+ value &= ~0x40000000;
+ writel(value, iobase + GETH_RX_CTL1);
+}
+
+void sunxi_start_tx(void *iobase, unsigned long txbase)
+{
+ unsigned int value;
+
+ /* Write the base address of Tx descriptor lists into registers */
+ writel(txbase, iobase + GETH_TX_DESC_LIST);
+
+ value = readl(iobase + GETH_TX_CTL1);
+ value |= 0x40000000;
+ writel(value, iobase + GETH_TX_CTL1);
+}
+
+void sunxi_stop_tx(void *iobase)
+{
+ unsigned int value = readl(iobase + GETH_TX_CTL1);
+
+ value &= ~0x40000000;
+ writel(value, iobase + GETH_TX_CTL1);
+}
+
+static int sunxi_dma_init(void *iobase)
+{
+ unsigned int value;
+
+ /* Burst should be 8 */
+ value = (8 << 24);
+
+#ifdef CONFIG_GMAC_DA
+ value |= RX_TX_PRI; /* Rx has priority over tx */
+#endif
+ writel(value, iobase + GETH_BASIC_CTL1);
+
+ /* Mask interrupts by writing to CSR7 */
+ writel(RX_INT | TX_UNF_INT, iobase + GETH_INT_EN);
+
+ return 0;
+}
+
+int sunxi_mac_init(void *iobase, int txmode, int rxmode)
+{
+ unsigned int value;
+
+ sunxi_dma_init(iobase);
+
+ /* Initialize the core component */
+ value = readl(iobase + GETH_TX_CTL0);
+ value |= (1 << 30); /* Jabber Disable */
+ writel(value, iobase + GETH_TX_CTL0);
+
+ value = readl(iobase + GETH_RX_CTL0);
+ value |= (1 << 27); /* Enable CRC & IPv4 Header Checksum */
+ value |= (1 << 28); /* Automatic Pad/CRC Stripping */
+ value |= (1 << 29); /* Jumbo Frame Enable */
+ writel(value, iobase + GETH_RX_CTL0);
+
+ writel((hwdev.mdc_div << 20), iobase + GETH_MDIO_ADDR); /* MDC_DIV_RATIO */
+
+ /* Set the Rx&Tx mode */
+ value = readl(iobase + GETH_TX_CTL1);
+ if (txmode == SF_DMA_MODE) {
+ /* Transmit COE type 2 cannot be done in cut-through mode. */
+ value |= TX_MD;
+ /* Operating on second frame increase the performance
+ * especially when transmit store-and-forward is used.
+ */
+ value |= TX_NEXT_FRM;
+ } else {
+ value &= ~TX_MD;
+ value &= ~TX_TH;
+ /* Set the transmit threshold */
+ if (txmode <= 64)
+ value |= 0x00000000;
+ else if (txmode <= 128)
+ value |= 0x00000100;
+ else if (txmode <= 192)
+ value |= 0x00000200;
+ else
+ value |= 0x00000300;
+ }
+ writel(value, iobase + GETH_TX_CTL1);
+
+ value = readl(iobase + GETH_RX_CTL1);
+ if (rxmode == SF_DMA_MODE) {
+ value |= RX_MD;
+ } else {
+ value &= ~RX_MD;
+ value &= ~RX_TH;
+ if (rxmode <= 32)
+ value |= 0x10;
+ else if (rxmode <= 64)
+ value |= 0x00;
+ else if (rxmode <= 96)
+ value |= 0x20;
+ else
+ value |= 0x30;
+ }
+
+ /* Forward frames with error and undersized good frame. */
+ value |= (RX_ERR_FRM | RX_RUNT_FRM);
+
+ writel(value, iobase + GETH_RX_CTL1);
+
+ return 0;
+}
+
+void sunxi_hash_filter(void *iobase, unsigned long low, unsigned long high)
+{
+ writel(high, iobase + GETH_RX_HASH0);
+ writel(low, iobase + GETH_RX_HASH1);
+}
+
+void sunxi_set_filter(void *iobase, unsigned long flags)
+{
+ int tmp_flags = 0;
+
+ tmp_flags |= ((flags >> 31) |
+ ((flags >> 9) & 0x00000002) |
+ ((flags << 1) & 0x00000010) |
+ ((flags >> 3) & 0x00000060) |
+ ((flags << 7) & 0x00000300) |
+ ((flags << 6) & 0x00003000) |
+ ((flags << 12) & 0x00030000) |
+ (flags << 31));
+
+ writel(tmp_flags, iobase + GETH_RX_FRM_FLT);
+}
+
+void sunxi_set_umac(void *iobase, unsigned char *addr, int index)
+{
+ unsigned long data;
+
+ data = (addr[5] << 8) | addr[4];
+ writel(data, iobase + GETH_ADDR_HI(index));
+ data = (addr[3] << 24) | (addr[2] << 16) | (addr[1] << 8) | addr[0];
+ writel(data, iobase + GETH_ADDR_LO(index));
+}
+
+void sunxi_mac_enable(void *iobase)
+{
+ unsigned long value;
+
+ value = readl(iobase + GETH_TX_CTL0);
+ value |= (1 << 31);
+ writel(value, iobase + GETH_TX_CTL0);
+
+ value = readl(iobase + GETH_RX_CTL0);
+ value |= (1 << 31);
+ writel(value, iobase + GETH_RX_CTL0);
+}
+
+void sunxi_mac_disable(void *iobase)
+{
+ unsigned long value;
+
+ value = readl(iobase + GETH_TX_CTL0);
+ value &= ~(1 << 31);
+ writel(value, iobase + GETH_TX_CTL0);
+
+ value = readl(iobase + GETH_RX_CTL0);
+ value &= ~(1 << 31);
+ writel(value, iobase + GETH_RX_CTL0);
+}
+
+void sunxi_tx_poll(void *iobase)
+{
+ unsigned int value;
+
+ value = readl(iobase + GETH_TX_CTL1);
+ writel(value | 0x80000000, iobase + GETH_TX_CTL1);
+}
+
+void sunxi_rx_poll(void *iobase)
+{
+ unsigned int value;
+
+ value = readl(iobase + GETH_RX_CTL1);
+ writel(value | 0x80000000, iobase + GETH_RX_CTL1);
+}
+
+void sunxi_int_enable(void *iobase)
+{
+ writel(RX_INT | TX_UNF_INT, iobase + GETH_INT_EN);
+}
+
+void sunxi_int_disable(void *iobase)
+{
+ writel(0, iobase + GETH_INT_EN);
+}
+
+void desc_buf_set(struct dma_desc *desc, unsigned long paddr, int size)
+{
+ desc->desc1.all &= (~((1 << 11) - 1));
+ desc->desc1.all |= (size & ((1 << 11) - 1));
+ desc->desc2 = paddr;
+}
+
+void desc_set_own(struct dma_desc *desc)
+{
+ desc->desc0.all |= 0x80000000;
+}
+
+void desc_tx_close(struct dma_desc *first, struct dma_desc *end, int csum_insert)
+{
+ struct dma_desc *desc = first;
+
+ first->desc1.tx.first_sg = 1;
+ end->desc1.tx.last_seg = 1;
+ end->desc1.tx.interrupt = 1;
+
+ if (csum_insert)
+ do {
+ desc->desc1.tx.cic = 3;
+ desc++;
+ } while (desc <= end);
+}
+
+void desc_init(struct dma_desc *desc)
+{
+ desc->desc1.all = 0;
+ desc->desc2 = 0;
+
+ desc->desc1.all |= (1 << 24);
+}
+
+int desc_get_tx_status(struct dma_desc *desc, struct geth_extra_stats *x)
+{
+ int ret = 0;
+
+ if (desc->desc0.tx.under_err) {
+ x->tx_underflow++;
+ ret = -1;
+ }
+ if (desc->desc0.tx.no_carr) {
+ x->tx_carrier++;
+ ret = -1;
+ }
+ if (desc->desc0.tx.loss_carr) {
+ x->tx_losscarrier++;
+ ret = -1;
+ }
+
+#if 0
+ if ((desc->desc0.tx.ex_deferral) ||
+ (desc->desc0.tx.ex_coll) ||
+ (desc->desc0.tx.late_coll))
+ stats->collisions += desc->desc0.tx.coll_cnt;
+#endif
+
+ if (desc->desc0.tx.deferred)
+ x->tx_deferred++;
+
+ return ret;
+}
+
+int desc_buf_get_len(struct dma_desc *desc)
+{
+ return (desc->desc1.all & ((1 << 11) - 1));
+}
+
+int desc_buf_get_addr(struct dma_desc *desc)
+{
+ return desc->desc2;
+}
+
+int desc_rx_frame_len(struct dma_desc *desc)
+{
+ return desc->desc0.rx.frm_len;
+}
+
+int desc_get_rx_status(struct dma_desc *desc, struct geth_extra_stats *x)
+{
+ int ret = good_frame;
+
+ if (desc->desc0.rx.last_desc == 0) {
+ return discard_frame;
+ }
+
+ if (desc->desc0.rx.err_sum) {
+ if (desc->desc0.rx.desc_err)
+ x->rx_desc++;
+
+ if (desc->desc0.rx.sou_filter)
+ x->sa_filter_fail++;
+
+ if (desc->desc0.rx.over_err)
+ x->overflow_error++;
+
+ if (desc->desc0.rx.ipch_err)
+ x->ipc_csum_error++;
+
+ if (desc->desc0.rx.late_coll)
+ x->rx_collision++;
+
+ if (desc->desc0.rx.crc_err)
+ x->rx_crc++;
+
+ ret = discard_frame;
+ }
+
+ if (desc->desc0.rx.len_err) {
+ ret = discard_frame;
+ }
+ if (desc->desc0.rx.mii_err) {
+ ret = discard_frame;
+ }
+
+ return ret;
+}
+
+int desc_get_own(struct dma_desc *desc)
+{
+ return desc->desc0.all & 0x80000000;
+}
+
+int desc_get_tx_ls(struct dma_desc *desc)
+{
+ return desc->desc1.tx.last_seg;
+}
+
+int sunxi_geth_register(void *iobase, int version, unsigned int div)
+{
+ hwdev.ver = version;
+ hwdev.iobase = iobase;
+ hwdev.mdc_div = div;
+
+ return 0;
+}
+
+int sunxi_mac_reset(void *iobase, void (*delay)(int), int n)
+{
+ unsigned int value;
+
+ /* DMA SW reset */
+ value = readl(iobase + GETH_BASIC_CTL1);
+ value |= SOFT_RST;
+ writel(value, iobase + GETH_BASIC_CTL1);
+
+ delay(n);
+
+ return !!(readl(iobase + GETH_BASIC_CTL1) & SOFT_RST);
+}
diff --git a/drivers/net/ethernet/allwinnertmp/sunxi-gmac.c b/drivers/net/ethernet/allwinnertmp/sunxi-gmac.c
new file mode 100644
index 00000000..0c67877
--- /dev/null
+++ b/drivers/net/ethernet/allwinnertmp/sunxi-gmac.c
@@ -0,0 +1,2240 @@
+/*
+ * linux/drivers/net/ethernet/allwinner/sunxi_gmac.c
+ *
+ * Copyright © 2016-2018, fuzhaoke
+ * Author: fuzhaoke <fuzhaoke at allwinnertech.com>
+ *
+ * This file is provided under a dual BSD/GPL license. When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * 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.
+ */
+//#include <linux/clk.h>
+//#include <linux/clk-provider.h>
+#include <linux/mii.h>
+#include <linux/gpio.h>
+#include <linux/crc32.h>
+#include <linux/skbuff.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
+#include <linux/platform_device.h>
+//#include <linux/pinctrl/consumer.h>
+//#include <linux/pinctrl/pinctrl.h>
+#include <linux/crypto.h>
+#include <crypto/algapi.h>
+#include <crypto/hash.h>
+#include <linux/err.h>
+#include <linux/scatterlist.h>
+//#include <linux/regulator/consumer.h>
+#include <linux/of_net.h>
+//#include <linux/of_gpio.h>
+#include <linux/io.h>
+//#include <linux/sunxi-sid.h>
+//#include <linux/sunxi-gpio.h>
+//#include <linux/reset.h>
+#include "sunxi-gmac.h"
+
+#define SUNXI_GMAC_VERSION "1.0.0"
+
+#define DMA_DESC_RX 256
+#define DMA_DESC_TX 256
+#define BUDGET (dma_desc_rx / 4)
+#define TX_THRESH (dma_desc_tx / 4)
+
+#define HASH_TABLE_SIZE 64
+#define MAX_BUF_SZ (SZ_2K - 1)
+
+#define POWER_CHAN_NUM 3
+
+#undef PKT_DEBUG
+#undef DESC_PRINT
+
+#define circ_cnt(head, tail, size) (((head) > (tail)) ? \
+ ((head) - (tail)) : \
+ ((head) - (tail)) & ((size) - 1))
+
+#define circ_space(head, tail, size) circ_cnt((tail), ((head) + 1), (size))
+
+#define circ_inc(n, s) (((n) + 1) % (s))
+
+#define GETH_MAC_ADDRESS "00:00:00:00:00:00"
+static char *mac_str = GETH_MAC_ADDRESS;
+module_param(mac_str, charp, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(mac_str, "MAC Address String.(xx:xx:xx:xx:xx:xx)");
+
+static int rxmode = 1;
+module_param(rxmode, int, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(rxmode, "DMA threshold control value");
+
+static int txmode = 1;
+module_param(txmode, int, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(txmode, "DMA threshold control value");
+
+static int pause = 0x400;
+module_param(pause, int, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(pause, "Flow Control Pause Time");
+
+#define TX_TIMEO 5000
+static int watchdog = TX_TIMEO;
+module_param(watchdog, int, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(watchdog, "Transmit timeout in milliseconds");
+
+static int dma_desc_rx = DMA_DESC_RX;
+module_param(dma_desc_rx, int, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(watchdog, "The number of receive's descriptors");
+
+static int dma_desc_tx = DMA_DESC_TX;
+module_param(dma_desc_tx, int, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(watchdog, "The number of transmit's descriptors");
+
+/* - 0: Flow Off
+ * - 1: Rx Flow
+ * - 2: Tx Flow
+ * - 3: Rx & Tx Flow
+ */
+static int flow_ctrl;
+module_param(flow_ctrl, int, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(flow_ctrl, "Flow control [0: off, 1: rx, 2: tx, 3: both]");
+
+struct geth_priv {
+ struct dma_desc *dma_tx;
+ struct sk_buff **tx_sk;
+ unsigned int tx_clean;
+ unsigned int tx_dirty;
+ dma_addr_t dma_tx_phy;
+
+ unsigned long buf_sz;
+
+ struct dma_desc *dma_rx;
+ struct sk_buff **rx_sk;
+ unsigned int rx_clean;
+ unsigned int rx_dirty;
+ dma_addr_t dma_rx_phy;
+
+ struct net_device *ndev;
+ struct device *dev;
+ struct napi_struct napi;
+
+ struct geth_extra_stats xstats;
+
+ struct mii_bus *mii;
+ int link;
+ int speed;
+ int duplex;
+#define INT_PHY 0
+#define EXT_PHY 1
+ int phy_ext;
+ phy_interface_t phy_interface;
+
+ void __iomem *base;
+ void __iomem *base_phy;
+/*
+ struct clk *geth_clk;
+ struct clk *ephy_clk;
+ struct reset_control *reset;
+ struct pinctrl *pinctrl;
+*/
+ struct regulator *gmac_power[POWER_CHAN_NUM];
+ bool is_suspend;
+ int phyrst;
+ u8 rst_active_low;
+ /* definition spinlock */
+ spinlock_t lock;
+ spinlock_t tx_lock;
+
+ /* whether using ephy_clk */
+ int use_ephy_clk;
+ int phy_addr;
+
+ /* adjust transmit clock delay, value: 0~7 */
+ /* adjust receive clock delay, value: 0~31 */
+ unsigned int tx_delay;
+ unsigned int rx_delay;
+
+ /* resume work */
+ struct work_struct eth_work;
+};
+
+static u64 geth_dma_mask = DMA_BIT_MASK(32);
+
+void sunxi_udelay(int n)
+{
+ udelay(n);
+}
+
+static int geth_stop(struct net_device *ndev);
+static int geth_open(struct net_device *ndev);
+static void geth_tx_complete(struct geth_priv *priv);
+static void geth_rx_refill(struct net_device *ndev);
+
+#ifdef CONFIG_GETH_ATTRS
+static ssize_t adjust_bgs_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ int value = 0;
+ u32 efuse_value;
+ struct net_device *ndev = to_net_dev(dev);
+ struct geth_priv *priv = netdev_priv(ndev);
+
+ if (priv->phy_ext == INT_PHY) {
+ value = readl(priv->base_phy) >> 28;
+ if (sunxi_efuse_read(EFUSE_OEM_NAME, &efuse_value) != 0)
+ pr_err("get PHY efuse fail!\n");
+ else
+#if IS_ENABLED(CONFIG_ARCH_SUN50IW2)
+ value = value - ((efuse_value >> 24) & 0x0F);
+#else
+ pr_warn("miss config come from efuse!\n");
+#endif
+ }
+
+ return sprintf(buf, "bgs: %d\n", value);
+}
+
+static ssize_t adjust_bgs_write(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned int out = 0;
+ struct net_device *ndev = to_net_dev(dev);
+ struct geth_priv *priv = netdev_priv(ndev);
+ u32 clk_value = readl(priv->base_phy);
+ u32 efuse_value;
+
+ out = simple_strtoul(buf, NULL, 10);
+
+ if (priv->phy_ext == INT_PHY) {
+ clk_value &= ~(0xF << 28);
+ if (sunxi_efuse_read(EFUSE_OEM_NAME, &efuse_value) != 0)
+ pr_err("get PHY efuse fail!\n");
+ else
+#if IS_ENABLED(CONFIG_ARCH_SUN50IW2)
+ clk_value |= (((efuse_value >> 24) & 0x0F) + out) << 28;
+#else
+ pr_warn("miss config come from efuse!\n");
+#endif
+ }
+
+ writel(clk_value, priv->base_phy);
+
+ return count;
+}
+
+static struct device_attribute adjust_reg[] = {
+ __ATTR(adjust_bgs, 0664, adjust_bgs_show, adjust_bgs_write),
+};
+
+static int geth_create_attrs(struct net_device *ndev)
+{
+ int j, ret;
+
+ for (j = 0; j < ARRAY_SIZE(adjust_reg); j++) {
+ ret = device_create_file(&ndev->dev, &adjust_reg[j]);
+ if (ret)
+ goto sysfs_failed;
+ }
+ goto succeed;
+
+sysfs_failed:
+ while (j--)
+ device_remove_file(&ndev->dev, &adjust_reg[j]);
+succeed:
+ return ret;
+}
+#endif
+
+#ifdef DEBUG
+static void desc_print(struct dma_desc *desc, int size)
+{
+#ifdef DESC_PRINT
+ int i;
+
+ for (i = 0; i < size; i++) {
+ u32 *x = (u32 *)(desc + i);
+
+ pr_info("\t%d [0x%08lx]: %08x %08x %08x %08x\n",
+ i, (unsigned long)(&desc[i]),
+ x[0], x[1], x[2], x[3]);
+ }
+ pr_info("\n");
+#endif
+}
+#endif
+
+static ssize_t extra_tx_stats_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct net_device *ndev = dev_get_drvdata(dev);
+ struct geth_priv *priv = netdev_priv(ndev);
+
+ if (!dev) {
+ pr_err("Argment is invalid\n");
+ return 0;
+ }
+
+ if (!ndev) {
+ pr_err("Net device is null\n");
+ return 0;
+ }
+
+ return sprintf(buf, "tx_underflow: %lu\ntx_carrier: %lu\n"
+ "tx_losscarrier: %lu\nvlan_tag: %lu\n"
+ "tx_deferred: %lu\ntx_vlan: %lu\n"
+ "tx_jabber: %lu\ntx_frame_flushed: %lu\n"
+ "tx_payload_error: %lu\ntx_ip_header_error: %lu\n\n",
+ priv->xstats.tx_underflow, priv->xstats.tx_carrier,
+ priv->xstats.tx_losscarrier, priv->xstats.vlan_tag,
+ priv->xstats.tx_deferred, priv->xstats.tx_vlan,
+ priv->xstats.tx_jabber, priv->xstats.tx_frame_flushed,
+ priv->xstats.tx_payload_error, priv->xstats.tx_ip_header_error);
+}
+static DEVICE_ATTR(extra_tx_stats, 0444, extra_tx_stats_show, NULL);
+
+static ssize_t extra_rx_stats_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct net_device *ndev = dev_get_drvdata(dev);
+ struct geth_priv *priv = netdev_priv(ndev);
+
+ if (!dev) {
+ pr_err("Argment is invalid\n");
+ return 0;
+ }
+
+ if (!ndev) {
+ pr_err("Net device is null\n");
+ return 0;
+ }
+
+ return sprintf(buf, "rx_desc: %lu\nsa_filter_fail: %lu\n"
+ "overflow_error: %lu\nipc_csum_error: %lu\n"
+ "rx_collision: %lu\nrx_crc: %lu\n"
+ "dribbling_bit: %lu\nrx_length: %lu\n"
+ "rx_mii: %lu\nrx_multicast: %lu\n"
+ "rx_gmac_overflow: %lu\nrx_watchdog: %lu\n"
+ "da_rx_filter_fail: %lu\nsa_rx_filter_fail: %lu\n"
+ "rx_missed_cntr: %lu\nrx_overflow_cntr: %lu\n"
+ "rx_vlan: %lu\n\n",
+ priv->xstats.rx_desc, priv->xstats.sa_filter_fail,
+ priv->xstats.overflow_error, priv->xstats.ipc_csum_error,
+ priv->xstats.rx_collision, priv->xstats.rx_crc,
+ priv->xstats.dribbling_bit, priv->xstats.rx_length,
+ priv->xstats.rx_mii, priv->xstats.rx_multicast,
+ priv->xstats.rx_gmac_overflow, priv->xstats.rx_length,
+ priv->xstats.da_rx_filter_fail, priv->xstats.sa_rx_filter_fail,
+ priv->xstats.rx_missed_cntr, priv->xstats.rx_overflow_cntr,
+ priv->xstats.rx_vlan);
+}
+static DEVICE_ATTR(extra_rx_stats, 0444, extra_rx_stats_show, NULL);
+
+static ssize_t gphy_test_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct net_device *ndev = dev_get_drvdata(dev);
+
+ if (!dev) {
+ pr_err("Argment is invalid\n");
+ return 0;
+ }
+
+ if (!ndev) {
+ pr_err("Net device is null\n");
+ return 0;
+ }
+
+ return sprintf(buf, "Usage:\necho [0/1/2/3/4] > gphy_test\n"
+ "0 - Normal Mode\n"
+ "1 - Transmit Jitter Test\n"
+ "2 - Transmit Jitter Test(MASTER mode)\n"
+ "3 - Transmit Jitter Test(SLAVE mode)\n"
+ "4 - Transmit Distortion Test\n\n");
+}
+
+static ssize_t gphy_test_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct net_device *ndev = dev_get_drvdata(dev);
+ struct geth_priv *priv = netdev_priv(ndev);
+ u16 value = 0;
+ int ret = 0;
+ u16 data = 0;
+
+ if (!dev) {
+ pr_err("Argument is invalid\n");
+ return count;
+ }
+
+ if (!ndev) {
+ pr_err("Net device is null\n");
+ return count;
+ }
+
+ data = sunxi_mdio_read(priv->base, priv->phy_addr, MII_CTRL1000);
+
+ ret = kstrtou16(buf, 0, &value);
+ if (ret)
+ return ret;
+
+ if (value >= 0 && value <= 4) {
+ data &= ~(0x7 << 13);
+ data |= value << 13;
+ sunxi_mdio_write(priv->base, priv->phy_addr, MII_CTRL1000, data);
+ pr_info("Set MII_CTRL1000(0x09) Reg: 0x%x\n", data);
+ } else {
+ pr_info("unknown value (%d)\n", value);
+ }
+
+ return count;
+}
+
+static DEVICE_ATTR(gphy_test, 0664, gphy_test_show, gphy_test_store);
+
+static ssize_t mii_reg_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct net_device *ndev = NULL;
+ struct geth_priv *priv = NULL;
+
+ if (dev == NULL) {
+ pr_err("Argment is invalid\n");
+ return 0;
+ }
+
+ ndev = dev_get_drvdata(dev);
+ if (ndev == NULL) {
+ pr_err("Net device is null\n");
+ return 0;
+ }
+
+ priv = netdev_priv(ndev);
+ if (priv == NULL) {
+ pr_err("geth_priv is null\n");
+ return 0;
+ }
+
+ if (!netif_running(ndev)) {
+ pr_warn("eth is down!\n");
+ return 0;
+ }
+
+ return sprintf(buf,
+ "Current MII Registers:\n"
+ "BMCR[0x%02x] = 0x%04x,\t\tBMSR[0x%02x] = 0x%04x,\t\tPHYSID1[0x%02x] = 0x%04x\n"
+ "PHYSID2[0x%02x] = 0x%04x,\t\tADVERTISE[0x%02x] = 0x%04x,\tLPA[0x%02x] = 0x%04x\n"
+ "EXPANSION[0x%02x] = 0x%04x,\tCTRL1000[0x%02x] = 0x%04x,\tSTAT1000[0x%02x] = 0x%04x\n",
+ MII_BMCR, sunxi_mdio_read(priv->base, priv->phy_addr, MII_BMCR),
+ MII_BMSR, sunxi_mdio_read(priv->base, priv->phy_addr, MII_BMSR),
+ MII_PHYSID1, sunxi_mdio_read(priv->base, priv->phy_addr, MII_PHYSID1),
+ MII_PHYSID2, sunxi_mdio_read(priv->base, priv->phy_addr, MII_PHYSID2),
+ MII_ADVERTISE, sunxi_mdio_read(priv->base, priv->phy_addr, MII_ADVERTISE),
+ MII_LPA, sunxi_mdio_read(priv->base, priv->phy_addr, MII_LPA),
+ MII_EXPANSION, sunxi_mdio_read(priv->base, priv->phy_addr, MII_EXPANSION),
+ MII_CTRL1000, sunxi_mdio_read(priv->base, priv->phy_addr, MII_CTRL1000),
+ MII_STAT1000, sunxi_mdio_read(priv->base, priv->phy_addr, MII_STAT1000));
+}
+static DEVICE_ATTR(mii_reg, 0444, mii_reg_show, NULL);
+
+static ssize_t loopback_test_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "Usage:\necho [0/1/2] > loopback_test\n"
+ "0 - Normal Mode\n"
+ "1 - Mac loopback test mode\n"
+ "2 - Phy loopback test mode\n");
+}
+
+static ssize_t loopback_test_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct net_device *ndev = NULL;
+ struct geth_priv *priv = NULL;
+ u16 value = 0;
+ int ret = 0;
+ u16 data = 0;
+
+ if (dev == NULL) {
+ pr_err("Argment is invalid\n");
+ return count;
+ }
+
+ ndev = dev_get_drvdata(dev);
+ if (ndev == NULL) {
+ pr_err("Net device is null\n");
+ return count;
+ }
+
+ priv = netdev_priv(ndev);
+ if (priv == NULL) {
+ pr_err("geth_priv is null\n");
+ return count;
+ }
+
+ if (!netif_running(ndev)) {
+ pr_warn("eth is down!\n");
+ return count;
+ }
+
+ ret = kstrtou16(buf, 0, &value);
+ if (ret)
+ return ret;
+
+ if (value == 0) { /* normal mode */
+ /* clear mac loopback */
+ sunxi_mac_loopback(priv->base, 0);
+
+ /* clear phy loopback */
+ data = sunxi_mdio_read(priv->base, priv->phy_addr, MII_BMCR);
+ sunxi_mdio_write(priv->base, priv->phy_addr, MII_BMCR, data & ~BMCR_LOOPBACK);
+ } else if (value == 1) { /* mac loopback test mode */
+ data = sunxi_mdio_read(priv->base, priv->phy_addr, MII_BMCR);
+ sunxi_mdio_write(priv->base, priv->phy_addr, MII_BMCR, data & ~BMCR_LOOPBACK);
+
+ sunxi_mac_loopback(priv->base, 1);
+ } else if (value == 2) { /* phy loopback test mode */
+ sunxi_mac_loopback(priv->base, 0);
+
+ data = sunxi_mdio_read(priv->base, priv->phy_addr, MII_BMCR);
+ sunxi_mdio_write(priv->base, priv->phy_addr, MII_BMCR, data | BMCR_LOOPBACK);
+ } else {
+ pr_err("Undefined value (%d)\n", value);
+ }
+
+ return count;
+}
+static DEVICE_ATTR(loopback_test, 0664, loopback_test_show, loopback_test_store);
+
+static int geth_power_on(struct geth_priv *priv)
+{
+ int value;
+
+ value = readl(priv->base_phy);
+ if (priv->phy_ext == INT_PHY) {
+ value |= (1 << 15);
+ value &= ~(1 << 16);
+ value |= (3 << 17);
+ } else {
+ value &= ~(1 << 15);
+/*
+ for (i = 0; i < POWER_CHAN_NUM; i++) {
+ if (IS_ERR_OR_NULL(priv->gmac_power[i]))
+ continue;
+ if (regulator_enable(priv->gmac_power[i]) != 0) {
+ pr_err("gmac-power%d enable error\n", i);
+ return -EINVAL;
+ }
+ }
+*/
+ }
+
+ writel(value, priv->base_phy);
+
+ return 0;
+}
+
+static void geth_power_off(struct geth_priv *priv)
+{
+ int value;
+
+ if (priv->phy_ext == INT_PHY) {
+ value = readl(priv->base_phy);
+ value |= (1 << 16);
+ writel(value, priv->base_phy);
+ } else {
+/*
+ for (i = 0; i < POWER_CHAN_NUM; i++) {
+ if (IS_ERR_OR_NULL(priv->gmac_power[i]))
+ continue;
+ regulator_disable(priv->gmac_power[i]);
+ }
+*/
+ }
+}
+
+/* PHY interface operations */
+static int geth_mdio_read(struct mii_bus *bus, int phyaddr, int phyreg)
+{
+ struct net_device *ndev = bus->priv;
+ struct geth_priv *priv = netdev_priv(ndev);
+
+ return (int)sunxi_mdio_read(priv->base, phyaddr, phyreg);
+}
+
+static int geth_mdio_write(struct mii_bus *bus, int phyaddr,
+ int phyreg, u16 data)
+{
+ struct net_device *ndev = bus->priv;
+ struct geth_priv *priv = netdev_priv(ndev);
+
+ sunxi_mdio_write(priv->base, phyaddr, phyreg, data);
+
+ return 0;
+}
+
+static int geth_mdio_reset(struct mii_bus *bus)
+{
+ struct net_device *ndev = bus->priv;
+ struct geth_priv *priv = netdev_priv(ndev);
+
+ return sunxi_mdio_reset(priv->base);
+}
+
+static void geth_adjust_link(struct net_device *ndev)
+{
+ struct geth_priv *priv = netdev_priv(ndev);
+ struct phy_device *phydev = ndev->phydev;
+ unsigned long flags;
+ int new_state = 0;
+
+ if (!phydev)
+ return;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ if (phydev->link) {
+ /* Now we make sure that we can be in full duplex mode.
+ * If not, we operate in half-duplex mode.
+ */
+ if (phydev->duplex != priv->duplex) {
+ new_state = 1;
+ priv->duplex = phydev->duplex;
+ }
+ /* Flow Control operation */
+ if (phydev->pause)
+ sunxi_flow_ctrl(priv->base, phydev->duplex,
+ flow_ctrl, pause);
+
+ if (phydev->speed != priv->speed) {
+ new_state = 1;
+ priv->speed = phydev->speed;
+ }
+
+ if (priv->link == 0) {
+ new_state = 1;
+ priv->link = phydev->link;
+ }
+
+ if (new_state)
+ sunxi_set_link_mode(priv->base, priv->duplex, priv->speed);
+
+#ifdef LOOPBACK_DEBUG
+ phydev->state = PHY_FORCING;
+#endif
+
+ } else if (priv->link != phydev->link) {
+ new_state = 1;
+ priv->link = 0;
+ priv->speed = 0;
+ priv->duplex = -1;
+ }
+
+ if (new_state)
+ phy_print_status(phydev);
+
+ spin_unlock_irqrestore(&priv->lock, flags);
+}
+
+static int geth_phy_init(struct net_device *ndev)
+{
+ int value;
+ struct mii_bus *new_bus;
+ struct geth_priv *priv = netdev_priv(ndev);
+ struct phy_device *phydev = ndev->phydev;
+
+ /* Fixup the phy interface type */
+ if (priv->phy_ext == INT_PHY) {
+ priv->phy_interface = PHY_INTERFACE_MODE_MII;
+ } else {
+ /* If config gpio to reset the phy device, we should reset it */
+ /*
+ if (gpio_is_valid(priv->phyrst)) {
+ gpio_direction_output(priv->phyrst,
+ priv->rst_active_low);
+ msleep(50);
+ gpio_direction_output(priv->phyrst,
+ !priv->rst_active_low);
+ msleep(50);
+ }
+ */
+ }
+
+ if (priv->is_suspend && phydev)
+ goto resume;
+
+ new_bus = mdiobus_alloc();
+ if (!new_bus) {
+ netdev_err(ndev, "Failed to alloc new mdio bus\n");
+ return -ENOMEM;
+ }
+
+ new_bus->name = dev_name(priv->dev);
+ new_bus->read = &geth_mdio_read;
+ new_bus->write = &geth_mdio_write;
+ new_bus->reset = &geth_mdio_reset;
+ snprintf(new_bus->id, MII_BUS_ID_SIZE, "%s-%x", new_bus->name, 0);
+
+ new_bus->parent = priv->dev;
+ new_bus->priv = ndev;
+
+ if (mdiobus_register(new_bus)) {
+ pr_err("%s: Cannot register as MDIO bus\n", new_bus->name);
+ goto reg_fail;
+ }
+
+ priv->mii = new_bus;
+
+ {
+ int addr;
+
+ for (addr = 0; addr < PHY_MAX_ADDR; addr++) {
+ struct phy_device *phydev_tmp = mdiobus_get_phy(new_bus, addr);
+
+ if (phydev_tmp && (phydev_tmp->phy_id != 0x00)) {
+ phydev = phydev_tmp;
+ priv->phy_addr = addr;
+ break;
+ }
+ }
+ }
+
+ if (!phydev) {
+ netdev_err(ndev, "No PHY found!\n");
+ goto err;
+ }
+
+ phydev->irq = PHY_POLL;
+
+ value = phy_connect_direct(ndev, phydev, &geth_adjust_link, priv->phy_interface);
+ if (value) {
+ netdev_err(ndev, "Could not attach to PHY\n");
+ goto err;
+ } else {
+ netdev_info(ndev, "%s: Type(%d) PHY ID %08x at %d IRQ %s (%s)\n",
+ ndev->name, phydev->interface, phydev->phy_id,
+ phydev->mdio.addr, "poll", dev_name(&phydev->mdio.dev));
+ }
+
+ //phydev->supported &= PHY_GBIT_FEATURES;
+ phydev->is_gigabit_capable = 1;
+ //phydev->advertising = phydev->supported;
+
+resume:
+ phy_write(phydev, MII_BMCR, BMCR_RESET);
+ while (BMCR_RESET & phy_read(phydev, MII_BMCR))
+ msleep(30);
+
+ value = phy_read(phydev, MII_BMCR);
+ phy_write(phydev, MII_BMCR, (value & ~BMCR_PDOWN));
+
+ if (priv->phy_ext == INT_PHY) {
+ /* EPHY Initial */
+ phy_write(phydev, 0x1f, 0x0100); /* switch to page 1 */
+ phy_write(phydev, 0x12, 0x4824); /* Disable APS */
+ phy_write(phydev, 0x1f, 0x0200); /* switchto page 2 */
+ phy_write(phydev, 0x18, 0x0000); /* PHYAFE TRX optimization */
+ phy_write(phydev, 0x1f, 0x0600); /* switchto page 6 */
+ phy_write(phydev, 0x14, 0x708F); /* PHYAFE TX optimization */
+ phy_write(phydev, 0x19, 0x0000);
+ phy_write(phydev, 0x13, 0xf000); /* PHYAFE RX optimization */
+ phy_write(phydev, 0x15, 0x1530);
+ phy_write(phydev, 0x1f, 0x0800); /* switch to page 8 */
+ phy_write(phydev, 0x18, 0x00bc); /* PHYAFE TRX optimization */
+ phy_write(phydev, 0x1f, 0x0100); /* switchto page 1 */
+ /* reg 0x17 bit3,set 0 to disable iEEE */
+ phy_write(phydev, 0x17, phy_read(phydev, 0x17) & (~(1<<3)));
+ phy_write(phydev, 0x1f, 0x0000); /* switch to page 0 */
+ }
+ if (priv->is_suspend)
+ phy_init_hw(phydev);
+
+ return 0;
+
+err:
+ mdiobus_unregister(new_bus);
+reg_fail:
+ mdiobus_free(new_bus);
+
+ return -EINVAL;
+}
+
+static int geth_phy_release(struct net_device *ndev)
+{
+ struct geth_priv *priv = netdev_priv(ndev);
+ struct phy_device *phydev = ndev->phydev;
+ int value = 0;
+
+ /* Stop and disconnect the PHY */
+ if (phydev)
+ phy_stop(phydev);
+
+ priv->link = PHY_DOWN;
+ priv->speed = 0;
+ priv->duplex = -1;
+
+ if (phydev) {
+ value = phy_read(phydev, MII_BMCR);
+ phy_write(phydev, MII_BMCR, (value | BMCR_PDOWN));
+ }
+
+ if (priv->is_suspend)
+ return 0;
+
+ if (phydev) {
+ phy_disconnect(phydev);
+ ndev->phydev = NULL;
+ }
+
+ if (priv->mii) {
+ mdiobus_unregister(priv->mii);
+ priv->mii->priv = NULL;
+ mdiobus_free(priv->mii);
+ priv->mii = NULL;
+ }
+
+ return 0;
+}
+
+static void geth_rx_refill(struct net_device *ndev)
+{
+ struct geth_priv *priv = netdev_priv(ndev);
+ struct dma_desc *desc;
+ struct sk_buff *sk = NULL;
+ dma_addr_t paddr;
+
+ while (circ_space(priv->rx_clean, priv->rx_dirty, dma_desc_rx) > 0) {
+ int entry = priv->rx_clean;
+
+ /* Find the dirty's desc and clean it */
+ desc = priv->dma_rx + entry;
+
+ if (priv->rx_sk[entry] == NULL) {
+ sk = netdev_alloc_skb_ip_align(ndev, priv->buf_sz);
+
+ if (unlikely(sk == NULL))
+ break;
+
+ priv->rx_sk[entry] = sk;
+ paddr = dma_map_single(priv->dev, sk->data,
+ priv->buf_sz, DMA_FROM_DEVICE);
+ desc_buf_set(desc, paddr, priv->buf_sz);
+ }
+
+ /* sync memery */
+ wmb();
+ desc_set_own(desc);
+ priv->rx_clean = circ_inc(priv->rx_clean, dma_desc_rx);
+ }
+}
+
+/* geth_dma_desc_init - initialize the RX/TX descriptor list
+ * @ndev: net device structure
+ * Description: initialize the list for dma.
+ */
+static int geth_dma_desc_init(struct net_device *ndev)
+{
+ struct geth_priv *priv = netdev_priv(ndev);
+ unsigned int buf_sz;
+
+ priv->rx_sk = kzalloc(sizeof(struct sk_buff *) * dma_desc_rx,
+ GFP_KERNEL);
+ if (!priv->rx_sk)
+ return -ENOMEM;
+
+ priv->tx_sk = kzalloc(sizeof(struct sk_buff *) * dma_desc_tx,
+ GFP_KERNEL);
+ if (!priv->tx_sk)
+ goto tx_sk_err;
+
+ /* Set the size of buffer depend on the MTU & max buf size */
+ buf_sz = MAX_BUF_SZ;
+
+ priv->dma_tx = dma_alloc_coherent(priv->dev,
+ dma_desc_tx *
+ sizeof(struct dma_desc),
+ &priv->dma_tx_phy,
+ GFP_KERNEL);
+ if (!priv->dma_tx)
+ goto dma_tx_err;
+
+ priv->dma_rx = dma_alloc_coherent(priv->dev,
+ dma_desc_rx *
+ sizeof(struct dma_desc),
+ &priv->dma_rx_phy,
+ GFP_KERNEL);
+ if (!priv->dma_rx)
+ goto dma_rx_err;
+
+ priv->buf_sz = buf_sz;
+
+ return 0;
+
+dma_rx_err:
+ dma_free_coherent(priv->dev, dma_desc_rx * sizeof(struct dma_desc),
+ priv->dma_tx, priv->dma_tx_phy);
+dma_tx_err:
+ kfree(priv->tx_sk);
+tx_sk_err:
+ kfree(priv->rx_sk);
+
+ return -ENOMEM;
+}
+
+static void geth_free_rx_sk(struct geth_priv *priv)
+{
+ int i;
+
+ for (i = 0; i < dma_desc_rx; i++) {
+ if (priv->rx_sk[i] != NULL) {
+ struct dma_desc *desc = priv->dma_rx + i;
+
+ dma_unmap_single(priv->dev, (u32)desc_buf_get_addr(desc),
+ desc_buf_get_len(desc),
+ DMA_FROM_DEVICE);
+ dev_kfree_skb_any(priv->rx_sk[i]);
+ priv->rx_sk[i] = NULL;
+ }
+ }
+}
+
+static void geth_free_tx_sk(struct geth_priv *priv)
+{
+ int i;
+
+ for (i = 0; i < dma_desc_tx; i++) {
+ if (priv->tx_sk[i] != NULL) {
+ struct dma_desc *desc = priv->dma_tx + i;
+
+ if (desc_buf_get_addr(desc))
+ dma_unmap_single(priv->dev, (u32)desc_buf_get_addr(desc),
+ desc_buf_get_len(desc),
+ DMA_TO_DEVICE);
+ dev_kfree_skb_any(priv->tx_sk[i]);
+ priv->tx_sk[i] = NULL;
+ }
+ }
+}
+
+static void geth_free_dma_desc(struct geth_priv *priv)
+{
+ /* Free the region of consistent memory previously allocated for the DMA */
+ dma_free_coherent(priv->dev, dma_desc_tx * sizeof(struct dma_desc),
+ priv->dma_tx, priv->dma_tx_phy);
+ dma_free_coherent(priv->dev, dma_desc_rx * sizeof(struct dma_desc),
+ priv->dma_rx, priv->dma_rx_phy);
+
+ kfree(priv->rx_sk);
+ kfree(priv->tx_sk);
+}
+
+#if IS_ENABLED(CONFIG_PM)
+/*
+static int geth_select_gpio_state(struct pinctrl *pctrl, char *name)
+{
+ int ret = 0;
+ struct pinctrl_state *pctrl_state = NULL;
+
+ pctrl_state = pinctrl_lookup_state(pctrl, name);
+ if (IS_ERR(pctrl_state)) {
+ pr_err("gmac pinctrl_lookup_state(%s) failed! return %p\n",
+ name, pctrl_state);
+ return -EINVAL;
+ }
+
+ ret = pinctrl_select_state(pctrl, pctrl_state);
+ if (ret < 0)
+ pr_err("gmac pinctrl_select_state(%s) failed! return %d\n",
+ name, ret);
+
+ return ret;
+}
+*/
+static int geth_suspend(struct device *dev)
+{
+ struct net_device *ndev = dev_get_drvdata(dev);
+ struct geth_priv *priv = netdev_priv(ndev);
+
+ cancel_work_sync(&priv->eth_work);
+
+ if (!ndev || !netif_running(ndev))
+ return 0;
+
+ priv->is_suspend = true;
+
+ spin_lock(&priv->lock);
+ netif_device_detach(ndev);
+ spin_unlock(&priv->lock);
+
+ geth_stop(ndev);
+/*
+ if (priv->phy_ext == EXT_PHY)
+
+ geth_select_gpio_state(priv->pinctrl, PINCTRL_STATE_SLEEP);
+*/
+ return 0;
+}
+
+static void geth_resume_work(struct work_struct *work)
+{
+ struct geth_priv *priv = container_of(work, struct geth_priv, eth_work);
+ struct net_device *ndev = priv->ndev;
+ int ret = 0;
+
+ if (!netif_running(ndev))
+ return;
+/*
+ if (priv->phy_ext == EXT_PHY)
+ geth_select_gpio_state(priv->pinctrl, PINCTRL_STATE_DEFAULT);
+*/
+ spin_lock(&priv->lock);
+ netif_device_attach(ndev);
+ spin_unlock(&priv->lock);
+
+#if IS_ENABLED(CONFIG_SUNXI_EPHY)
+ if (!ephy_is_enable()) {
+ pr_info("[geth_resume] ephy is not enable, waiting...\n");
+ msleep(2000);
+ if (!ephy_is_enable()) {
+ netdev_err(ndev, "Wait for ephy resume timeout.\n");
+ return;
+ }
+ }
+#endif
+
+ ret = geth_open(ndev);
+ if (!ret)
+ priv->is_suspend = false;
+}
+
+static void geth_resume(struct device *dev)
+{
+ struct net_device *ndev = dev_get_drvdata(dev);
+ struct geth_priv *priv = netdev_priv(ndev);
+
+ schedule_work(&priv->eth_work);
+}
+
+static int geth_freeze(struct device *dev)
+{
+ return 0;
+}
+
+static int geth_restore(struct device *dev)
+{
+ return 0;
+}
+
+static const struct dev_pm_ops geth_pm_ops = {
+ .complete = geth_resume,
+ .prepare = geth_suspend,
+ .suspend = NULL,
+ .resume = NULL,
+ .freeze = geth_freeze,
+ .restore = geth_restore,
+};
+#else
+static const struct dev_pm_ops geth_pm_ops;
+#endif /* CONFIG_PM */
+
+#define sunxi_get_soc_chipid(x) {}
+static void geth_chip_hwaddr(u8 *addr)
+{
+#define MD5_SIZE 16
+#define CHIP_SIZE 16
+
+ struct crypto_ahash *tfm;
+ struct ahash_request *req;
+ struct scatterlist sg;
+ u8 result[MD5_SIZE];
+ u8 chipid[CHIP_SIZE];
+ int i = 0;
+ int ret = -1;
+
+ memset(chipid, 0, sizeof(chipid));
+ memset(result, 0, sizeof(result));
+
+ sunxi_get_soc_chipid((u8 *)chipid);
+
+ tfm = crypto_alloc_ahash("md5", 0, CRYPTO_ALG_ASYNC);
+ if (IS_ERR(tfm)) {
+ pr_err("Failed to alloc md5\n");
+ return;
+ }
+
+ req = ahash_request_alloc(tfm, GFP_KERNEL);
+ if (!req)
+ goto out;
+
+ ahash_request_set_callback(req, 0, NULL, NULL);
+
+ ret = crypto_ahash_init(req);
+ if (ret) {
+ pr_err("crypto_ahash_init() failed\n");
+ goto out;
+ }
+
+ sg_init_one(&sg, chipid, sizeof(chipid));
+ ahash_request_set_crypt(req, &sg, result, sizeof(chipid));
+ ret = crypto_ahash_update(req);
+ if (ret) {
+ pr_err("crypto_ahash_update() failed for id\n");
+ goto out;
+ }
+
+ ret = crypto_ahash_final(req);
+ if (ret) {
+ pr_err("crypto_ahash_final() failed for result\n");
+ goto out;
+ }
+
+ ahash_request_free(req);
+
+ /* Choose md5 result's [0][2][4][6][8][10] byte as mac address */
+ for (i = 0; i < ETH_ALEN; i++)
+ addr[i] = result[2 * i];
+ addr[0] &= 0xfe; /* clear multicast bit */
+ addr[0] |= 0x02; /* set local assignment bit (IEEE802) */
+
+out:
+ crypto_free_ahash(tfm);
+}
+
+static void geth_check_addr(struct net_device *ndev, unsigned char *mac)
+{
+ int i;
+ char *p = mac;
+
+ if (!is_valid_ether_addr(ndev->dev_addr)) {
+ for (i = 0; i < ETH_ALEN; i++, p++)
+ ndev->dev_addr[i] = simple_strtoul(p, &p, 16);
+
+ if (!is_valid_ether_addr(ndev->dev_addr))
+ geth_chip_hwaddr(ndev->dev_addr);
+
+ if (!is_valid_ether_addr(ndev->dev_addr)) {
+ random_ether_addr(ndev->dev_addr);
+ pr_warn("%s: Use random mac address\n", ndev->name);
+ }
+ }
+}
+
+static int geth_clk_enable(struct geth_priv *priv)
+{
+ int ret;
+ phy_interface_t phy_interface = 0;
+ u32 clk_value;
+ /*u32 efuse_value;*/
+/*
+ ret = reset_control_deassert(priv->reset);
+ if (ret) {
+ pr_err("deassert gmac rst failed!\n");
+ return ret;
+ }
+
+ ret = clk_prepare_enable(priv->geth_clk);
+ if (ret) {
+ pr_err("try to enable geth_clk failed!\n");
+ goto assert_reset;
+ }
+
+ if (((priv->phy_ext == INT_PHY) || priv->use_ephy_clk)
+ && !IS_ERR_OR_NULL(priv->ephy_clk)) {
+ ret = clk_prepare_enable(priv->ephy_clk);
+ if (ret) {
+ pr_err("try to enable ephy_clk failed!\n");
+ goto ephy_clk_disable;
+ }
+ }
+*/
+ phy_interface = priv->phy_interface;
+
+ clk_value = readl(priv->base_phy);
+ if (phy_interface == PHY_INTERFACE_MODE_RGMII)
+ clk_value |= 0x00000004;
+ else
+ clk_value &= (~0x00000004);
+
+ clk_value &= (~0x00002003);
+ if (phy_interface == PHY_INTERFACE_MODE_RGMII
+ || phy_interface == PHY_INTERFACE_MODE_GMII)
+ clk_value |= 0x00000002;
+ else if (phy_interface == PHY_INTERFACE_MODE_RMII)
+ clk_value |= 0x00002001;
+
+ /*if (priv->phy_ext == INT_PHY) {
+ if (0 != sunxi_efuse_read(EFUSE_OEM_NAME, &efuse_value))
+ pr_err("get PHY efuse fail!\n");
+ else
+#if IS_ENABLED(CONFIG_ARCH_SUN50IW2)
+ clk_value |= (((efuse_value >> 24) & 0x0F) + 3) << 28;
+#else
+ pr_warn("miss config come from efuse!\n");
+#endif
+ }*/
+
+ /* Adjust Tx/Rx clock delay */
+ clk_value &= ~(0x07 << 10);
+ clk_value |= ((priv->tx_delay & 0x07) << 10);
+ clk_value &= ~(0x1F << 5);
+ clk_value |= ((priv->rx_delay & 0x1F) << 5);
+
+ writel(clk_value, priv->base_phy);
+
+ return 0;
+/*
+ephy_clk_disable:
+ clk_disable_unprepare(priv->ephy_clk);
+assert_reset:
+ reset_control_assert(priv->reset);
+*/
+ return ret;
+}
+
+static void geth_clk_disable(struct geth_priv *priv)
+{
+/*
+ if (((priv->phy_ext == INT_PHY) || priv->use_ephy_clk)
+ && !IS_ERR_OR_NULL(priv->ephy_clk))
+ clk_disable_unprepare(priv->ephy_clk);
+
+ clk_disable_unprepare(priv->geth_clk);
+ reset_control_assert(priv->reset);
+*/
+}
+
+static void geth_tx_err(struct geth_priv *priv)
+{
+ netif_stop_queue(priv->ndev);
+
+ sunxi_stop_tx(priv->base);
+
+ geth_free_tx_sk(priv);
+ memset(priv->dma_tx, 0, dma_desc_tx * sizeof(struct dma_desc));
+ desc_init_chain(priv->dma_tx, (unsigned long)priv->dma_tx_phy, dma_desc_tx);
+ priv->tx_dirty = 0;
+ priv->tx_clean = 0;
+ sunxi_start_tx(priv->base, priv->dma_tx_phy);
+
+ priv->ndev->stats.tx_errors++;
+ netif_wake_queue(priv->ndev);
+}
+
+static inline void geth_schedule(struct geth_priv *priv)
+{
+ if (likely(napi_schedule_prep(&priv->napi))) {
+ sunxi_int_disable(priv->base);
+ __napi_schedule(&priv->napi);
+ }
+}
+
+static irqreturn_t geth_interrupt(int irq, void *dev_id)
+{
+ struct net_device *ndev = (struct net_device *)dev_id;
+ struct geth_priv *priv = netdev_priv(ndev);
+ int status;
+
+ if (unlikely(!ndev)) {
+ pr_err("%s: invalid ndev pointer\n", __func__);
+ return IRQ_NONE;
+ }
+
+ status = sunxi_int_status(priv->base, (void *)(&priv->xstats));
+
+ if (likely(status == handle_tx_rx))
+ geth_schedule(priv);
+ else if (unlikely(status == tx_hard_error_bump_tc))
+ netdev_info(ndev, "Do nothing for bump tc\n");
+ else if (unlikely(status == tx_hard_error))
+ geth_tx_err(priv);
+ else
+ netdev_info(ndev, "Do nothing.....\n");
+
+ return IRQ_HANDLED;
+}
+
+static int geth_open(struct net_device *ndev)
+{
+ struct geth_priv *priv = netdev_priv(ndev);
+ int ret = 0;
+
+ ret = geth_power_on(priv);
+ if (ret) {
+ netdev_err(ndev, "Power on is failed\n");
+ ret = -EINVAL;
+ }
+
+ ret = geth_clk_enable(priv);
+ if (ret) {
+ pr_err("%s: clk enable is failed\n", __func__);
+ ret = -EINVAL;
+ }
+
+ netif_carrier_off(ndev);
+
+ ret = geth_phy_init(ndev);
+ if (ret)
+ goto err;
+
+ ret = sunxi_mac_reset((void *)priv->base, &sunxi_udelay, 10000);
+ if (ret) {
+ netdev_err(ndev, "Initialize hardware error\n");
+ goto desc_err;
+ }
+
+ sunxi_mac_init(priv->base, txmode, rxmode);
+ sunxi_set_umac(priv->base, ndev->dev_addr, 0);
+
+ if (!priv->is_suspend) {
+ ret = geth_dma_desc_init(ndev);
+ if (ret) {
+ ret = -EINVAL;
+ goto desc_err;
+ }
+ }
+
+ memset(priv->dma_tx, 0, dma_desc_tx * sizeof(struct dma_desc));
+ memset(priv->dma_rx, 0, dma_desc_rx * sizeof(struct dma_desc));
+
+ desc_init_chain(priv->dma_rx, (unsigned long)priv->dma_rx_phy, dma_desc_rx);
+ desc_init_chain(priv->dma_tx, (unsigned long)priv->dma_tx_phy, dma_desc_tx);
+
+ priv->rx_clean = 0;
+ priv->rx_dirty = 0;
+ priv->tx_clean = 0;
+ priv->tx_dirty = 0;
+ geth_rx_refill(ndev);
+
+ /* Extra statistics */
+ memset(&priv->xstats, 0, sizeof(struct geth_extra_stats));
+
+ if (ndev->phydev)
+ phy_start(ndev->phydev);
+
+ sunxi_start_rx(priv->base, (unsigned long)((struct dma_desc *)
+ priv->dma_rx_phy + priv->rx_dirty));
+ sunxi_start_tx(priv->base, (unsigned long)((struct dma_desc *)
+ priv->dma_tx_phy + priv->tx_clean));
+
+ napi_enable(&priv->napi);
+ netif_start_queue(ndev);
+
+ /* Enable the Rx/Tx */
+ sunxi_mac_enable(priv->base);
+
+ return 0;
+
+desc_err:
+ geth_phy_release(ndev);
+err:
+ geth_clk_disable(priv);
+ if (priv->is_suspend)
+ napi_enable(&priv->napi);
+
+ geth_power_off(priv);
+
+ return ret;
+}
+
+static int geth_stop(struct net_device *ndev)
+{
+ struct geth_priv *priv = netdev_priv(ndev);
+
+ netif_stop_queue(ndev);
+ napi_disable(&priv->napi);
+
+ netif_carrier_off(ndev);
+
+ /* Release PHY resources */
+ geth_phy_release(ndev);
+
+ /* Disable Rx/Tx */
+ sunxi_mac_disable(priv->base);
+
+ geth_clk_disable(priv);
+ geth_power_off(priv);
+
+ netif_tx_lock_bh(ndev);
+ /* Release the DMA TX/RX socket buffers */
+ geth_free_rx_sk(priv);
+ geth_free_tx_sk(priv);
+ netif_tx_unlock_bh(ndev);
+
+ /* Ensure that hareware have been stopped */
+ if (!priv->is_suspend)
+ geth_free_dma_desc(priv);
+
+ return 0;
+}
+
+static void geth_tx_complete(struct geth_priv *priv)
+{
+ unsigned int entry = 0;
+ struct sk_buff *skb = NULL;
+ struct dma_desc *desc = NULL;
+ int tx_stat;
+
+ spin_lock(&priv->tx_lock);
+ while (circ_cnt(priv->tx_dirty, priv->tx_clean, dma_desc_tx) > 0) {
+ entry = priv->tx_clean;
+ desc = priv->dma_tx + entry;
+
+ /* Check if the descriptor is owned by the DMA. */
+ if (desc_get_own(desc))
+ break;
+
+ /* Verify tx error by looking at the last segment */
+ if (desc_get_tx_ls(desc)) {
+ tx_stat = desc_get_tx_status(desc, (void *)(&priv->xstats));
+
+ if (likely(!tx_stat))
+ priv->ndev->stats.tx_packets++;
+ else
+ priv->ndev->stats.tx_errors++;
+ }
+
+ dma_unmap_single(priv->dev, (u32)desc_buf_get_addr(desc),
+ desc_buf_get_len(desc), DMA_TO_DEVICE);
+
+ skb = priv->tx_sk[entry];
+ priv->tx_sk[entry] = NULL;
+ desc_init(desc);
+
+ /* Find next dirty desc */
+ priv->tx_clean = circ_inc(entry, dma_desc_tx);
+
+ if (unlikely(skb == NULL))
+ continue;
+
+ dev_kfree_skb(skb);
+ }
+
+ if (unlikely(netif_queue_stopped(priv->ndev)) &&
+ circ_space(priv->tx_dirty, priv->tx_clean, dma_desc_tx) >
+ TX_THRESH) {
+ netif_wake_queue(priv->ndev);
+ }
+ spin_unlock(&priv->tx_lock);
+}
+
+static netdev_tx_t geth_xmit(struct sk_buff *skb, struct net_device *ndev)
+{
+ struct geth_priv *priv = netdev_priv(ndev);
+ unsigned int entry;
+ struct dma_desc *desc, *first;
+ unsigned int len, tmp_len = 0;
+ int i, csum_insert;
+ int nfrags = skb_shinfo(skb)->nr_frags;
+ dma_addr_t paddr;
+
+ spin_lock(&priv->tx_lock);
+ if (unlikely(circ_space(priv->tx_dirty, priv->tx_clean,
+ dma_desc_tx) < (nfrags + 1))) {
+ if (!netif_queue_stopped(ndev)) {
+ netdev_err(ndev, "%s: BUG! Tx Ring full when queue awake\n", __func__);
+ netif_stop_queue(ndev);
+ }
+ spin_unlock(&priv->tx_lock);
+
+ return NETDEV_TX_BUSY;
+ }
+
+ csum_insert = (skb->ip_summed == CHECKSUM_PARTIAL);
+ entry = priv->tx_dirty;
+ first = priv->dma_tx + entry;
+ desc = priv->dma_tx + entry;
+
+ len = skb_headlen(skb);
+ priv->tx_sk[entry] = skb;
+
+#ifdef PKT_DEBUG
+ printk("======TX PKT DATA: ============\n");
+ /* dump the packet */
+ print_hex_dump(KERN_DEBUG, "skb->data: ", DUMP_PREFIX_NONE,
+ 16, 1, skb->data, 64, true);
+#endif
+
+ /* Every desc max size is 2K */
+ while (len != 0) {
+ desc = priv->dma_tx + entry;
+ tmp_len = ((len > MAX_BUF_SZ) ? MAX_BUF_SZ : len);
+
+ paddr = dma_map_single(priv->dev, skb->data, tmp_len, DMA_TO_DEVICE);
+ if (dma_mapping_error(priv->dev, paddr)) {
+ dev_kfree_skb(skb);
+ return -EIO;
+ }
+ desc_buf_set(desc, paddr, tmp_len);
+ /* Don't set the first's own bit, here */
+ if (first != desc) {
+ priv->tx_sk[entry] = NULL;
+ desc_set_own(desc);
+ }
+
+ entry = circ_inc(entry, dma_desc_tx);
+ len -= tmp_len;
+ }
+
+ for (i = 0; i < nfrags; i++) {
+ const skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
+
+ len = skb_frag_size(frag);
+ desc = priv->dma_tx + entry;
+ paddr = skb_frag_dma_map(priv->dev, frag, 0, len, DMA_TO_DEVICE);
+ if (dma_mapping_error(priv->dev, paddr)) {
+ dev_kfree_skb(skb);
+ return -EIO;
+ }
+
+ desc_buf_set(desc, paddr, len);
+ desc_set_own(desc);
+ priv->tx_sk[entry] = NULL;
+ entry = circ_inc(entry, dma_desc_tx);
+ }
+
+ ndev->stats.tx_bytes += skb->len;
+ priv->tx_dirty = entry;
+ desc_tx_close(first, desc, csum_insert);
+
+ desc_set_own(first);
+ spin_unlock(&priv->tx_lock);
+
+ if (circ_space(priv->tx_dirty, priv->tx_clean, dma_desc_tx) <=
+ (MAX_SKB_FRAGS + 1)) {
+ netif_stop_queue(ndev);
+ if (circ_space(priv->tx_dirty, priv->tx_clean, dma_desc_tx) >
+ TX_THRESH)
+ netif_wake_queue(ndev);
+ }
+
+#ifdef DEBUG
+ printk("=======TX Descriptor DMA: 0x%08llx\n", priv->dma_tx_phy);
+ printk("Tx pointor: dirty: %d, clean: %d\n", priv->tx_dirty, priv->tx_clean);
+ desc_print(priv->dma_tx, dma_desc_tx);
+#endif
+ sunxi_tx_poll(priv->base);
+ geth_tx_complete(priv);
+
+ return NETDEV_TX_OK;
+}
+
+static int geth_rx(struct geth_priv *priv, int limit)
+{
+ unsigned int rxcount = 0;
+ unsigned int entry;
+ struct dma_desc *desc;
+ struct sk_buff *skb;
+ int status;
+ int frame_len;
+
+ while (rxcount < limit) {
+ entry = priv->rx_dirty;
+ desc = priv->dma_rx + entry;
+
+ if (desc_get_own(desc))
+ break;
+
+ rxcount++;
+ priv->rx_dirty = circ_inc(priv->rx_dirty, dma_desc_rx);
+
+ /* Get length & status from hardware */
+ frame_len = desc_rx_frame_len(desc);
+ status = desc_get_rx_status(desc, (void *)(&priv->xstats));
+
+ netdev_dbg(priv->ndev, "Rx frame size %d, status: %d\n",
+ frame_len, status);
+
+ skb = priv->rx_sk[entry];
+ if (unlikely(!skb)) {
+ netdev_err(priv->ndev, "Skb is null\n");
+ priv->ndev->stats.rx_dropped++;
+ break;
+ }
+
+#ifdef PKT_DEBUG
+ printk("======RX PKT DATA: ============\n");
+ /* dump the packet */
+ print_hex_dump(KERN_DEBUG, "skb->data: ", DUMP_PREFIX_NONE,
+ 16, 1, skb->data, 64, true);
+#endif
+
+ if (status == discard_frame) {
+ netdev_dbg(priv->ndev, "Get error pkt\n");
+ priv->ndev->stats.rx_errors++;
+ continue;
+ }
+
+ if (unlikely(status != llc_snap))
+ frame_len -= ETH_FCS_LEN;
+
+ priv->rx_sk[entry] = NULL;
+
+ skb_put(skb, frame_len);
+ dma_unmap_single(priv->dev, (u32)desc_buf_get_addr(desc),
+ desc_buf_get_len(desc), DMA_FROM_DEVICE);
+
+ skb->protocol = eth_type_trans(skb, priv->ndev);
+
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+ napi_gro_receive(&priv->napi, skb);
+
+ priv->ndev->stats.rx_packets++;
+ priv->ndev->stats.rx_bytes += frame_len;
+ }
+
+#ifdef DEBUG
+ if (rxcount > 0) {
+ printk("======RX Descriptor DMA: 0x%08llx=\n", priv->dma_rx_phy);
+ printk("RX pointor: dirty: %d, clean: %d\n", priv->rx_dirty, priv->rx_clean);
+ desc_print(priv->dma_rx, dma_desc_rx);
+ }
+#endif
+ geth_rx_refill(priv->ndev);
+
+ return rxcount;
+}
+
+static int geth_poll(struct napi_struct *napi, int budget)
+{
+ struct geth_priv *priv = container_of(napi, struct geth_priv, napi);
+ int work_done = 0;
+
+ geth_tx_complete(priv);
+ work_done = geth_rx(priv, budget);
+
+ if (work_done < budget) {
+ napi_complete(napi);
+ sunxi_int_enable(priv->base);
+ }
+
+ return work_done;
+}
+
+static int geth_change_mtu(struct net_device *ndev, int new_mtu)
+{
+ int max_mtu;
+
+ if (netif_running(ndev)) {
+ pr_err("%s: must be stopped to change its MTU\n", ndev->name);
+ return -EBUSY;
+ }
+
+ max_mtu = SKB_MAX_HEAD(NET_SKB_PAD + NET_IP_ALIGN);
+
+ if ((new_mtu < 46) || (new_mtu > max_mtu)) {
+ pr_err("%s: invalid MTU, max MTU is: %d\n", ndev->name, max_mtu);
+ return -EINVAL;
+ }
+
+ ndev->mtu = new_mtu;
+ netdev_update_features(ndev);
+
+ return 0;
+}
+
+static netdev_features_t geth_fix_features(struct net_device *ndev,
+ netdev_features_t features)
+{
+ return features;
+}
+
+static void geth_set_rx_mode(struct net_device *ndev)
+{
+ struct geth_priv *priv = netdev_priv(ndev);
+ unsigned int value = 0;
+
+ pr_debug("%s: # mcasts %d, # unicast %d\n",
+ __func__, netdev_mc_count(ndev), netdev_uc_count(ndev));
+
+ spin_lock(&priv->lock);
+ if (ndev->flags & IFF_PROMISC) {
+ value = GETH_FRAME_FILTER_PR;
+ } else if ((netdev_mc_count(ndev) > HASH_TABLE_SIZE) ||
+ (ndev->flags & IFF_ALLMULTI)) {
+ value = GETH_FRAME_FILTER_PM; /* pass all multi */
+ sunxi_hash_filter(priv->base, ~0UL, ~0UL);
+ } else if (!netdev_mc_empty(ndev)) {
+ u32 mc_filter[2];
+ struct netdev_hw_addr *ha;
+
+ /* Hash filter for multicast */
+ value = GETH_FRAME_FILTER_HMC;
+
+ memset(mc_filter, 0, sizeof(mc_filter));
+ netdev_for_each_mc_addr(ha, ndev) {
+ /* The upper 6 bits of the calculated CRC are used to
+ * index the contens of the hash table
+ */
+ int bit_nr = bitrev32(~crc32_le(~0, ha->addr, 6)) >> 26;
+ /* The most significant bit determines the register to
+ * use (H/L) while the other 5 bits determine the bit
+ * within the register.
+ */
+ mc_filter[bit_nr >> 5] |= 1 << (bit_nr & 31);
+ }
+ sunxi_hash_filter(priv->base, mc_filter[0], mc_filter[1]);
+ }
+
+ /* Handle multiple unicast addresses (perfect filtering)*/
+ if (netdev_uc_count(ndev) > 16) {
+ /* Switch to promiscuous mode is more than 8 addrs are required */
+ value |= GETH_FRAME_FILTER_PR;
+ } else {
+ int reg = 1;
+ struct netdev_hw_addr *ha;
+
+ netdev_for_each_uc_addr(ha, ndev) {
+ sunxi_set_umac(priv->base, ha->addr, reg);
+ reg++;
+ }
+ }
+
+#ifdef FRAME_FILTER_DEBUG
+ /* Enable Receive all mode (to debug filtering_fail errors) */
+ value |= GETH_FRAME_FILTER_RA;
+#endif
+ sunxi_set_filter(priv->base, value);
+ spin_unlock(&priv->lock);
+}
+
+static void geth_tx_timeout(struct net_device *ndev, unsigned int txqueue)
+{
+ struct geth_priv *priv = netdev_priv(ndev);
+
+ geth_tx_err(priv);
+}
+
+static int geth_ioctl(struct net_device *ndev, struct ifreq *rq, int cmd)
+{
+ if (!netif_running(ndev))
+ return -EINVAL;
+
+ if (!ndev->phydev)
+ return -EINVAL;
+
+ return phy_mii_ioctl(ndev->phydev, rq, cmd);
+}
+
+/* Configuration changes (passed on by ifconfig) */
+static int geth_config(struct net_device *ndev, struct ifmap *map)
+{
+ if (ndev->flags & IFF_UP) /* can't act on a running interface */
+ return -EBUSY;
+
+ /* Don't allow changing the I/O address */
+ if (map->base_addr != ndev->base_addr) {
+ pr_warn("%s: can't change I/O address\n", ndev->name);
+ return -EOPNOTSUPP;
+ }
+
+ /* Don't allow changing the IRQ */
+ if (map->irq != ndev->irq) {
+ pr_warn("%s: can't change IRQ number %d\n", ndev->name, ndev->irq);
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int geth_set_mac_address(struct net_device *ndev, void *p)
+{
+ struct geth_priv *priv = netdev_priv(ndev);
+ struct sockaddr *addr = p;
+
+ if (!is_valid_ether_addr(addr->sa_data))
+ return -EADDRNOTAVAIL;
+
+ memcpy(ndev->dev_addr, addr->sa_data, ndev->addr_len);
+ sunxi_set_umac(priv->base, ndev->dev_addr, 0);
+
+ return 0;
+}
+
+int geth_set_features(struct net_device *ndev, netdev_features_t features)
+{
+ struct geth_priv *priv = netdev_priv(ndev);
+
+ if (features & NETIF_F_LOOPBACK && netif_running(ndev))
+ sunxi_mac_loopback(priv->base, 1);
+ else
+ sunxi_mac_loopback(priv->base, 0);
+
+ return 0;
+}
+
+#if IS_ENABLED(CONFIG_NET_POLL_CONTROLLER)
+/* Polling receive - used by NETCONSOLE and other diagnostic tools
+ * to allow network I/O with interrupts disabled.
+ */
+static void geth_poll_controller(struct net_device *dev)
+{
+ disable_irq(dev->irq);
+ geth_interrupt(dev->irq, dev);
+ enable_irq(dev->irq);
+}
+#endif
+
+static const struct net_device_ops geth_netdev_ops = {
+ .ndo_init = NULL,
+ .ndo_open = geth_open,
+ .ndo_start_xmit = geth_xmit,
+ .ndo_stop = geth_stop,
+ .ndo_change_mtu = geth_change_mtu,
+ .ndo_fix_features = geth_fix_features,
+ .ndo_set_rx_mode = geth_set_rx_mode,
+ .ndo_tx_timeout = geth_tx_timeout,
+ .ndo_do_ioctl = geth_ioctl,
+ .ndo_set_config = geth_config,
+#if IS_ENABLED(CONFIG_NET_POLL_CONTROLLER)
+ .ndo_poll_controller = geth_poll_controller,
+#endif
+ .ndo_set_mac_address = geth_set_mac_address,
+ .ndo_set_features = geth_set_features,
+};
+
+static int geth_check_if_running(struct net_device *ndev)
+{
+ if (!netif_running(ndev))
+ return -EBUSY;
+ return 0;
+}
+
+static int geth_get_sset_count(struct net_device *netdev, int sset)
+{
+ int len;
+
+ switch (sset) {
+ case ETH_SS_STATS:
+ len = 0;
+ return len;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+/*static int geth_ethtool_getsettings(struct net_device *ndev,
+ struct ethtool_cmd *cmd)
+{
+ struct geth_priv *priv = netdev_priv(ndev);
+ struct phy_device *phy = ndev->phydev;
+ int rc;
+
+ if (phy == NULL) {
+ netdev_err(ndev, "%s: %s: PHY is not registered\n",
+ __func__, ndev->name);
+ return -ENODEV;
+ }
+
+ if (!netif_running(ndev)) {
+ pr_err("%s: interface is disabled: we cannot track "
+ "link speed / duplex setting\n", ndev->name);
+ return -EBUSY;
+ }
+
+ cmd->transceiver = XCVR_INTERNAL;
+ spin_lock_irq(&priv->lock);
+ //rc = phy_ethtool_gset(phy, cmd);
+ spin_unlock_irq(&priv->lock);
+
+ return rc;
+}
+
+static int geth_ethtool_setsettings(struct net_device *ndev,
+ struct ethtool_cmd *cmd)
+{
+ struct geth_priv *priv = netdev_priv(ndev);
+ struct phy_device *phy = ndev->phydev;
+ int rc;
+
+ spin_lock(&priv->lock);
+ rc = phy_ethtool_sset(phy, cmd);
+ spin_unlock(&priv->lock);
+
+ return rc;
+}*/
+
+static void geth_ethtool_getdrvinfo(struct net_device *ndev,
+ struct ethtool_drvinfo *info)
+{
+ strlcpy(info->driver, "sunxi_geth", sizeof(info->driver));
+
+#define DRV_MODULE_VERSION "SUNXI Gbgit driver V1.1"
+
+ strcpy(info->version, DRV_MODULE_VERSION);
+ info->fw_version[0] = '\0';
+}
+
+static const struct ethtool_ops geth_ethtool_ops = {
+ .begin = geth_check_if_running,
+ //.get_settings = geth_ethtool_getsettings,
+ //.set_settings = geth_ethtool_setsettings,
+ .get_link = ethtool_op_get_link,
+ .get_pauseparam = NULL,
+ .set_pauseparam = NULL,
+ .get_ethtool_stats = NULL,
+ .get_strings = NULL,
+ .get_wol = NULL,
+ .set_wol = NULL,
+ .get_sset_count = geth_get_sset_count,
+ .get_drvinfo = geth_ethtool_getdrvinfo,
+};
+
+/* config hardware resource */
+static int geth_hw_init(struct platform_device *pdev)
+{
+ struct net_device *ndev = platform_get_drvdata(pdev);
+ struct geth_priv *priv = netdev_priv(ndev);
+ struct device_node *np = pdev->dev.of_node;
+ int ret = 0;
+ struct resource *res;
+ u32 value;
+// struct gpio_config cfg;
+// const char *gmac_power;
+// char power[20];
+// int i;
+
+#if 1
+ priv->phy_ext = EXT_PHY;
+#else
+ priv->phy_ext = INT_PHY;
+#endif
+
+ /* config memery resource */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (unlikely(!res)) {
+ pr_err("%s: ERROR: get gmac memory failed", __func__);
+ return -ENODEV;
+ }
+
+ priv->base = devm_ioremap_resource(&pdev->dev, res);
+ if (!priv->base) {
+ pr_err("%s: ERROR: gmac memory mapping failed", __func__);
+ return -ENOMEM;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ if (unlikely(!res)) {
+ pr_err("%s: ERROR: get phy memory failed", __func__);
+ ret = -ENODEV;
+ goto mem_err;
+ }
+
+ priv->base_phy = devm_ioremap_resource(&pdev->dev, res);
+ if (unlikely(!priv->base_phy)) {
+ pr_err("%s: ERROR: phy memory mapping failed", __func__);
+ ret = -ENOMEM;
+ goto mem_err;
+ }
+
+ /* config IRQ */
+ ndev->irq = platform_get_irq_byname(pdev, "gmacirq");
+ if (ndev->irq == -ENXIO) {
+ pr_err("%s: ERROR: MAC IRQ not found\n", __func__);
+ ret = -ENXIO;
+ goto irq_err;
+ }
+
+ ret = request_irq(ndev->irq, geth_interrupt, IRQF_SHARED, dev_name(&pdev->dev), ndev);
+ if (unlikely(ret < 0)) {
+ pr_err("Could not request irq %d, error: %d\n", ndev->irq, ret);
+ goto irq_err;
+ }
+
+ /* get gmac rst handle */
+/*
+ priv->reset = devm_reset_control_get(&pdev->dev, NULL);
+ if (IS_ERR(priv->reset)) {
+ pr_err("%s: Get gmac reset control failed!\n", __func__);
+ return PTR_ERR(priv->reset);
+ }
+*/
+ /* config clock */
+/*
+ priv->geth_clk = of_clk_get_by_name(np, "gmac");
+ if (unlikely(!priv->geth_clk || IS_ERR(priv->geth_clk))) {
+ pr_err("Get gmac clock failed!\n");
+ ret = -EINVAL;
+ goto clk_err;
+ }
+
+ if (INT_PHY == priv->phy_ext) {
+ priv->ephy_clk = of_clk_get_by_name(np, "ephy");
+ if (unlikely(IS_ERR_OR_NULL(priv->ephy_clk))) {
+ pr_err("Get ephy clock failed!\n");
+ ret = -EINVAL;
+ goto clk_err;
+ }
+ } else {
+ if (!of_property_read_u32(np, "use-ephy25m", &(priv->use_ephy_clk))
+ && priv->use_ephy_clk) {
+ priv->ephy_clk = of_clk_get_by_name(np, "ephy");
+ if (unlikely(IS_ERR_OR_NULL(priv->ephy_clk))) {
+ pr_err("Get ephy clk failed!\n");
+ ret = -EINVAL;
+ goto clk_err;
+ }
+ }
+ }
+*/
+ /* config power regulator */
+/*
+ if (EXT_PHY == priv->phy_ext) {
+ for (i = 0; i < POWER_CHAN_NUM; i++) {
+ snprintf(power, 15, "gmac-power%d", i);
+ ret = of_property_read_string(np, power, &gmac_power);
+ if (ret) {
+ priv->gmac_power[i] = NULL;
+ pr_info("gmac-power%d: NULL\n", i);
+ continue;
+ }
+ priv->gmac_power[i] = regulator_get(NULL, gmac_power);
+ if (IS_ERR(priv->gmac_power[i])) {
+ pr_err("gmac-power%d get error!\n", i);
+ ret = -EINVAL;
+ goto clk_err;
+ }
+ }
+ }
+*/
+ /* config other parameters */
+ of_get_phy_mode(np, &(priv->phy_interface));
+ if (priv->phy_interface != PHY_INTERFACE_MODE_MII &&
+ priv->phy_interface != PHY_INTERFACE_MODE_RGMII &&
+ priv->phy_interface != PHY_INTERFACE_MODE_RMII) {
+ pr_err("Not support phy type!\n");
+ priv->phy_interface = PHY_INTERFACE_MODE_MII;
+ }
+
+ if (!of_property_read_u32(np, "tx-delay", &value))
+ priv->tx_delay = value;
+
+ if (!of_property_read_u32(np, "rx-delay", &value))
+ priv->rx_delay = value;
+
+ /* config pinctrl */
+/*
+ if (EXT_PHY == priv->phy_ext) {
+ priv->phyrst = of_get_named_gpio_flags(np, "phy-rst", 0, (enum of_gpio_flags *)&cfg);
+ priv->rst_active_low = (cfg.data == OF_GPIO_ACTIVE_LOW) ? 1 : 0;
+
+ if (gpio_is_valid(priv->phyrst)) {
+ if (gpio_request(priv->phyrst, "phy-rst") < 0) {
+ pr_err("gmac gpio request fail!\n");
+ ret = -EINVAL;
+ goto pin_err;
+ }
+ }
+
+ priv->pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
+ if (IS_ERR_OR_NULL(priv->pinctrl)) {
+ pr_err("gmac pinctrl error!\n");
+ priv->pinctrl = NULL;
+ ret = -EINVAL;
+ goto pin_err;
+ }
+ }
+*/
+ return 0;
+
+//pin_err:
+/*
+ if (EXT_PHY == priv->phy_ext) {
+ for (i = 0; i < POWER_CHAN_NUM; i++) {
+ if (IS_ERR_OR_NULL(priv->gmac_power[i]))
+ continue;
+ regulator_put(priv->gmac_power[i]);
+ }
+ }
+*/
+//clk_err:
+// free_irq(ndev->irq, ndev);
+irq_err:
+ devm_iounmap(&pdev->dev, priv->base_phy);
+mem_err:
+ devm_iounmap(&pdev->dev, priv->base);
+
+ return ret;
+}
+
+static void geth_hw_release(struct platform_device *pdev)
+{
+ struct net_device *ndev = platform_get_drvdata(pdev);
+ struct geth_priv *priv = netdev_priv(ndev);
+
+ devm_iounmap(&pdev->dev, (priv->base_phy));
+ devm_iounmap(&pdev->dev, priv->base);
+ free_irq(ndev->irq, ndev);
+/*
+ if (priv->geth_clk)
+ clk_put(priv->geth_clk);
+
+ if (EXT_PHY == priv->phy_ext) {
+ for (i = 0; i < POWER_CHAN_NUM; i++) {
+ if (IS_ERR_OR_NULL(priv->gmac_power[i]))
+ continue;
+ regulator_put(priv->gmac_power[i]);
+ }
+
+ if (!IS_ERR_OR_NULL(priv->pinctrl))
+ devm_pinctrl_put(priv->pinctrl);
+
+ if (gpio_is_valid(priv->phyrst))
+ gpio_free(priv->phyrst);
+ }
+
+ if (!IS_ERR_OR_NULL(priv->ephy_clk))
+ clk_put(priv->ephy_clk);
+*/
+}
+
+/**
+ * geth_probe
+ * @pdev: platform device pointer
+ * Description: the driver is initialized through platform_device.
+ */
+static int geth_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+ struct net_device *ndev = NULL;
+ struct geth_priv *priv;
+
+ pr_info("sunxi gmac driver's version: %s\n", SUNXI_GMAC_VERSION);
+
+#if IS_ENABLED(CONFIG_OF)
+ pdev->dev.dma_mask = &geth_dma_mask;
+ pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
+#endif
+
+ ndev = alloc_etherdev(sizeof(struct geth_priv));
+ if (!ndev) {
+ dev_err(&pdev->dev, "could not allocate device.\n");
+ return -ENOMEM;
+ }
+ SET_NETDEV_DEV(ndev, &pdev->dev);
+
+ priv = netdev_priv(ndev);
+ platform_set_drvdata(pdev, ndev);
+
+ /* Must set private data to pdev, before call it */
+ ret = geth_hw_init(pdev);
+ if (0 != ret) {
+ pr_err("geth_hw_init fail!\n");
+ goto hw_err;
+ }
+
+ /* setup the netdevice, fill the field of netdevice */
+ ether_setup(ndev);
+ ndev->netdev_ops = &geth_netdev_ops;
+ netdev_set_default_ethtool_ops(ndev, &geth_ethtool_ops);
+ ndev->base_addr = (unsigned long)priv->base;
+
+ priv->ndev = ndev;
+ priv->dev = &pdev->dev;
+
+ /* TODO: support the VLAN frames */
+ ndev->hw_features = NETIF_F_SG | NETIF_F_HIGHDMA | NETIF_F_IP_CSUM |
+ NETIF_F_IPV6_CSUM | NETIF_F_RXCSUM;
+
+ ndev->features |= ndev->hw_features;
+ ndev->hw_features |= NETIF_F_LOOPBACK;
+ ndev->priv_flags |= IFF_UNICAST_FLT;
+
+ ndev->watchdog_timeo = msecs_to_jiffies(watchdog);
+
+ netif_napi_add(ndev, &priv->napi, geth_poll, BUDGET);
+
+ spin_lock_init(&priv->lock);
+ spin_lock_init(&priv->tx_lock);
+
+ /* The last val is mdc clock ratio */
+ sunxi_geth_register((void *)ndev->base_addr, HW_VERSION, 0x03);
+
+ ret = register_netdev(ndev);
+ if (ret) {
+ netif_napi_del(&priv->napi);
+ pr_err("Error: Register %s failed\n", ndev->name);
+ goto reg_err;
+ }
+
+ /* Before open the device, the mac address should be set */
+ geth_check_addr(ndev, mac_str);
+
+#ifdef CONFIG_GETH_ATTRS
+ geth_create_attrs(ndev);
+#endif
+ device_create_file(&pdev->dev, &dev_attr_gphy_test);
+ device_create_file(&pdev->dev, &dev_attr_mii_reg);
+ device_create_file(&pdev->dev, &dev_attr_loopback_test);
+ device_create_file(&pdev->dev, &dev_attr_extra_tx_stats);
+ device_create_file(&pdev->dev, &dev_attr_extra_rx_stats);
+
+ device_enable_async_suspend(&pdev->dev);
+
+#if IS_ENABLED(CONFIG_PM)
+ INIT_WORK(&priv->eth_work, geth_resume_work);
+#endif
+
+ return 0;
+
+reg_err:
+ geth_hw_release(pdev);
+hw_err:
+ platform_set_drvdata(pdev, NULL);
+ free_netdev(ndev);
+
+ return ret;
+}
+
+static int geth_remove(struct platform_device *pdev)
+{
+ struct net_device *ndev = platform_get_drvdata(pdev);
+ struct geth_priv *priv = netdev_priv(ndev);
+
+ device_remove_file(&pdev->dev, &dev_attr_gphy_test);
+ device_remove_file(&pdev->dev, &dev_attr_mii_reg);
+ device_remove_file(&pdev->dev, &dev_attr_loopback_test);
+ device_remove_file(&pdev->dev, &dev_attr_extra_tx_stats);
+ device_remove_file(&pdev->dev, &dev_attr_extra_rx_stats);
+
+ netif_napi_del(&priv->napi);
+ unregister_netdev(ndev);
+ geth_hw_release(pdev);
+ platform_set_drvdata(pdev, NULL);
+ free_netdev(ndev);
+
+ return 0;
+}
+
+static const struct of_device_id geth_of_match[] = {
+ {.compatible = "allwinner,sunxi-gmac",},
+ {},
+};
+MODULE_DEVICE_TABLE(of, geth_of_match);
+
+static struct platform_driver geth_driver = {
+ .probe = geth_probe,
+ .remove = geth_remove,
+ .driver = {
+ .name = "sunxi-gmac",
+ .owner = THIS_MODULE,
+ .pm = &geth_pm_ops,
+ .of_match_table = geth_of_match,
+ },
+};
+module_platform_driver(geth_driver);
+
+#ifdef MODULE
+static int __init set_mac_addr(char *str)
+{
+ char *p = str;
+
+ if (str && strlen(str))
+ memcpy(mac_str, p, 18);
+
+ return 0;
+}
+__setup("mac_addr=", set_mac_addr);
+#endif
+
+MODULE_DESCRIPTION("Allwinner Gigabit Ethernet driver");
+MODULE_AUTHOR("fuzhaoke <fuzhaoke at allwinnertech.com>");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/net/ethernet/allwinnertmp/sunxi-gmac.h b/drivers/net/ethernet/allwinnertmp/sunxi-gmac.h
new file mode 100644
index 00000000..ea7a6f15
--- /dev/null
+++ b/drivers/net/ethernet/allwinnertmp/sunxi-gmac.h
@@ -0,0 +1,258 @@
+/*
+ * linux/drivers/net/ethernet/allwinner/sunxi_gmac.h
+ *
+ * Copyright © 2016-2018, fuzhaoke
+ * Author: fuzhaoke <fuzhaoke at allwinnertech.com>
+ *
+ * This file is provided under a dual BSD/GPL license. When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * 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.
+ */
+#ifndef __SUNXI_GETH_H__
+#define __SUNXI_GETH_H__
+
+#include <linux/etherdevice.h>
+#include <linux/netdevice.h>
+#include <linux/phy.h>
+#include <linux/module.h>
+#include <linux/init.h>
+
+/* GETH_FRAME_FILTER register value */
+#define GETH_FRAME_FILTER_PR 0x00000001 /* Promiscuous Mode */
+#define GETH_FRAME_FILTER_HUC 0x00000002 /* Hash Unicast */
+#define GETH_FRAME_FILTER_HMC 0x00000004 /* Hash Multicast */
+#define GETH_FRAME_FILTER_DAIF 0x00000008 /* DA Inverse Filtering */
+#define GETH_FRAME_FILTER_PM 0x00000010 /* Pass all multicast */
+#define GETH_FRAME_FILTER_DBF 0x00000020 /* Disable Broadcast frames */
+#define GETH_FRAME_FILTER_SAIF 0x00000100 /* Inverse Filtering */
+#define GETH_FRAME_FILTER_SAF 0x00000200 /* Source Address Filter */
+#define GETH_FRAME_FILTER_HPF 0x00000400 /* Hash or perfect Filter */
+#define GETH_FRAME_FILTER_RA 0x80000000 /* Receive all mode */
+
+/* Default tx descriptor */
+#define TX_SINGLE_DESC0 0x80000000
+#define TX_SINGLE_DESC1 0x63000000
+
+/* Default rx descriptor */
+#define RX_SINGLE_DESC0 0x80000000
+#define RX_SINGLE_DESC1 0x83000000
+
+typedef union {
+ struct {
+ /* TDES0 */
+ unsigned int deferred:1; /* Deferred bit (only half-duplex) */
+ unsigned int under_err:1; /* Underflow error */
+ unsigned int ex_deferral:1; /* Excessive deferral */
+ unsigned int coll_cnt:4; /* Collision count */
+ unsigned int vlan_tag:1; /* VLAN Frame */
+ unsigned int ex_coll:1; /* Excessive collision */
+ unsigned int late_coll:1; /* Late collision */
+ unsigned int no_carr:1; /* No carrier */
+ unsigned int loss_carr:1; /* Loss of collision */
+ unsigned int ipdat_err:1; /* IP payload error */
+ unsigned int frm_flu:1; /* Frame flushed */
+ unsigned int jab_timeout:1; /* Jabber timeout */
+ unsigned int err_sum:1; /* Error summary */
+ unsigned int iphead_err:1; /* IP header error */
+ unsigned int ttss:1; /* Transmit time stamp status */
+ unsigned int reserved0:13;
+ unsigned int own:1; /* Own bit. CPU:0, DMA:1 */
+ } tx;
+
+ /* bits 5 7 0 | Frame status
+ * ----------------------------------------------------------
+ * 0 0 0 | IEEE 802.3 Type frame (length < 1536 octects)
+ * 1 0 0 | IPv4/6 No CSUM errorS.
+ * 1 0 1 | IPv4/6 CSUM PAYLOAD error
+ * 1 1 0 | IPv4/6 CSUM IP HR error
+ * 1 1 1 | IPv4/6 IP PAYLOAD AND HEADER errorS
+ * 0 0 1 | IPv4/6 unsupported IP PAYLOAD
+ * 0 1 1 | COE bypassed.. no IPv4/6 frame
+ * 0 1 0 | Reserved.
+ */
+ struct {
+ /* RDES0 */
+ unsigned int chsum_err:1; /* Payload checksum error */
+ unsigned int crc_err:1; /* CRC error */
+ unsigned int dribbling:1; /* Dribble bit error */
+ unsigned int mii_err:1; /* Received error (bit3) */
+ unsigned int recv_wt:1; /* Received watchdog timeout */
+ unsigned int frm_type:1; /* Frame type */
+ unsigned int late_coll:1; /* Late Collision */
+ unsigned int ipch_err:1; /* IPv header checksum error (bit7) */
+ unsigned int last_desc:1; /* Laset descriptor */
+ unsigned int first_desc:1; /* First descriptor */
+ unsigned int vlan_tag:1; /* VLAN Tag */
+ unsigned int over_err:1; /* Overflow error (bit11) */
+ unsigned int len_err:1; /* Length error */
+ unsigned int sou_filter:1; /* Source address filter fail */
+ unsigned int desc_err:1; /* Descriptor error */
+ unsigned int err_sum:1; /* Error summary (bit15) */
+ unsigned int frm_len:14; /* Frame length */
+ unsigned int des_filter:1; /* Destination address filter fail */
+ unsigned int own:1; /* Own bit. CPU:0, DMA:1 */
+ #define RX_PKT_OK 0x7FFFB77C
+ #define RX_LEN 0x3FFF0000
+ } rx;
+
+ unsigned int all;
+} desc0_u;
+
+typedef union {
+ struct {
+ /* TDES1 */
+ unsigned int buf1_size:11; /* Transmit buffer1 size */
+ unsigned int buf2_size:11; /* Transmit buffer2 size */
+ unsigned int ttse:1; /* Transmit time stamp enable */
+ unsigned int dis_pad:1; /* Disable pad (bit23) */
+ unsigned int adr_chain:1; /* Second address chained */
+ unsigned int end_ring:1; /* Transmit end of ring */
+ unsigned int crc_dis:1; /* Disable CRC */
+ unsigned int cic:2; /* Checksum insertion control (bit27:28) */
+ unsigned int first_sg:1; /* First Segment */
+ unsigned int last_seg:1; /* Last Segment */
+ unsigned int interrupt:1; /* Interrupt on completion */
+ } tx;
+
+ struct {
+ /* RDES1 */
+ unsigned int buf1_size:11; /* Received buffer1 size */
+ unsigned int buf2_size:11; /* Received buffer2 size */
+ unsigned int reserved1:2;
+ unsigned int adr_chain:1; /* Second address chained */
+ unsigned int end_ring:1; /* Received end of ring */
+ unsigned int reserved2:5;
+ unsigned int dis_ic:1; /* Disable interrupt on completion */
+ } rx;
+
+ unsigned int all;
+} desc1_u;
+
+typedef struct dma_desc {
+ desc0_u desc0;
+ desc1_u desc1;
+ /* The address of buffers */
+ unsigned int desc2;
+ /* Next desc's address */
+ unsigned int desc3;
+} __attribute__((packed)) dma_desc_t;
+
+enum rx_frame_status { /* IPC status */
+ good_frame = 0,
+ discard_frame = 1,
+ csum_none = 2,
+ llc_snap = 4,
+};
+
+enum tx_dma_irq_status {
+ tx_hard_error = 1,
+ tx_hard_error_bump_tc = 2,
+ handle_tx_rx = 3,
+};
+
+struct geth_extra_stats {
+ /* Transmit errors */
+ unsigned long tx_underflow;
+ unsigned long tx_carrier;
+ unsigned long tx_losscarrier;
+ unsigned long vlan_tag;
+ unsigned long tx_deferred;
+ unsigned long tx_vlan;
+ unsigned long tx_jabber;
+ unsigned long tx_frame_flushed;
+ unsigned long tx_payload_error;
+ unsigned long tx_ip_header_error;
+
+ /* Receive errors */
+ unsigned long rx_desc;
+ unsigned long sa_filter_fail;
+ unsigned long overflow_error;
+ unsigned long ipc_csum_error;
+ unsigned long rx_collision;
+ unsigned long rx_crc;
+ unsigned long dribbling_bit;
+ unsigned long rx_length;
+ unsigned long rx_mii;
+ unsigned long rx_multicast;
+ unsigned long rx_gmac_overflow;
+ unsigned long rx_watchdog;
+ unsigned long da_rx_filter_fail;
+ unsigned long sa_rx_filter_fail;
+ unsigned long rx_missed_cntr;
+ unsigned long rx_overflow_cntr;
+ unsigned long rx_vlan;
+
+ /* Tx/Rx IRQ errors */
+ unsigned long tx_undeflow_irq;
+ unsigned long tx_process_stopped_irq;
+ unsigned long tx_jabber_irq;
+ unsigned long rx_overflow_irq;
+ unsigned long rx_buf_unav_irq;
+ unsigned long rx_process_stopped_irq;
+ unsigned long rx_watchdog_irq;
+ unsigned long tx_early_irq;
+ unsigned long fatal_bus_error_irq;
+
+ /* Extra info */
+ unsigned long threshold;
+ unsigned long tx_pkt_n;
+ unsigned long rx_pkt_n;
+ unsigned long poll_n;
+ unsigned long sched_timer_n;
+ unsigned long normal_irq_n;
+};
+
+int sunxi_mdio_read(void *, int, int);
+int sunxi_mdio_write(void *, int, int, unsigned short);
+int sunxi_mdio_reset(void *);
+void sunxi_set_link_mode(void *iobase, int duplex, int speed);
+void sunxi_int_disable(void *);
+int sunxi_int_status(void *, struct geth_extra_stats *x);
+int sunxi_mac_init(void *, int txmode, int rxmode);
+void sunxi_set_umac(void *, unsigned char *, int);
+void sunxi_mac_enable(void *);
+void sunxi_mac_disable(void *);
+void sunxi_tx_poll(void *);
+void sunxi_int_enable(void *);
+void sunxi_start_rx(void *, unsigned long);
+void sunxi_start_tx(void *, unsigned long);
+void sunxi_stop_tx(void *);
+void sunxi_stop_rx(void *);
+void sunxi_hash_filter(void *iobase, unsigned long low, unsigned long high);
+void sunxi_set_filter(void *iobase, unsigned long flags);
+void sunxi_flow_ctrl(void *iobase, int duplex, int fc, int pause);
+void sunxi_mac_loopback(void *iobase, int enable);
+
+void desc_buf_set(struct dma_desc *p, unsigned long paddr, int size);
+void desc_set_own(struct dma_desc *p);
+void desc_init_chain(struct dma_desc *p, unsigned long paddr, unsigned int size);
+void desc_tx_close(struct dma_desc *first, struct dma_desc *end, int csum_insert);
+void desc_init(struct dma_desc *p);
+int desc_get_tx_status(struct dma_desc *desc, struct geth_extra_stats *x);
+int desc_buf_get_len(struct dma_desc *desc);
+int desc_buf_get_addr(struct dma_desc *desc);
+int desc_get_rx_status(struct dma_desc *desc, struct geth_extra_stats *x);
+int desc_get_own(struct dma_desc *desc);
+int desc_get_tx_ls(struct dma_desc *desc);
+int desc_rx_frame_len(struct dma_desc *desc);
+
+int sunxi_mac_reset(void *iobase, void (*mdelay)(int), int n);
+int sunxi_geth_register(void *iobase, int version, unsigned int div);
+
+#if IS_ENABLED(CONFIG_SUNXI_EPHY)
+extern int ephy_is_enable(void);
+#endif
+
+#if IS_ENABLED(CONFIG_ARCH_SUN8IW3) \
+ || IS_ENABLED(CONFIG_ARCH_SUN9IW1) \
+ || IS_ENABLED(CONFIG_ARCH_SUN7I)
+#define HW_VERSION 0
+#else
+#define HW_VERSION 1
+#endif
+
+#endif
diff --git a/drivers/net/phy/realtek.c b/drivers/net/phy/realtek.c
index 821e85a..3c86c2a 100644
--- a/drivers/net/phy/realtek.c
+++ b/drivers/net/phy/realtek.c
@@ -338,7 +338,7 @@ static int rtl8211f_config_init(struct phy_device *phydev)
"2ns TX delay was already %s (by pin-strapping RXD1 or bootloader configuration)\n",
val_txdly ? "enabled" : "disabled");
}
-
+return 0;
ret = phy_modify_paged_changed(phydev, 0xd08, 0x15, RTL8211F_RX_DELAY,
val_rxdly);
if (ret < 0) {
--
2.7.4
More information about the linux-riscv
mailing list