From 73d5c27e53e0a2ee71cbd060f19029957fc551a5 Mon Sep 17 00:00:00 2001
From: chraac <chraac@gmail.com>
Date: Fri, 16 Aug 2024 16:44:41 +0800
Subject: driver: allwinner h618 emac

---
 drivers/gpio/gpiolib-of.c                     |   29 +-
 drivers/mfd/Kconfig                           |   10 +
 drivers/mfd/Makefile                          |    1 +
 drivers/mfd/sunxi-ac200.c                     |  289 +++
 drivers/net/ethernet/allwinner/Kconfig        |    8 +
 drivers/net/ethernet/allwinner/Makefile       |    2 +
 drivers/net/ethernet/allwinner/sunxi-gmac.c   | 2217 +++++++++++++++++
 drivers/net/ethernet/allwinner/sunxi-gmac.h   |  270 ++
 .../net/ethernet/allwinner/sunxi_gmac_ops.c   |  768 ++++++
 drivers/net/phy/Kconfig                       |    8 +
 drivers/net/phy/Makefile                      |    1 +
 drivers/net/phy/sunxi-ephy.c                  |  516 ++++
 include/linux/mfd/ac200.h                     |  213 ++
 include/linux/of_gpio.h                       |   18 +
 14 files changed, 4335 insertions(+), 15 deletions(-)
 create mode 100644 drivers/mfd/sunxi-ac200.c
 create mode 100644 drivers/net/ethernet/allwinner/sunxi-gmac.c
 create mode 100644 drivers/net/ethernet/allwinner/sunxi-gmac.h
 create mode 100644 drivers/net/ethernet/allwinner/sunxi_gmac_ops.c
 create mode 100644 drivers/net/phy/sunxi-ephy.c
 create mode 100644 include/linux/mfd/ac200.h

diff --git a/drivers/gpio/gpiolib-of.c b/drivers/gpio/gpiolib-of.c
index 176e9142fd8f..b9f82442a985 100644
--- a/drivers/gpio/gpiolib-of.c
+++ b/drivers/gpio/gpiolib-of.c
@@ -25,21 +25,6 @@
 #include "gpiolib.h"
 #include "gpiolib-of.h"
 
-/*
- * This is Linux-specific flags. By default controllers' and Linux' mapping
- * match, but GPIO controllers are free to translate their own flags to
- * Linux-specific in their .xlate callback. Though, 1:1 mapping is recommended.
- */
-enum of_gpio_flags {
-	OF_GPIO_ACTIVE_LOW = 0x1,
-	OF_GPIO_SINGLE_ENDED = 0x2,
-	OF_GPIO_OPEN_DRAIN = 0x4,
-	OF_GPIO_TRANSITORY = 0x8,
-	OF_GPIO_PULL_UP = 0x10,
-	OF_GPIO_PULL_DOWN = 0x20,
-	OF_GPIO_PULL_DISABLE = 0x40,
-};
-
 /**
  * of_gpio_named_count() - Count GPIOs for a device
  * @np:		device node to count GPIOs for
@@ -428,6 +413,20 @@ static struct gpio_desc *of_get_named_gpiod_flags(const struct device_node *np,
 	return desc;
 }
 
+int of_get_named_gpio_flags(const struct device_node *np, const char *list_name,
+			    int index, enum of_gpio_flags *flags)
+{
+	struct gpio_desc *desc;
+
+	desc = of_get_named_gpiod_flags(np, list_name, index, flags);
+
+	if (IS_ERR(desc))
+		return PTR_ERR(desc);
+	else
+		return desc_to_gpio(desc);
+}
+EXPORT_SYMBOL_GPL(of_get_named_gpio_flags);
+
 /**
  * of_get_named_gpio() - Get a GPIO number to use with GPIO API
  * @np:		device node to get GPIO from
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 77ab69463ae8..5ab57bf43741 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -215,6 +215,16 @@ config MFD_AC200
 	  This driver include only the core APIs. You have to select individual
 	  components like Ethernet PHY or codec under the corresponding menus.
 
+config MFD_AC200_SUNXI
+	tristate "X-Powers AC200 (Sunxi)"
+	select MFD_CORE
+	depends on I2C
+	depends on PWM_SUNXI_ENHANCE
+	help
+	  If you say Y here you get support for the X-Powers AC200 IC.
+	  This driver include only the core APIs. You have to select individual
+	  components like Ethernet PHY or RTC under the corresponding menus.
+
 config MFD_AXP20X
 	tristate
 	select MFD_CORE
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 568ca7b227db..3ba1fcdbbbbd 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -147,6 +147,7 @@ obj-$(CONFIG_MFD_DA9052_I2C)	+= da9052-i2c.o
 
 obj-$(CONFIG_MFD_AC100)		+= ac100.o
 obj-$(CONFIG_MFD_AC200)		+= ac200.o
+obj-$(CONFIG_MFD_AC200_SUNXI)		+= sunxi-ac200.o
 obj-$(CONFIG_MFD_AXP20X)	+= axp20x.o
 obj-$(CONFIG_MFD_AXP20X_I2C)	+= axp20x-i2c.o
 obj-$(CONFIG_MFD_AXP20X_RSB)	+= axp20x-rsb.o
diff --git a/drivers/mfd/sunxi-ac200.c b/drivers/mfd/sunxi-ac200.c
new file mode 100644
index 000000000000..75c032ce147f
--- /dev/null
+++ b/drivers/mfd/sunxi-ac200.c
@@ -0,0 +1,289 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * MFD core driver for X-Powers' AC200 IC
+ *
+ * The AC200 is a chip which is co-packaged with Allwinner H6 SoC and
+ * includes analog audio codec, analog TV encoder, ethernet PHY, eFuse
+ * and RTC.
+ *
+ * Copyright (c) 2020 Jernej Skrabec <jernej.skrabec@siol.net>
+ */
+
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/ac200.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/nvmem-consumer.h>
+
+#define SUNXI_AC300_KEY	(0x1 << 8)
+
+/* Interrupts */
+#define AC200_IRQ_RTC  0
+#define AC200_IRQ_EPHY 1
+#define AC200_IRQ_TVE  2
+
+/* IRQ enable register */
+#define AC200_SYS_IRQ_ENABLE_OUT_EN BIT(15)
+#define AC200_SYS_IRQ_ENABLE_RTC    BIT(12)
+#define AC200_SYS_IRQ_ENABLE_EPHY   BIT(8)
+#define AC200_SYS_IRQ_ENABLE_TVE    BIT(4)
+
+static const struct regmap_range_cfg ac200_range_cfg[] = {
+	{
+		.range_min = AC200_SYS_VERSION,
+		.range_max = AC200_IC_CHARA1,
+		.selector_reg = AC200_TWI_REG_ADDR_H,
+		.selector_mask = 0xff,
+		.selector_shift = 0,
+		.window_start = 0,
+		.window_len = 256,
+	}
+};
+
+static const struct regmap_config ac200_regmap_config = {
+	.name = "ac200",
+	.reg_bits	= 8,
+	.val_bits	= 16,
+	.ranges		= ac200_range_cfg,
+	.num_ranges	= ARRAY_SIZE(ac200_range_cfg),
+	.max_register	= AC200_IC_CHARA1,
+};
+
+static const struct regmap_irq ac200_regmap_irqs[] = {
+	REGMAP_IRQ_REG(AC200_IRQ_RTC,  0, AC200_SYS_IRQ_ENABLE_RTC),
+	REGMAP_IRQ_REG(AC200_IRQ_EPHY, 0, AC200_SYS_IRQ_ENABLE_EPHY),
+	REGMAP_IRQ_REG(AC200_IRQ_TVE,  0, AC200_SYS_IRQ_ENABLE_TVE),
+};
+
+static const struct regmap_irq_chip ac200_regmap_irq_chip = {
+	.name			= "ac200_irq_chip",
+	.status_base		= AC200_SYS_IRQ_STATUS,
+	.mask_base		= AC200_SYS_IRQ_ENABLE,
+	.irqs			= ac200_regmap_irqs,
+	.num_irqs		= ARRAY_SIZE(ac200_regmap_irqs),
+	.num_regs		= 1,
+};
+
+static const struct resource ephy_resource[] = {
+	DEFINE_RES_IRQ(AC200_IRQ_EPHY),
+};
+
+static const struct mfd_cell ac200_cells[] = {
+	{
+		.name		= "ac200-ephy-sunxi",
+		.num_resources	= ARRAY_SIZE(ephy_resource),
+		.resources	= ephy_resource,
+		.of_compatible	= "x-powers,ac200-ephy-sunxi",
+	},
+	{
+		.name = "acx00-codec",
+		.of_compatible	= "x-powers,ac200-codec-sunxi",
+	},
+};
+
+atomic_t ac200_en;
+
+int ac200_enable(void)
+{
+    return atomic_read(&ac200_en);
+}
+
+EXPORT_SYMBOL(ac200_enable);
+
+static uint16_t ephy_caldata = 0;
+
+static int sun50i_ephy_get_calibrate(struct device *dev)
+{
+	struct nvmem_cell *calcell;
+	uint16_t *caldata;
+	size_t callen;
+	int ret = 0;
+
+	calcell = devm_nvmem_cell_get(dev, "calibration");
+	if (IS_ERR(calcell)) {
+		dev_err_probe(dev, PTR_ERR(calcell),
+			      "Failed to get calibration nvmem cell (%pe)\n",
+			      calcell);
+
+		if (PTR_ERR(calcell) == -EPROBE_DEFER)
+			return -EPROBE_DEFER;
+		goto out;
+	}
+
+	caldata = nvmem_cell_read(calcell, &callen);
+	if (IS_ERR(caldata)) {
+		ret = PTR_ERR(caldata);
+		dev_err(dev, "Failed to read calibration data (%pe)\n",
+			caldata);
+		goto out;
+	}
+
+    ephy_caldata = *caldata;
+	kfree(caldata);
+out:
+	return ret;
+}
+
+uint16_t sun50i_ephy_calibrate_value(void)
+{
+    return ephy_caldata;
+}
+
+EXPORT_SYMBOL(sun50i_ephy_calibrate_value);
+
+static int ac200_i2c_probe(struct i2c_client *i2c)
+{
+    struct device *dev = &i2c->dev;
+    struct ac200_dev *ac200;
+    uint32_t ephy_cal;
+    int ret;
+
+    // 24Mhz clock for both ac200 and ac300 devices
+    ac200 = devm_kzalloc(dev, sizeof(*ac200), GFP_KERNEL);
+    if (!ac200)
+        return -ENOMEM;
+
+    ac200->clk = devm_clk_get(dev, NULL);
+    if (IS_ERR(ac200->clk)) {
+        dev_err(dev, "Can't obtain the clock!\n");
+        return PTR_ERR(ac200->clk);
+    }
+
+    ret = clk_prepare_enable(ac200->clk);
+    if (ret) {
+        dev_err(dev, "rclk_prepare_enable failed! \n");
+        return ret;
+    }
+
+    ret = sun50i_ephy_get_calibrate(dev);
+	if (ret) {
+        dev_err(dev, "sun50i get ephy id failed\n");
+        return ret;
+    }
+    ephy_cal = sun50i_ephy_calibrate_value();
+
+    if (ephy_cal & SUNXI_AC300_KEY) {
+        pr_warn("it's ac300, skip the ac200 init!\n");
+        return -EINVAL;
+    } else {
+        pr_warn("it's ac200, ac200 init start!\n");
+    }
+
+    i2c_set_clientdata(i2c, ac200);
+
+    ac200->regmap = devm_regmap_init_i2c(i2c, &ac200_regmap_config);
+    if (IS_ERR(ac200->regmap))
+    {
+        ret = PTR_ERR(ac200->regmap);
+        dev_err(dev, "regmap init failed: %d\n", ret);
+        return ret;
+    }
+
+    /* do a reset to put chip in a known state */
+    ret = regmap_write(ac200->regmap, AC200_SYS_CONTROL, 0);
+    if (ret)
+    {
+        dev_err(dev, "AC200_SYS_CONTROL 0 failed! \n");
+        return ret;
+    }
+	atomic_set(&ac200_en, 0);
+
+    ret = regmap_write(ac200->regmap, AC200_SYS_CONTROL, 1);
+    if (ret)
+    {
+        dev_err(dev, "AC200_SYS_CONTROL 1 failed! \n");
+        return ret;
+    }
+	atomic_set(&ac200_en, 1);
+
+    ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, ac200_cells,
+                               ARRAY_SIZE(ac200_cells), NULL, 0, NULL);
+    if (ret)
+    {
+        dev_err(dev, "failed to add MFD devices: %d\n", ret);
+        return ret;
+    }
+    else
+    {
+        dev_err(dev, "add MFD devices success! \n");
+    }
+
+    return 0;
+}
+
+static void ac200_i2c_remove(struct i2c_client *i2c)
+{
+	struct ac200_dev *ac200 = i2c_get_clientdata(i2c);
+
+	regmap_write(ac200->regmap, AC200_SYS_CONTROL, 0);
+	mfd_remove_devices(&i2c->dev);
+}
+
+static void ac200_i2c_shutdown(struct i2c_client *i2c)
+{
+    struct ac200_dev *ac200 = i2c_get_clientdata(i2c);
+
+    regmap_write(ac200->regmap, AC200_SYS_CONTROL, 0);
+}
+
+static int ac200_i2c_suspend(struct device *dev)
+{
+	struct ac200_dev *ac200 = dev_get_drvdata(dev);
+
+	if (!IS_ERR_OR_NULL(ac200->clk))
+		clk_disable_unprepare(ac200->clk);
+
+	atomic_set(&ac200_en, 0);
+	return 0;
+}
+
+static int ac200_i2c_resume(struct device *dev)
+{
+	struct ac200_dev *ac200 = dev_get_drvdata(dev);
+
+	if (!IS_ERR_OR_NULL(ac200->clk))
+		clk_prepare_enable(ac200->clk);
+
+	atomic_set(&ac200_en, 0);
+	msleep(40);
+    regmap_write(ac200->regmap, AC200_SYS_CONTROL, 1);
+	atomic_set(&ac200_en, 1);
+	return 0;
+}
+
+static const struct dev_pm_ops ac200_core_pm_ops = {
+	.suspend_late = ac200_i2c_suspend,
+	.resume_early = ac200_i2c_resume,
+};
+
+static const struct i2c_device_id ac200_ids[] = {
+	{ "ac200", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(i2c, ac200_ids);
+
+static const struct of_device_id ac200_of_match[] = {
+	{ .compatible = "x-powers,ac200-sunxi" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, ac200_of_match);
+
+static struct i2c_driver ac200_i2c_driver = {
+	.driver = {
+		.name	= "ac200-sunxi",
+		.of_match_table	= of_match_ptr(ac200_of_match),
+        .pm = &ac200_core_pm_ops,
+	},
+	.probe	= ac200_i2c_probe,
+	.remove = ac200_i2c_remove,
+	.shutdown = ac200_i2c_shutdown,
+	.id_table = ac200_ids,
+};
+module_i2c_driver(ac200_i2c_driver);
+
+MODULE_DESCRIPTION("MFD core driver for AC200");
+MODULE_AUTHOR("Jernej Skrabec <jernej.skrabec@siol.net>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/ethernet/allwinner/Kconfig b/drivers/net/ethernet/allwinner/Kconfig
index 3e81059f8693..36d808810ca4 100644
--- a/drivers/net/ethernet/allwinner/Kconfig
+++ b/drivers/net/ethernet/allwinner/Kconfig
@@ -34,4 +34,12 @@ config SUN4I_EMAC
 	  To compile this driver as a module, choose M here.  The module
 	  will be called sun4i-emac.
 
+config SUNXI_GMAC
+    tristate "Allwinner GMAC support"
+	depends on ARCH_SUNXI
+	depends on OF
+	depends on AC200_PHY_SUNXI
+	select CRC32
+	select MII
+
 endif # NET_VENDOR_ALLWINNER
diff --git a/drivers/net/ethernet/allwinner/Makefile b/drivers/net/ethernet/allwinner/Makefile
index ddd5a5079e8a..56b9c434a5b8 100644
--- a/drivers/net/ethernet/allwinner/Makefile
+++ b/drivers/net/ethernet/allwinner/Makefile
@@ -4,3 +4,5 @@
 #
 
 obj-$(CONFIG_SUN4I_EMAC) += sun4i-emac.o
+sunxi_gmac-objs := sunxi_gmac_ops.o sunxi-gmac.o
+obj-$(CONFIG_SUNXI_GMAC) += sunxi_gmac.o
diff --git a/drivers/net/ethernet/allwinner/sunxi-gmac.c b/drivers/net/ethernet/allwinner/sunxi-gmac.c
new file mode 100644
index 000000000000..249cee0607e3
--- /dev/null
+++ b/drivers/net/ethernet/allwinner/sunxi-gmac.c
@@ -0,0 +1,2217 @@
+/*
+ * linux/drivers/net/ethernet/allwinner/sunxi_gmac.c
+ *
+ * Copyright © 2016-2018, fuzhaoke
+ *		Author: fuzhaoke <fuzhaoke@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 "sunxi-gmac.h"
+#include <linux/timer.h>
+#include <linux/phylink.h>
+#include <linux/reset.h>
+
+
+int sunxi_get_soc_chipid(unsigned char *chipid);
+
+#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 "01:02:03:04:05:06"
+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]");
+
+static unsigned long tx_delay;
+module_param(tx_delay, ulong, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(tx_delay, "Adjust transmit clock delay, value: 0~7");
+
+static unsigned long rx_delay;
+module_param(rx_delay, ulong, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(rx_delay, "Adjust receive clock delay, value: 0~31");
+
+/* whether using ephy_clk */
+static int g_use_ephy_clk;
+static int g_phy_addr;
+
+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;
+
+    struct reset_control *ephy_rst;
+
+    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;
+    int phy_interface;
+
+    void __iomem *base;
+    void __iomem *base_phy;
+    struct clk *geth_clk;
+    struct clk *ephy_clk;
+    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;
+
+    /* 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 defined(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 defined(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 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, g_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, g_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 struct mii_reg_dump mii_reg;
+
+static ssize_t mii_read_show(struct device *dev,
+                             struct device_attribute *attr, char *buf)
+{
+    struct net_device *ndev;
+    struct geth_priv *priv;
+
+    if (!dev) {
+        pr_err("Argment is invalid\n");
+        return 0;
+    }
+
+    ndev = dev_get_drvdata(dev);
+    if (!ndev) {
+        pr_err("Net device is null\n");
+        return 0;
+    }
+
+    priv = netdev_priv(ndev);
+    if (!priv) {
+        pr_err("geth_priv is null\n");
+        return 0;
+    }
+
+    if (!netif_running(ndev)) {
+        pr_warn("eth is down!\n");
+        return 0;
+    }
+
+    mii_reg.value = sunxi_mdio_read(priv->base, mii_reg.addr, mii_reg.reg);
+    return sprintf(buf, "ADDR[0x%02x]:REG[0x%02x] = 0x%04x\n",
+               mii_reg.addr, mii_reg.reg, mii_reg.value);
+}
+
+static ssize_t mii_read_store(struct device *dev,
+                              struct device_attribute *attr, const char *buf, size_t count)
+{
+    struct net_device *ndev;
+    struct geth_priv *priv;
+    int ret = 0;
+    u16 reg, addr;
+    char *ptr;
+
+    ptr = (char *)buf;
+
+    if (!dev) {
+        pr_err("Argment is invalid\n");
+        return count;
+    }
+
+    ndev = dev_get_drvdata(dev);
+    if (!ndev) {
+        pr_err("Net device is null\n");
+        return count;
+    }
+
+    priv = netdev_priv(ndev);
+    if (!priv) {
+        pr_err("geth_priv is null\n");
+        return count;
+    }
+
+    if (!netif_running(ndev)) {
+        pr_warn("eth is down!\n");
+        return count;
+    }
+
+    ret = sunxi_parse_read_str(ptr, &addr, &reg);
+    if (ret)
+        return ret;
+
+    mii_reg.addr = addr;
+    mii_reg.reg = reg;
+
+    return count;
+}
+
+static DEVICE_ATTR(mii_read, 0664, mii_read_show, mii_read_store);
+
+static ssize_t mii_write_show(struct device *dev,
+                              struct device_attribute *attr, char *buf)
+{
+    struct net_device *ndev;
+    struct geth_priv *priv;
+    u16 bef_val, aft_val;
+
+    if (!dev) {
+        pr_err("Argment is invalid\n");
+        return 0;
+    }
+
+    ndev = dev_get_drvdata(dev);
+    if (!ndev) {
+        pr_err("Net device is null\n");
+        return 0;
+    }
+
+    priv = netdev_priv(ndev);
+    if (!priv) {
+        pr_err("geth_priv is null\n");
+        return 0;
+    }
+
+    if (!netif_running(ndev)) {
+        pr_warn("eth is down!\n");
+        return 0;
+    }
+
+    bef_val = sunxi_mdio_read(priv->base, mii_reg.addr, mii_reg.reg);
+    sunxi_mdio_write(priv->base, mii_reg.addr, mii_reg.reg, mii_reg.value);
+    aft_val = sunxi_mdio_read(priv->base, mii_reg.addr, mii_reg.reg);
+    return sprintf(buf, "before ADDR[0x%02x]:REG[0x%02x] = 0x%04x\n"
+                "after  ADDR[0x%02x]:REG[0x%02x] = 0x%04x\n",
+                mii_reg.addr, mii_reg.reg, bef_val,
+                mii_reg.addr, mii_reg.reg, aft_val);
+}
+
+static ssize_t mii_write_store(struct device *dev,
+                               struct device_attribute *attr, const char *buf, size_t count)
+{
+    struct net_device *ndev;
+    struct geth_priv *priv;
+    int ret = 0;
+    u16 reg, addr, val;
+    char *ptr;
+
+    ptr = (char *)buf;
+
+    if (!dev) {
+        pr_err("Argment is invalid\n");
+        return count;
+    }
+
+    ndev = dev_get_drvdata(dev);
+    if (!ndev) {
+        pr_err("Net device is null\n");
+        return count;
+    }
+
+    priv = netdev_priv(ndev);
+    if (!priv) {
+        pr_err("geth_priv is null\n");
+        return count;
+    }
+
+    if (!netif_running(ndev)) {
+        pr_warn("eth is down!\n");
+        return count;
+    }
+
+    ret = sunxi_parse_write_str(ptr, &addr, &reg, &val);
+    if (ret)
+        return ret;
+
+    mii_reg.reg = reg;
+    mii_reg.addr = addr;
+    mii_reg.value = val;
+
+    return count;
+}
+
+static DEVICE_ATTR(mii_write, 0664, mii_write_show, mii_write_store);
+
+static int geth_power_on(struct geth_priv *priv)
+{
+    int value;
+    int i;
+
+    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;
+    int i;
+
+    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;
+    u32 supported = 0, advertising = 0;
+
+    /* 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);
+        }
+    }
+
+    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 (IS_ERR_OR_NULL(phydev_tmp) || phydev_tmp->phy_id == 0xffff) {
+                if (!IS_ERR_OR_NULL(phydev_tmp))
+                    phy_device_remove(phydev_tmp);
+                phydev_tmp = mdiobus_scan_c22(new_bus, addr);
+            }
+
+            if (IS_ERR_OR_NULL(phydev_tmp) || phydev_tmp->phy_id == 0xffff ||
+                phydev_tmp->phy_id == 0x00 || phydev_tmp->phy_id == AC300_ID)
+                continue;
+
+            if (phydev_tmp->phy_id == EPHY_ID || phydev_tmp->phy_id == IP101G_ID) {
+                phydev = phydev_tmp;
+                g_phy_addr = addr;
+                break;
+            }
+        }
+    }
+
+    if (!phydev) {
+        netdev_err(ndev, "No PHY found!\n");
+        goto err;
+    }
+
+    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));
+
+    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));
+    }
+
+#if 0
+    phydev->supported &= PHY_GBIT_FEATURES; //kandy 000002cf
+    phydev->advertising = phydev->supported;
+#else
+
+    ethtool_convert_link_mode_to_legacy_u32(&supported, phydev->supported);
+
+    advertising = supported &= *PHY_BASIC_FEATURES; // kandy 000002cf
+
+    ethtool_convert_legacy_u32_to_link_mode(phydev->supported, supported);
+    ethtool_convert_legacy_u32_to_link_mode(phydev->advertising, advertising);
+#endif
+
+    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 */
+    }
+
+    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));
+        phy_disconnect(phydev);
+        ndev->phydev = NULL;
+    }
+
+#if 1 // defined(CONFIG_SUNXI_EPHY)
+    gmac_ephy_shutdown(); /* Turn off the clock of PHY for low power consumption  */
+#endif
+
+    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);
+}
+
+#ifdef 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;
+
+    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;
+
+    if (!netif_running(ndev))
+        return;
+
+    if (priv->phy_ext == EXT_PHY)
+        geth_select_gpio_state(priv->pinctrl, PINCTRL_STATE_DEFAULT);
+
+    geth_open(ndev);
+
+#if 1 //defined(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
+
+    spin_lock(&priv->lock);
+    netif_device_attach(ndev);
+    spin_unlock(&priv->lock);
+
+}
+
+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 ucc_geth_suspend(struct platform_device *ofdev, pm_message_t state)
+{
+	struct net_device *ndev = platform_get_drvdata(ofdev);
+    struct geth_priv *priv = netdev_priv(ndev);
+
+    cancel_work_sync(&priv->eth_work);
+
+    if (!ndev || !netif_running(ndev))
+        return 0;
+
+    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 int ucc_geth_resume(struct platform_device *ofdev)
+{
+	struct net_device *ndev = platform_get_drvdata(ofdev);
+    struct geth_priv *priv = netdev_priv(ndev);
+
+    schedule_work(&priv->eth_work);
+
+
+	return 0;
+}
+
+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;
+    u8 addr[ETH_ALEN];
+
+    if (!is_valid_ether_addr(ndev->dev_addr)) {
+        for (i = 0; i < ETH_ALEN; i++, p++)
+            addr[i] = simple_strtoul(p, &p, 16);
+
+        if (!is_valid_ether_addr(addr))
+            geth_chip_hwaddr(addr);
+
+        if (!is_valid_ether_addr(addr)) {
+            eth_random_addr(addr);
+            pr_warn("%s: Use random mac address\n", ndev->name);
+        }
+    }
+
+    eth_hw_addr_set(ndev, addr);
+}
+
+static void geth_clk_enable(struct geth_priv *priv)
+{
+    int phy_interface = 0;
+    u32 clk_value;
+    u32 efuse_value;
+
+    if (clk_prepare_enable(priv->geth_clk))
+        pr_err("try to enable geth_clk failed!\n");
+
+    if (((priv->phy_ext == INT_PHY) || g_use_ephy_clk) && !IS_ERR_OR_NULL(priv->ephy_clk)) {
+        if (clk_prepare_enable(priv->ephy_clk))
+            pr_err("try to enable ephy_clk failed!\n");
+    }
+
+    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 defined(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 |= ((tx_delay & 0x07) << 10);
+    clk_value &= ~(0x1F << 5);
+    clk_value |= ((rx_delay & 0x1F) << 5);
+
+    writel(clk_value, priv->base_phy);
+}
+
+static void geth_clk_disable(struct geth_priv *priv)
+{
+    if (((priv->phy_ext == INT_PHY) || g_use_ephy_clk) && !IS_ERR_OR_NULL(priv->ephy_clk))
+        clk_disable_unprepare(priv->ephy_clk);
+
+    clk_disable_unprepare(priv->geth_clk);
+}
+
+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_dma_desc_init(ndev);
+    if (ret) {
+        ret = -EINVAL;
+        return ret;
+    }
+
+    ret = geth_power_on(priv);
+    if (ret) {
+        netdev_err(ndev, "Power on is failed\n");
+        ret = -EINVAL;
+        goto power_err;
+    }
+
+    geth_clk_enable(priv);
+
+    ret = geth_phy_init(ndev);
+    if (ret) {
+        netdev_err(ndev, "phy init again...\n");
+        ret = geth_phy_init(ndev);
+        if (ret) {
+            netdev_err(ndev, "phy init failed\n");
+            ret = -EINVAL;
+            goto phy_err;
+        }
+    }
+
+    ret = sunxi_mac_reset((void *)priv->base, &sunxi_udelay, 10000);
+    if (ret) {
+        netdev_err(ndev, "Initialize hardware error\n");
+        goto mac_err;
+    }
+
+    sunxi_mac_init(priv->base, txmode, rxmode);
+    sunxi_set_umac(priv->base, ndev->dev_addr, 0);
+
+    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));
+
+    /* Enable the Rx/Tx */
+    sunxi_mac_enable(priv->base);
+
+    netif_carrier_on(ndev);
+
+    napi_enable(&priv->napi);
+    netif_start_queue(ndev);
+
+    return 0;
+
+mac_err:
+    geth_phy_release(ndev);
+phy_err:
+    geth_clk_disable(priv);
+
+    geth_power_off(priv);
+
+power_err:
+    geth_free_dma_desc(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 */
+    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;
+
+	eth_hw_addr_set(ndev, addr->sa_data);
+
+    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;
+}
+
+#ifdef 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,
+#ifdef 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;
+    }
+}
+
+#if 0
+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;
+}
+#endif
+
+static void geth_ethtool_getdrvinfo(struct net_device *ndev,
+                                    struct ethtool_drvinfo *info)
+{
+    strscpy(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_ksettings = phy_ethtool_get_link_ksettings,
+    .set_link_ksettings = phy_ethtool_set_link_ksettings,
+    .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,
+};
+
+struct gpio_config {
+	u32	gpio;
+	u32	mul_sel;
+	u32	pull;
+	u32	drv_level;
+	u32	data;
+};
+
+/* 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;
+
+// #ifdef CONFIG_SUNXI_EXT_PHY
+    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;
+    }
+
+    /* config clock */
+    priv->geth_clk = of_clk_get_by_name(np, "bus-emac1");
+    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, "emac-25m");
+        if (unlikely(IS_ERR_OR_NULL(priv->ephy_clk))) {
+            pr_err("Get ephy clock failed!\n");
+            ret = -EINVAL;
+            goto clk_err;
+        }
+    }
+#if 1 // defined(CONFIG_ARCH_SUN8IW12) || defined(CONFIG_ARCH_SUN50IW9)
+    else {
+        if (!of_property_read_u32(np, "use_ephy25m", &g_use_ephy_clk)
+                && g_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;
+            }
+        }
+    }
+#endif
+
+    /* 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 */
+    // priv->phy_interface = of_get_phy_mode(np);
+    // 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;
+    // }
+
+    priv->phy_interface = PHY_INTERFACE_MODE_RMII;
+
+    if (!of_property_read_u32(np, "tx-delay", &value))
+        tx_delay = value;
+
+    if (!of_property_read_u32(np, "rx-delay", &value))
+        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;
+        }
+    }
+
+    priv->ephy_rst = devm_reset_control_get_optional(&pdev->dev, "stmmaceth");
+    reset_control_assert(priv->ephy_rst);
+    reset_control_deassert(priv->ephy_rst);
+
+    msleep(800);
+
+    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);
+    int i;
+
+    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;
+
+#ifdef 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);
+
+    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_read);
+    device_create_file(&pdev->dev, &dev_attr_mii_write);
+
+    device_enable_async_suspend(&pdev->dev);
+
+#ifdef 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 void 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_read);
+    device_remove_file(&pdev->dev, &dev_attr_mii_write);
+
+    netif_napi_del(&priv->napi);
+    unregister_netdev(ndev);
+    geth_hw_release(pdev);
+    platform_set_drvdata(pdev, NULL);
+    free_netdev(ndev);
+}
+
+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,
+	.suspend	= ucc_geth_suspend,
+	.resume		= ucc_geth_resume,
+    .driver = {
+        .name = "sunxi-gmac",
+        .owner = THIS_MODULE,
+        .pm = &geth_pm_ops,
+        .of_match_table = geth_of_match,
+    },
+};
+module_platform_driver(geth_driver);
+
+#ifndef 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@allwinnertech.com>");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/net/ethernet/allwinner/sunxi-gmac.h b/drivers/net/ethernet/allwinner/sunxi-gmac.h
new file mode 100644
index 000000000000..0ba8977d28f4
--- /dev/null
+++ b/drivers/net/ethernet/allwinner/sunxi-gmac.h
@@ -0,0 +1,270 @@
+/*
+ * linux/drivers/net/ethernet/allwinner/sunxi_gmac.h
+ *
+ * Copyright © 2016-2018, fuzhaoke
+ *		Author: fuzhaoke <fuzhaoke@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
+
+#define EPHY_ID			0x00441400
+#define AC300_ID		0xc0000000
+#define EPHY_ID_RTL8211F	0x001CC916
+#define IP101G_ID		0x02430c54
+
+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;
+};
+
+struct mii_reg_dump {
+	u32 addr;
+	u16 reg;
+	u16 value;
+};
+
+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);
+
+int sunxi_parse_read_str(char *str, u16 *addr, u16 *reg);
+int sunxi_parse_write_str(char *str, u16 *addr, u16 *reg, u16 *val);
+extern int ephy_is_enable(void);
+extern int gmac_ephy_shutdown(void);
+
+#if defined(CONFIG_ARCH_SUN8IW3) \
+	|| defined(CONFIG_ARCH_SUN9IW1) \
+	|| defined(CONFIG_ARCH_SUN7I)
+#define HW_VERSION	0
+#else
+#define HW_VERSION	1
+#endif
+
+#endif
diff --git a/drivers/net/ethernet/allwinner/sunxi_gmac_ops.c b/drivers/net/ethernet/allwinner/sunxi_gmac_ops.c
new file mode 100644
index 000000000000..926516835023
--- /dev/null
+++ b/drivers/net/ethernet/allwinner/sunxi_gmac_ops.c
@@ -0,0 +1,768 @@
+/*
+ * linux/drivers/net/ethernet/allwinner/sunxi_gmac_ops.c
+ *
+ * Copyright © 2016-2018, fuzhaoke
+ *		Author: fuzhaoke <fuzhaoke@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_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_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;
+	}
+	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;
+
+	tmp_flags = readl(iobase + GETH_RX_FRM_FLT);
+
+	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_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);
+}
+
+/**
+ * sunxi_parse_read_str - parse the input string for write attri.
+ * @str: string to be parsed, eg: "0x00 0x01".
+ * @addr: store the reg address. eg: 0x00.
+ * @reg: store the expect value. eg: 0x01.
+ *
+ * return 0 if success, otherwise failed.
+ */
+int sunxi_parse_read_str(char *str, u16 *addr, u16 *reg)
+{
+	char *ptr = str;
+	char *tstr = NULL;
+	int ret;
+
+	/*
+	 * Skip the leading whitespace, find the true split symbol.
+	 * And it must be 'address value'.
+	 */
+	tstr = strim(str);
+	ptr = strchr(tstr, ' ');
+	if (!ptr)
+		return -EINVAL;
+
+	/*
+	 * Replaced split symbol with a %NUL-terminator temporary.
+	 * Will be fixed at end.
+	 */
+	*ptr = '\0';
+	ret = kstrtos16(tstr, 16, addr);
+	if (ret)
+		goto out;
+
+	ret = kstrtos16(skip_spaces(ptr + 1), 16, reg);
+
+out:
+	return ret;
+}
+
+/**
+ * sunxi_parse_write_str - parse the input string for compare attri.
+ * @str: string to be parsed, eg: "0x00 0x11 0x11".
+ * @addr: store the address. eg: 0x00.
+ * @reg: store the reg. eg: 0x11.
+ * @val: store the value. eg: 0x11.
+ *
+ * return 0 if success, otherwise failed.
+ */
+int sunxi_parse_write_str(char *str, u16 *addr,
+			  u16 *reg, u16 *val)
+{
+	u16 result_addr[3] = { 0 };
+	char *ptr = str;
+	char *ptr2 = NULL;
+	int i, ret = 0;
+
+	for (i = 0; i < ARRAY_SIZE(result_addr); i++) {
+		ptr = skip_spaces(ptr);
+		ptr2 = strchr(ptr, ' ');
+		if (ptr2)
+			*ptr2 = '\0';
+
+		ret = kstrtou16(ptr, 16, &result_addr[i]);
+		if (!ptr2)
+			break;
+
+		*ptr2 = ' ';
+
+		if (ret)
+			break;
+
+		ptr = ptr2 + 1;
+	}
+
+	*addr = result_addr[0];
+	*reg = result_addr[1];
+	*val = result_addr[2];
+
+	return ret;
+}
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index 9e73ab1b2fbb..782fb4445e55 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -91,6 +91,14 @@ config AC200_PHY
 	help
 	  Fast ethernet PHY as found in X-Powers AC200 multi-function device.
 
+config AC200_PHY_SUNXI
+	tristate "AC200 EPHY(Sunxi)"
+	depends on NVMEM
+	depends on OF
+	depends on MFD_AC200_SUNXI
+	help
+	  Fast ethernet PHY as found in X-Powers AC200(Sunxi) multi-function device.
+
 config AMD_PHY
 	tristate "AMD and Altima PHYs"
 	help
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index 828de189d7ae..88597adfcebb 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -34,6 +34,7 @@ sfp-obj-$(CONFIG_SFP)		+= sfp-bus.o
 obj-y				+= $(sfp-obj-y) $(sfp-obj-m)
 
 obj-$(CONFIG_AC200_PHY)		+= ac200-phy.o
+obj-$(CONFIG_AC200_PHY_SUNXI)		+= sunxi-ephy.o
 obj-$(CONFIG_ADIN_PHY)		+= adin.o
 obj-$(CONFIG_ADIN1100_PHY)	+= adin1100.o
 obj-$(CONFIG_AIR_EN8811H_PHY)   += air_en8811h.o
diff --git a/drivers/net/phy/sunxi-ephy.c b/drivers/net/phy/sunxi-ephy.c
new file mode 100644
index 000000000000..44eb2790ea62
--- /dev/null
+++ b/drivers/net/phy/sunxi-ephy.c
@@ -0,0 +1,516 @@
+/*
+ * Copyright © 2015-2016, Shuge
+ *		Author: Sugar <shugeLinux@gmail.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/string.h>
+#include <linux/errno.h>
+#include <linux/unistd.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/spinlock.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/mii.h>
+#include <linux/ethtool.h>
+#include <linux/phy.h>
+#include <linux/platform_device.h>
+
+#include <asm/io.h>
+#include <asm/irq.h>
+
+#include <linux/mfd/ac200.h>
+
+#define EPHY_AC200 1
+#define EPHY_AC300 2
+
+#define WAIT_MAX_COUNT 200
+
+struct ephy_res {
+	struct phy_device *ac300;
+	struct ac200_dev *ac200;
+	atomic_t ephy_en;
+};
+
+static struct ephy_res ac200_ephy;
+static struct ephy_res ac300_ephy;
+u8 ephy_type;
+
+static int ac200_reg_read(struct ac200_dev *ac200, unsigned short reg)
+{
+	unsigned int val;
+	int ret;
+
+	ret = regmap_read(ac200->regmap, reg, &val);
+
+	if (ret < 0)
+		return ret;
+	else
+		return val;
+}
+
+static int ac200_reg_write(struct ac200_dev *ac200, unsigned short reg, unsigned short val)
+{
+	return regmap_write(ac200->regmap, reg, val);
+}
+
+int ephy_is_enable(void)
+{
+	if (ephy_type == EPHY_AC200)
+		return atomic_read(&ac200_ephy.ephy_en);
+	else if (ephy_type == EPHY_AC300)
+		return atomic_read(&ac300_ephy.ephy_en);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(ephy_is_enable);
+
+static void disable_intelligent_ieee(struct phy_device *phydev)
+{
+	unsigned int value;
+
+	phy_write(phydev, 0x1f, 0x0100); /* switch to page 1 */
+	value = phy_read(phydev, 0x17);  /* read address 0 0x17 register */
+	value &= ~(1 << 3);			  /* reg 0x17 bit 3, set 0 to disable IEEE */
+	phy_write(phydev, 0x17, value);
+	phy_write(phydev, 0x1f, 0x0000); /* switch to page 0 */
+}
+
+static void disable_802_3az_ieee(struct phy_device *phydev)
+{
+	unsigned int value;
+
+	phy_write(phydev, 0xd, 0x7);
+	phy_write(phydev, 0xe, 0x3c);
+	phy_write(phydev, 0xd, 0x1 << 14 | 0x7);
+	value = phy_read(phydev, 0xe);
+	value &= ~(0x1 << 1);
+	phy_write(phydev, 0xd, 0x7);
+	phy_write(phydev, 0xe, 0x3c);
+	phy_write(phydev, 0xd, 0x1 << 14 | 0x7);
+	phy_write(phydev, 0xe, value);
+
+	phy_write(phydev, 0x1f, 0x0200); /* switch to page 2 */
+	phy_write(phydev, 0x18, 0x0000);
+}
+
+static void ephy_config_default(struct phy_device *phydev)
+{
+	phy_write(phydev, 0x1f, 0x0100); /* Switch to Page 1 */
+	phy_write(phydev, 0x12, 0x4824); /* Disable APS */
+
+	phy_write(phydev, 0x1f, 0x0200); /* Switch to Page 2 */
+	phy_write(phydev, 0x18, 0x0000); /* PHYAFE TRX optimization */
+
+	phy_write(phydev, 0x1f, 0x0600); /* Switch to Page 6 */
+	phy_write(phydev, 0x14, 0x708b); /* PHYAFE TX optimization */
+	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 */
+}
+
+static void __maybe_unused ephy_config_fixed(struct phy_device *phydev)
+{
+	phy_write(phydev, 0x1f, 0x0100); /*switch to Page 1 */
+	phy_write(phydev, 0x12, 0x4824); /*Disable APS */
+
+	phy_write(phydev, 0x1f, 0x0200); /*switch to Page 2 */
+	phy_write(phydev, 0x18, 0x0000); /*PHYAFE TRX optimization */
+
+	phy_write(phydev, 0x1f, 0x0600); /*switch to Page 6 */
+	phy_write(phydev, 0x14, 0x7809); /*PHYAFE TX optimization */
+	phy_write(phydev, 0x13, 0xf000); /*PHYAFE RX optimization */
+	phy_write(phydev, 0x10, 0x5523);
+	phy_write(phydev, 0x15, 0x3533);
+
+	phy_write(phydev, 0x1f, 0x0800); /*switch to Page 8 */
+	phy_write(phydev, 0x1d, 0x0844); /*disable auto offset */
+	phy_write(phydev, 0x18, 0x00bc); /*PHYAFE TRX optimization */
+}
+
+static void __maybe_unused ephy_config_cali(struct phy_device *phydev, u16 ephy_cali)
+{
+	int value;
+	value = phy_read(phydev, 0x06);
+	value &= ~(0x0F << 12);
+	value |= (0x0F & (0x03 + ephy_cali)) << 12;
+	phy_write(phydev, 0x06, value);
+
+	return;
+}
+
+int ephy_config_init(struct phy_device *phydev)
+{
+	int value;
+#if 1 //defined(CONFIG_ARCH_SUN50IW9)
+	if (ephy_type == EPHY_AC300) {
+		int ret;
+		u16 ephy_cali = 0;
+		ephy_cali = sun50i_ephy_calibrate_value();
+		if (ret) {
+			pr_err("ephy cali efuse read fail!\n");
+			return -1;
+		}
+		ephy_config_cali(ac300_ephy.ac300, ephy_cali);
+		/*
+		* BIT9: the flag of calibration value
+		* 0: Normal
+		* 1: Low level of calibration value
+		*/
+		if (ephy_cali & BIT(9)) {
+			pr_debug("ac300:ephy cali efuse read: fixed!\n");
+			ephy_config_fixed(phydev);
+		} else {
+			pr_debug("ac300:ephy cali efuse read: default!\n");
+			ephy_config_default(phydev);
+		}
+	} else {
+		pr_debug("ac200:ephy cali efuse read: default!\n");
+		ephy_config_default(phydev);
+	}
+#else
+	ephy_config_default(phydev);
+#endif
+
+#if 0
+	/* Disable Auto Power Saving mode */
+	phy_write(phydev, 0x1f, 0x0100); /* Switch to Page 1 */
+	value = phy_read(phydev, 0x17);
+	value &= ~BIT(13);
+	phy_write(phydev, 0x17, value);
+#endif
+	disable_intelligent_ieee(phydev); /* Disable Intelligent IEEE */
+	disable_802_3az_ieee(phydev);  /* Disable 802.3az IEEE */
+	phy_write(phydev, 0x1f, 0x0000); /* Switch to Page 0 */
+
+#if 1 // def CONFIG_MFD_ACX00
+	if (ephy_type == EPHY_AC200) {
+		value = ac200_reg_read(ac200_ephy.ac200, AC200_EPHY_CTL);
+		if (phydev->interface == PHY_INTERFACE_MODE_RMII)
+			value |= (1 << 11);
+		else
+			value &= (~(1 << 11));
+		ac200_reg_write(ac200_ephy.ac200, AC200_EPHY_CTL, value | (1 << 11));
+	} else
+#endif
+	if (ephy_type == EPHY_AC300) {
+		value = phy_read(ac300_ephy.ac300, 0x06);
+		if (phydev->interface == PHY_INTERFACE_MODE_RMII)
+			value |= (1 << 11);
+		else
+			value &= (~(1 << 11));
+		phy_write(ac300_ephy.ac300, 0x06, value | (1 << 1)); /*LED_POL 1:Low active*/
+	}
+
+#if defined(CONFIG_ARCH_SUN50IW6)
+	value = phy_read(phydev, 0x13);
+	value |= 1 << 12;
+	phy_write(phydev, 0x13, value);
+#endif
+
+	return 0;
+}
+EXPORT_SYMBOL(ephy_config_init);
+
+static int ephy_probe(struct phy_device *phydev)
+{
+	return 0;
+}
+
+#if 0
+static int ephy_ack_interrupt(struct phy_device *phydev)
+{
+	int err = phy_read(phydev, IP101A_G_IRQ_CONF_STATUS);
+
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+#endif
+
+
+static struct phy_driver sunxi_phy_driver = {
+	.phy_id = 0x00441400,
+	.name = "ephy",
+	.phy_id_mask = 0x0ffffff0,
+	.features = PHY_BASIC_FEATURES, //| SUPPORTED_Pause |SUPPORTED_Asym_Pause,
+#if 0
+	.flags = PHY_HAS_INTERRUPT,
+	.ack_interrupt = ephy_ack_interrupt,
+#endif
+	.config_init = &ephy_config_init,
+	.config_aneg = &genphy_config_aneg,
+	.read_status = &genphy_read_status,
+	.suspend = genphy_suspend,
+	.resume = genphy_resume,
+	.probe = ephy_probe,
+};
+
+static void ac300_ephy_enable(struct ephy_res *priv)
+{
+	phy_write(priv->ac300, 0x00, 0x1f83); /* release reset */
+
+	phy_write(priv->ac300, 0x00, 0x1fb7); /* clk gating (24MHz clock)*/
+
+	//phy_write(priv->ac300, 0x05, 0xa81f);
+	phy_write(priv->ac300, 0x05, 0xa819);
+
+	phy_write(priv->ac300, 0x06, 0x00);
+
+	msleep(1000); /* FIXME: fix some board compatible issues. */
+
+	atomic_set(&ac300_ephy.ephy_en, 1);
+}
+
+static void ac300_ephy_disable(struct ephy_res *priv)
+{
+	phy_write(priv->ac300, 0x00, 0x1f40);
+	phy_write(priv->ac300, 0x05, 0xa800);
+
+	phy_write(priv->ac300, 0x06, 0x01);
+
+	atomic_set(&ac300_ephy.ephy_en, 0);
+}
+
+static int ac300_ephy_probe(struct phy_device *phydev)
+{
+	ac300_ephy.ac300 = phydev;
+
+	atomic_set(&ac300_ephy.ephy_en, 0);
+	ac300_ephy_enable(&ac300_ephy);
+
+	ephy_type = EPHY_AC300;
+
+	return 0;
+}
+
+static void ac300_ephy_shutdown(struct phy_device *phydev)
+{
+	ac300_ephy.ac300 = phydev;
+
+	phy_write(ac300_ephy.ac300, 0x00, 0x1f40);
+	phy_write(ac300_ephy.ac300, 0x05, 0xa800);
+	phy_write(ac300_ephy.ac300, 0x06, 0x01);
+}
+
+int gmac_ephy_shutdown(void)
+{
+	if (ephy_type == EPHY_AC300)
+		ac300_ephy_disable(&ac300_ephy);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(gmac_ephy_shutdown);
+
+static int ac300_ephy_suspend(struct phy_device *phydev)
+{
+	ac300_ephy_disable(&ac300_ephy);
+
+	return 0;
+}
+
+static int ac300_ephy_resume(struct phy_device *phydev)
+{
+	ac300_ephy_enable(&ac300_ephy);
+
+	return 0;
+}
+
+static struct phy_driver ac300_ephy_driver = {
+	.phy_id = 0xc0000000,
+	.name = "ac300",
+	.phy_id_mask = 0xffffffff,
+	.suspend = ac300_ephy_suspend,
+	.resume = ac300_ephy_resume,
+	.probe = ac300_ephy_probe,
+	// .shutdown = ac300_ephy_shutdown,
+};
+
+
+static void ac200_ephy_enable(struct ephy_res *priv)
+{
+#if 1 //ifdef CONFIG_MFD_ACX00
+	int value;
+	unsigned char i = 0;
+#if 1 // defined(CONFIG_ARCH_SUN50IW6) || defined(CONFIG_ARCH_SUN50IW9)
+	u16 ephy_cali = 0;
+#endif
+
+	if (!ac200_enable()) {
+		for (i = 0; i < WAIT_MAX_COUNT; i++) {
+			msleep(10);
+			if (ac200_enable())
+			break;
+		}
+		if (i == WAIT_MAX_COUNT) {
+			pr_err("acx00 is no enable, and ac200_ephy_enable is fail\n");
+			return;
+		}
+	}
+
+	value = ac200_reg_read(priv->ac200, AC200_SYS_EPHY_CTL0);
+	value |= 0x03;
+	ac200_reg_write(priv->ac200, AC200_SYS_EPHY_CTL0, value);
+	value = ac200_reg_read(priv->ac200, AC200_SYS_EPHY_CTL1);
+
+	value |= 0x0f;
+
+	ac200_reg_write(priv->ac200, AC200_SYS_EPHY_CTL1, value);
+	ac200_reg_write(priv->ac200, AC200_EPHY_CTL, 0x06);
+
+	/*for ephy */
+	value = ac200_reg_read(priv->ac200, AC200_EPHY_CTL);
+	value &= ~(0xf << 12);
+
+#if 1 // defined(CONFIG_ARCH_SUN50IW6) || defined(CONFIG_ARCH_SUN50IW9)
+
+	ephy_cali = sun50i_ephy_calibrate_value();
+	value |= (0x0F & (0x03 + ephy_cali)) << 12;
+#else
+	value |= (0x0F & (0x03 + ac200_reg_read(priv->ac200, AC200_SID))) << 12;
+#endif
+
+	ac200_reg_write(priv->ac200, AC200_EPHY_CTL, value);
+
+	atomic_set(&ac200_ephy.ephy_en, 1);
+#endif
+}
+
+static void ac200_ephy_disable(struct ephy_res *priv)
+{
+	int value;
+
+	/* reset ephy */
+	value = ac200_reg_read(priv->ac200, AC200_SYS_EPHY_CTL0);
+	value &= ~0x01;
+	ac200_reg_write(priv->ac200, AC200_SYS_EPHY_CTL0, value);
+
+	/* shutdown ephy */
+	value = ac200_reg_read(priv->ac200, AC200_EPHY_CTL);
+	value |= 0x01;
+	ac200_reg_write(priv->ac200, AC200_EPHY_CTL, value);
+
+	atomic_set(&priv->ephy_en, 0);
+}
+
+static const struct platform_device_id ac200_ephy_id[] = {
+	{ "ac200-ephy", 0},
+	{ },
+};
+MODULE_DEVICE_TABLE(platform, ac200_ephy_id);
+
+static int ac200_ephy_probe(struct platform_device *pdev)
+{
+	struct ac200_dev *ax = dev_get_drvdata(pdev->dev.parent);
+
+	if (!ax)
+		return -ENODEV;
+
+	ac200_ephy.ac200 = ax;
+	ephy_type = EPHY_AC200;
+	platform_set_drvdata(pdev, &ac200_ephy);
+
+	atomic_set(&ac200_ephy.ephy_en, 0);
+	ac200_ephy_enable(&ac200_ephy);
+
+	return 0;
+}
+
+static void ac200_ephy_remove(struct platform_device *pdev)
+{
+	ac200_ephy_disable(&ac200_ephy);
+}
+
+static int ac200_ephy_suspend(struct device *dev)
+{
+	ac200_ephy_disable(&ac200_ephy);
+
+	return 0;
+}
+
+static int ac200_ephy_resume(struct device *dev)
+{
+	ac200_ephy_enable(&ac200_ephy);
+
+	return 0;
+}
+
+/* Suspend hook structures */
+static const struct dev_pm_ops ac200_ephy_pm_ops = {
+	.suspend = ac200_ephy_suspend,
+	.resume = ac200_ephy_resume,
+};
+
+static struct platform_driver ac200_ephy_driver = {
+	.driver = {
+		.name = "ac200-ephy-sunxi",
+		.owner = THIS_MODULE,
+		.pm = &ac200_ephy_pm_ops,
+	},
+	.probe = ac200_ephy_probe,
+	.remove = ac200_ephy_remove,
+	.id_table = ac200_ephy_id,
+};
+
+static int ephy_init(void)
+{
+	int ret = 0;
+
+	ret = platform_driver_register(&ac200_ephy_driver);
+	if (ret)
+		return ret;
+
+	ret = phy_driver_register(&ac300_ephy_driver, THIS_MODULE);
+	if (ret)
+		goto ac300_ephy_error;
+
+	ret = phy_driver_register(&sunxi_phy_driver, THIS_MODULE);
+	if (ret)
+		goto ephy_driver_error;
+
+	return ret;
+
+ephy_driver_error:
+	phy_driver_unregister(&ac300_ephy_driver);
+
+ac300_ephy_error:
+	platform_driver_unregister(&ac200_ephy_driver);
+
+	return ret;
+}
+
+static void ephy_exit(void)
+{
+	phy_driver_unregister(&sunxi_phy_driver);
+	phy_driver_unregister(&ac300_ephy_driver);
+	platform_driver_unregister(&ac200_ephy_driver);
+}
+
+module_init(ephy_init);
+module_exit(ephy_exit);
+
+static struct mdio_device_id __maybe_unused ephy_tbl[] = {
+	{ 0x00441400, 0x0ffffff0 },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(mdio, ephy_tbl);
+
+MODULE_DESCRIPTION("Allwinner EPHY drivers");
+MODULE_AUTHOR("Sugar <shugeLinux@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/ac200.h b/include/linux/mfd/ac200.h
new file mode 100644
index 000000000000..84d89cbfbd3d
--- /dev/null
+++ b/include/linux/mfd/ac200.h
@@ -0,0 +1,213 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * AC200 register list
+ *
+ * Copyright (C) 2019 Jernej Skrabec <jernej.skrabec@siol.net>
+ */
+
+#ifndef __LINUX_MFD_AC200_H
+#define __LINUX_MFD_AC200_H
+
+#include <linux/regmap.h>
+#include <linux/clk.h>
+
+/* interface registers (can be accessed from any page) */
+#define AC200_TWI_CHANGE_TO_RSB		0x3E
+#define AC200_TWI_PAD_DELAY		0xC4
+#define AC200_TWI_REG_ADDR_H		0xFE
+
+/* General registers */
+#define AC200_SYS_VERSION		0x0000
+#define AC200_SYS_CONTROL		0x0002
+#define AC200_SYS_IRQ_ENABLE		0x0004
+#define AC200_SYS_IRQ_STATUS		0x0006
+#define AC200_SYS_CLK_CTL		0x0008
+#define AC200_SYS_DLDO_OSC_CTL		0x000A
+#define AC200_SYS_PLL_CTL0		0x000C
+#define AC200_SYS_PLL_CTL1		0x000E
+#define AC200_SYS_AUDIO_CTL0		0x0010
+#define AC200_SYS_AUDIO_CTL1		0x0012
+#define AC200_SYS_EPHY_CTL0		0x0014
+#define AC200_SYS_EPHY_CTL1		0x0016
+#define AC200_SYS_TVE_CTL0		0x0018
+#define AC200_SYS_TVE_CTL1		0x001A
+
+/* Audio Codec registers */
+#define AC200_AC_SYS_CLK_CTL		0x2000
+#define AC200_SYS_MOD_RST		0x2002
+#define AC200_SYS_SAMP_CTL		0x2004
+#define AC200_I2S_CTL			0x2100
+#define AC200_I2S_CLK			0x2102
+#define AC200_I2S_FMT0			0x2104
+#define AC200_I2S_FMT1			0x2108
+#define AC200_I2S_MIX_SRC		0x2114
+#define AC200_I2S_MIX_GAIN		0x2116
+#define AC200_I2S_DACDAT_DVC		0x2118
+#define AC200_I2S_ADCDAT_DVC		0x211A
+#define AC200_AC_DAC_DPC		0x2200
+#define AC200_AC_DAC_MIX_SRC		0x2202
+#define AC200_AC_DAC_MIX_GAIN		0x2204
+#define AC200_DACA_OMIXER_CTRL		0x2220
+#define AC200_OMIXER_SR			0x2222
+#define AC200_LINEOUT_CTRL		0x2224
+#define AC200_AC_ADC_DPC		0x2300
+#define AC200_MBIAS_CTRL		0x2310
+#define AC200_ADC_MIC_CTRL		0x2320
+#define AC200_ADCMIXER_SR		0x2322
+#define AC200_ANALOG_TUNING0		0x232A
+#define AC200_ANALOG_TUNING1		0x232C
+#define AC200_AC_AGC_SEL		0x2480
+#define AC200_ADC_DAPLCTRL		0x2500
+#define AC200_ADC_DAPRCTRL		0x2502
+#define AC200_ADC_DAPLSTA		0x2504
+#define AC200_ADC_DAPRSTA		0x2506
+#define AC200_ADC_DAPLTL		0x2508
+#define AC200_ADC_DAPRTL		0x250A
+#define AC200_ADC_DAPLHAC		0x250C
+#define AC200_ADC_DAPLLAC		0x250E
+#define AC200_ADC_DAPRHAC		0x2510
+#define AC200_ADC_DAPRLAC		0x2512
+#define AC200_ADC_DAPLDT		0x2514
+#define AC200_ADC_DAPLAT		0x2516
+#define AC200_ADC_DAPRDT		0x2518
+#define AC200_ADC_DAPRAT		0x251A
+#define AC200_ADC_DAPNTH		0x251C
+#define AC200_ADC_DAPLHNAC		0x251E
+#define AC200_ADC_DAPLLNAC		0x2520
+#define AC200_ADC_DAPRHNAC		0x2522
+#define AC200_ADC_DAPRLNAC		0x2524
+#define AC200_AC_DAPHHPFC		0x2526
+#define AC200_AC_DAPLHPFC		0x2528
+#define AC200_AC_DAPOPT			0x252A
+#define AC200_AC_DAC_DAPCTRL		0x3000
+#define AC200_AC_DRC_HHPFC		0x3002
+#define AC200_AC_DRC_LHPFC		0x3004
+#define AC200_AC_DRC_CTRL		0x3006
+#define AC200_AC_DRC_LPFHAT		0x3008
+#define AC200_AC_DRC_LPFLAT		0x300A
+#define AC200_AC_DRC_RPFHAT		0x300C
+#define AC200_AC_DRC_RPFLAT		0x300E
+#define AC200_AC_DRC_LPFHRT		0x3010
+#define AC200_AC_DRC_LPFLRT		0x3012
+#define AC200_AC_DRC_RPFHRT		0x3014
+#define AC200_AC_DRC_RPFLRT		0x3016
+#define AC200_AC_DRC_LRMSHAT		0x3018
+#define AC200_AC_DRC_LRMSLAT		0x301A
+#define AC200_AC_DRC_RRMSHAT		0x301C
+#define AC200_AC_DRC_RRMSLAT		0x301E
+#define AC200_AC_DRC_HCT		0x3020
+#define AC200_AC_DRC_LCT		0x3022
+#define AC200_AC_DRC_HKC		0x3024
+#define AC200_AC_DRC_LKC		0x3026
+#define AC200_AC_DRC_HOPC		0x3028
+#define AC200_AC_DRC_LOPC		0x302A
+#define AC200_AC_DRC_HLT		0x302C
+#define AC200_AC_DRC_LLT		0x302E
+#define AC200_AC_DRC_HKI		0x3030
+#define AC200_AC_DRC_LKI		0x3032
+#define AC200_AC_DRC_HOPL		0x3034
+#define AC200_AC_DRC_LOPL		0x3036
+#define AC200_AC_DRC_HET		0x3038
+#define AC200_AC_DRC_LET		0x303A
+#define AC200_AC_DRC_HKE		0x303C
+#define AC200_AC_DRC_LKE		0x303E
+#define AC200_AC_DRC_HOPE		0x3040
+#define AC200_AC_DRC_LOPE		0x3042
+#define AC200_AC_DRC_HKN		0x3044
+#define AC200_AC_DRC_LKN		0x3046
+#define AC200_AC_DRC_SFHAT		0x3048
+#define AC200_AC_DRC_SFLAT		0x304A
+#define AC200_AC_DRC_SFHRT		0x304C
+#define AC200_AC_DRC_SFLRT		0x304E
+#define AC200_AC_DRC_MXGHS		0x3050
+#define AC200_AC_DRC_MXGLS		0x3052
+#define AC200_AC_DRC_MNGHS		0x3054
+#define AC200_AC_DRC_MNGLS		0x3056
+#define AC200_AC_DRC_EPSHC		0x3058
+#define AC200_AC_DRC_EPSLC		0x305A
+#define AC200_AC_DRC_HPFHGAIN		0x305E
+#define AC200_AC_DRC_HPFLGAIN		0x3060
+#define AC200_AC_DRC_BISTCR		0x3100
+#define AC200_AC_DRC_BISTST		0x3102
+
+/* TVE registers */
+#define AC200_TVE_CTL0			0x4000
+#define AC200_TVE_CTL1			0x4002
+#define AC200_TVE_MOD0			0x4004
+#define AC200_TVE_MOD1			0x4006
+#define AC200_TVE_DAC_CFG0		0x4008
+#define AC200_TVE_DAC_CFG1		0x400A
+#define AC200_TVE_YC_DELAY		0x400C
+#define AC200_TVE_YC_FILTER		0x400E
+#define AC200_TVE_BURST_FRQ0		0x4010
+#define AC200_TVE_BURST_FRQ1		0x4012
+#define AC200_TVE_FRONT_PORCH		0x4014
+#define AC200_TVE_BACK_PORCH		0x4016
+#define AC200_TVE_TOTAL_LINE		0x401C
+#define AC200_TVE_FIRST_ACTIVE		0x401E
+#define AC200_TVE_BLACK_LEVEL		0x4020
+#define AC200_TVE_BLANK_LEVEL		0x4022
+#define AC200_TVE_PLUG_EN		0x4030
+#define AC200_TVE_PLUG_IRQ_EN		0x4032
+#define AC200_TVE_PLUG_IRQ_STA		0x4034
+#define AC200_TVE_PLUG_STA		0x4038
+#define AC200_TVE_PLUG_DEBOUNCE		0x4040
+#define AC200_TVE_DAC_TEST		0x4042
+#define AC200_TVE_PLUG_PULSE_LEVEL	0x40F4
+#define AC200_TVE_PLUG_PULSE_START	0x40F8
+#define AC200_TVE_PLUG_PULSE_PERIOD	0x40FA
+#define AC200_TVE_IF_CTL		0x5000
+#define AC200_TVE_IF_TIM0		0x5008
+#define AC200_TVE_IF_TIM1		0x500A
+#define AC200_TVE_IF_TIM2		0x500C
+#define AC200_TVE_IF_TIM3		0x500E
+#define AC200_TVE_IF_SYNC0		0x5010
+#define AC200_TVE_IF_SYNC1		0x5012
+#define AC200_TVE_IF_SYNC2		0x5014
+#define AC200_TVE_IF_TIM4		0x5016
+#define AC200_TVE_IF_STATUS		0x5018
+
+/* EPHY registers */
+#define AC200_EPHY_CTL			0x6000
+#define AC200_EPHY_BIST			0x6002
+
+/* eFuse registers (0x8000 - 0x9FFF, layout unknown) */
+
+/* RTC registers */
+#define AC200_LOSC_CTRL0		0xA000
+#define AC200_LOSC_CTRL1		0xA002
+#define AC200_LOSC_AUTO_SWT_STA		0xA004
+#define AC200_INTOSC_CLK_PRESCAL	0xA008
+#define AC200_RTC_YY_MM_DD0		0xA010
+#define AC200_RTC_YY_MM_DD1		0xA012
+#define AC200_RTC_HH_MM_SS0		0xA014
+#define AC200_RTC_HH_MM_SS1		0xA016
+#define AC200_ALARM0_CUR_VLU0		0xA024
+#define AC200_ALARM0_CUR_VLU1		0xA026
+#define AC200_ALARM0_ENABLE		0xA028
+#define AC200_ALARM0_IRQ_EN		0xA02C
+#define AC200_ALARM0_IRQ_STA		0xA030
+#define AC200_ALARM1_WK_HH_MM_SS0	0xA040
+#define AC200_ALARM1_WK_HH_MM_SS1	0xA042
+#define AC200_ALARM1_ENABLE		0xA044
+#define AC200_ALARM1_IRQ_EN		0xA048
+#define AC200_ALARM1_IRQ_STA		0xA04C
+#define AC200_ALARM_CONFIG		0xA050
+#define AC200_LOSC_OUT_GATING		0xA060
+#define AC200_GP_DATA(x)		(0xA100 + (x) * 2)
+#define AC200_RTC_DEB			0xA170
+#define AC200_GPL_HOLD_OUTPUT		0xA180
+#define AC200_VDD_RTC			0xA190
+#define AC200_IC_CHARA0			0xA1F0
+#define AC200_IC_CHARA1			0xA1F2
+
+struct ac200_dev {
+	struct clk			*clk;
+	struct regmap			*regmap;
+	struct regmap_irq_chip_data	*regmap_irqc;
+};
+
+extern int ac200_enable(void);
+extern uint16_t sun50i_ephy_calibrate_value(void);
+
+#endif /* __LINUX_MFD_AC200_H */
diff --git a/include/linux/of_gpio.h b/include/linux/of_gpio.h
index d0f66a5e1b2a..71349cac1a67 100644
--- a/include/linux/of_gpio.h
+++ b/include/linux/of_gpio.h
@@ -15,6 +15,21 @@
 #include <linux/gpio.h>		/* FIXME: Shouldn't be here */
 #include <linux/of.h>
 
+/*
+ * This is Linux-specific flags. By default controllers' and Linux' mapping
+ * match, but GPIO controllers are free to translate their own flags to
+ * Linux-specific in their .xlate callback. Though, 1:1 mapping is recommended.
+ */
+enum of_gpio_flags {
+	OF_GPIO_ACTIVE_LOW = 0x1,
+	OF_GPIO_SINGLE_ENDED = 0x2,
+	OF_GPIO_OPEN_DRAIN = 0x4,
+	OF_GPIO_TRANSITORY = 0x8,
+	OF_GPIO_PULL_UP = 0x10,
+	OF_GPIO_PULL_DOWN = 0x20,
+	OF_GPIO_PULL_DISABLE = 0x40,
+};
+
 struct device_node;
 
 #ifdef CONFIG_OF_GPIO
@@ -22,6 +37,9 @@ struct device_node;
 extern int of_get_named_gpio(const struct device_node *np,
 			     const char *list_name, int index);
 
+extern int of_get_named_gpio_flags(const struct device_node *np,
+		const char *list_name, int index, enum of_gpio_flags *flags);
+
 #else /* CONFIG_OF_GPIO */
 
 #include <linux/errno.h>
-- 
2.35.3

