From dbb0d3a019f57d858320fc7e2b62b6c49ba159bf Mon Sep 17 00:00:00 2001
From: LABBE Corentin <clabbe.montjoie@gmail.com>
Date: Thu, 14 Jan 2016 11:40:23 +0100
Subject: [PATCH 101/101] ethernet: add sun8i-emac driver

This patch add support for sun8i-emac ethernet MAC hardware.
It could be found in Allwinner H3/A83T/A64 SoCs.

Signed-off-by: LABBE Corentin <clabbe.montjoie@gmail.com>
---
 drivers/net/ethernet/allwinner/Kconfig      |   13 +
 drivers/net/ethernet/allwinner/Makefile     |    1 +
 drivers/net/ethernet/allwinner/sun8i-emac.c | 1501 +++++++++++++++++++++++++++
 3 files changed, 1515 insertions(+)
 create mode 100644 drivers/net/ethernet/allwinner/sun8i-emac.c

diff --git a/drivers/net/ethernet/allwinner/Kconfig b/drivers/net/ethernet/allwinner/Kconfig
index 47da7e7..31ed97e 100644
--- a/drivers/net/ethernet/allwinner/Kconfig
+++ b/drivers/net/ethernet/allwinner/Kconfig
@@ -33,4 +33,17 @@ config SUN4I_EMAC
           To compile this driver as a module, choose M here.  The module
           will be called sun4i-emac.
 
+config SUN8I_EMAC
+        tristate "Allwinner H3 EMAC support"
+	depends on ARCH_SUNXI
+	depends on OF
+	select CRC32
+	select MII
+	select PHYLIB
+        ---help---
+          Support for Allwinner H3 EMAC ethernet driver.
+
+          To compile this driver as a module, choose M here.  The module
+          will be called sun8i-emac.
+
 endif # NET_VENDOR_ALLWINNER
diff --git a/drivers/net/ethernet/allwinner/Makefile b/drivers/net/ethernet/allwinner/Makefile
index 03129f7..8bd1693c 100644
--- a/drivers/net/ethernet/allwinner/Makefile
+++ b/drivers/net/ethernet/allwinner/Makefile
@@ -3,3 +3,4 @@
 #
 
 obj-$(CONFIG_SUN4I_EMAC) += sun4i-emac.o
+obj-$(CONFIG_SUN8I_EMAC) += sun8i-emac.o
diff --git a/drivers/net/ethernet/allwinner/sun8i-emac.c b/drivers/net/ethernet/allwinner/sun8i-emac.c
new file mode 100644
index 0000000..dbda381
--- /dev/null
+++ b/drivers/net/ethernet/allwinner/sun8i-emac.c
@@ -0,0 +1,1501 @@
+/*
+ * sun8i-h3-emac driver
+ *
+ * Copyright (C) 2015-2016 Corentin LABBE <clabbe.montjoie@gmail.com>
+ *
+ * This is the driver for Allwinner Ethernet MAC found in H3/A83T/A64 SoC
+ *
+ * This is a mono block driver that need to be splited:
+ * - A classic ethernet MAC driver
+ * - A PHY driver
+ * - A clk driver
+ */
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/phy.h>
+#include <linux/mii.h>
+#include <linux/gpio.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 <linux/err.h>
+#include <linux/scatterlist.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/reset.h>
+#include <linux/of_net.h>
+#include <linux/of_mdio.h>
+#include <linux/iopoll.h>
+/* for A83T */
+#include <linux/regulator/consumer.h>
+
+#define SUN8I_EMAC_BASIC_CTL0 0x00
+#define SUN8I_EMAC_BASIC_CTL1 0x04
+
+#define SUN8I_EMAC_MDIO_CMD 0x48
+#define SUN8I_EMAC_MDIO_DATA 0x4C
+
+#define SUN8I_EMAC_RX_CTL0 0x24
+#define SUN8I_EMAC_RX_CTL1 0x28
+
+#define SUN8I_EMAC_TX_CTL0 0x10
+#define SUN8I_EMAC_TX_CTL1 0x14
+
+#define SUN8I_EMAC_TX_FLOW_CTL 0x1C
+
+#define SUN8I_EMAC_RX_FRM_FLT 0x38
+
+#define SUN8I_EMAC_INT_STA 0x08
+#define SUN8I_EMAC_INT_EN 0x0C
+#define SUN8I_EMAC_RGMII_STA 0xD0
+
+#define SUN8I_EMAC_TX_DMA_STA 0xB0
+#define SUN8I_EMAC_TX_CUR_DDESC 0xB4
+#define SUN8I_EMAC_RX_DMA_STA 0xC0
+
+#define MDIO_CMD_MII_BUSY	BIT(0)
+#define MDIO_CMD_MII_WRITE	BIT(1)
+#define MDIO_CMD_MII_PHY_REG_ADDR_MASK	GENMASK(8, 4)
+#define MDIO_CMD_MII_PHY_REG_ADDR_SHIFT	4
+#define MDIO_CMD_MII_PHY_ADDR_MASK	GENMASK(16, 12)
+#define MDIO_CMD_MII_PHY_ADDR_SHIFT	12
+
+#define SUN8I_EMAC_MACADDR_HI	0x50
+#define SUN8I_EMAC_MACADDR_LO	0x54
+
+#define SUN8I_EMAC_RX_DESC_LIST 0x34
+#define SUN8I_EMAC_TX_DESC_LIST 0x20
+
+#define SUN8I_COULD_BE_USED_BY_DMA BIT(31)
+
+struct dma_desc {
+	u32 status;
+	u32 st;
+	u32 buf_addr;
+	u32 next;
+} __packed __aligned(4);
+
+static int flow_ctrl;
+static int pause = 0x400;
+
+static int nbdesc = 16;
+struct sun8i_emac_priv {
+	void __iomem *base;
+	int irq;
+	struct device *dev;
+	struct net_device *ndev;
+	struct mii_bus *mdio;
+	spinlock_t lock;/* for adjust_link */
+	spinlock_t tx_lock;/* control the access of transmit descriptors */
+	int duplex;
+	int speed;
+	int link;
+	int phy_interface;
+	struct device_node *phy_node;
+	struct clk *ahb_clk;
+	struct clk *tx_clk;
+	u32 mdc;
+
+	struct reset_control *rst_phy;
+	struct reset_control *rst;
+
+	struct dma_desc *dd_rx ____cacheline_aligned;
+	dma_addr_t dd_rx_phy ____cacheline_aligned;
+	struct dma_desc *dd_tx;
+	dma_addr_t dd_tx_phy;
+	struct sk_buff **rx_sk;
+	struct sk_buff **tx_sk;
+
+	int tx_slot;
+	int tx_dma_start;
+};
+
+void rb_inc(int *p)
+{
+	(*p)++;
+	if (*p >= nbdesc)
+		*p = 0;
+}
+
+static int sun8i_ephy_hack(struct net_device *ndev);
+
+/* allocate a sk in a dma descriptor
+ *
+ * @i index of slot to fill
+*/
+static int sun8i_emac_rx_sk(struct net_device *ndev, int i)
+{
+	struct sun8i_emac_priv *priv = netdev_priv(ndev);
+	struct dma_desc *ddesc;
+	struct sk_buff *sk;
+
+	ddesc = priv->dd_rx + i;
+
+	ddesc->st = 0;
+
+	/* TODO need to recheck this value */
+	sk = netdev_alloc_skb_ip_align(ndev, 2000);
+	if (!sk)
+		return -ENOMEM;
+
+	if (priv->rx_sk[i]) {
+		dev_warn(priv->dev, "WARN: Leaking a skbuff\n");
+		/* TODO should not happen */
+	}
+
+	priv->rx_sk[i] = sk;
+
+	ddesc->buf_addr = dma_map_single(priv->dev, sk->data,
+					 ndev->mtu, DMA_FROM_DEVICE);
+	if (dma_mapping_error(priv->dev, ddesc->buf_addr)) {
+		dev_err(priv->dev, "ERROR: Cannot dma_map RX\n");
+		dev_kfree_skb(sk);
+		return -EINVAL;
+	}
+	ddesc->st |= 2000;
+	ddesc->status = BIT(31);
+
+	return 0;
+}
+
+/* Set MAC address for slot index */
+static void sun8i_emac_set_macaddr(struct sun8i_emac_priv *priv,
+				   unsigned char *addr, int index)
+{
+	u32 v;
+
+	if (!is_valid_ether_addr(addr)) {
+		random_ether_addr(priv->ndev->dev_addr);
+		addr = priv->ndev->dev_addr;
+	}
+	dev_info(priv->dev, "device MAC address %02x:%02x:%02x:%02x:%02x:%02x\n",
+		 addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]),
+
+	v = (addr[5] << 8) | addr[4];
+	writel(v, priv->base + SUN8I_EMAC_MACADDR_HI + index * 8);
+	v = (addr[3] << 24) | (addr[2] << 16) | (addr[1] << 8) | addr[0];
+	writel(v, priv->base + SUN8I_EMAC_MACADDR_HI + index * 8);
+}
+
+void sun8i_emac_set_link_mode(struct sun8i_emac_priv *priv)
+{
+	u32 v;
+
+	v = readl(priv->base + SUN8I_EMAC_BASIC_CTL0);
+
+	if (priv->duplex)
+		v |= BIT(0);
+	else
+		v &= ~BIT(0);
+
+	v &= ~0x0C;
+	switch (priv->speed) {
+	case 1000:
+		break;
+	case 100:
+		v |= BIT(2);
+		v |= BIT(3);
+		break;
+	case 10:
+		v |= BIT(3);
+		break;
+	}
+
+	writel(v, priv->base + SUN8I_EMAC_BASIC_CTL0);
+}
+
+static void sun8i_emac_flow_ctrl(struct sun8i_emac_priv *priv, int duplex,
+				 int fc, int pause)
+{
+	u32 flow = 0;
+
+	dev_dbg(priv->dev, "%s %d %d %d\n", __func__, duplex, fc, pause);
+
+	if (fc & BIT(0)) {
+		flow = readl(priv->base + SUN8I_EMAC_RX_CTL0);
+		flow |= 0x10000;
+		/*flow |= BIT(16);*/
+		writel(flow, priv->base + SUN8I_EMAC_RX_CTL0);
+	}
+
+	if (fc & BIT(1)) {
+		flow = readl(priv->base + SUN8I_EMAC_TX_FLOW_CTL);
+		flow |= BIT(0);
+		writel(flow, priv->base + SUN8I_EMAC_TX_FLOW_CTL);
+	}
+
+	if (duplex) {
+		flow = readl(priv->base + SUN8I_EMAC_TX_FLOW_CTL);
+		flow |= (pause << 4);
+		/* pause & BIT(4)*/
+		writel(flow, priv->base + SUN8I_EMAC_TX_FLOW_CTL);
+	}
+}
+
+/* Grab a frame into a skb from descriptor number i */
+static int sun8i_emac_rx_from_ddesc(struct net_device *ndev, int i)
+{
+	struct sk_buff *skb;
+	struct sun8i_emac_priv *priv = netdev_priv(ndev);
+	struct dma_desc *ddesc;
+	int frame_len;
+
+	ddesc = priv->dd_rx + i;
+
+	/* possible erros */
+	if ((ddesc->status & BIT(0)) > 0) {
+		/* the checksum or length of received frame's payload is wrong*/
+		/* TODO its the case for ARP */
+	}
+	if ((ddesc->status & BIT(7)) > 0) {
+		/*dev_warn(priv->dev, "RX_HEADER_ERR\n");*/
+	}
+
+	if ((ddesc->status & BIT(9)) == 0) {
+		/* begin of a frame */
+		dev_warn(priv->dev, "This should not happen\n");
+	}
+	frame_len = (ddesc->status >> 16) & 0x3FFF;
+	skb = priv->rx_sk[i];
+
+	dev_dbg(priv->dev, "%s from %02d %pad len=%d status=%x st=%x\n",
+		__func__, i, &ddesc, frame_len, ddesc->status, ddesc->st);
+
+	skb_put(skb, frame_len);
+
+	dma_unmap_single(priv->dev, ddesc->buf_addr, ndev->mtu,
+			 DMA_FROM_DEVICE);
+	skb->protocol = eth_type_trans(skb, priv->ndev);
+	skb->ip_summed = CHECKSUM_UNNECESSARY;
+	skb->dev = priv->ndev;
+
+	priv->ndev->stats.rx_packets++;
+	priv->ndev->stats.rx_bytes += frame_len;
+	priv->rx_sk[i] = NULL;
+
+	/* this frame is not the last */
+	/*
+	if ((ddesc->status & BIT(8)) == 0) {
+		dev_warn(priv->dev, "Multi frame not implemented currlen=%d\n",
+			 frame_len);
+		rb_inc(&i);
+		ddesc = priv->dd_rx + i;
+		frame_len = (ddesc->status >> 16) & 0x3FFF;
+		dev_info(priv->dev, "Multi frame currlen=%d\n", frame_len);
+	}
+	*/
+
+	sun8i_emac_rx_sk(ndev, i);
+
+	netif_rx(skb);
+
+	return 0;
+}
+
+/* rate over dma desc for finding frame to receive */
+static int sun8i_emac_receive_all(struct net_device *ndev)
+{
+	struct sun8i_emac_priv *priv = netdev_priv(ndev);
+	struct dma_desc *ddesc;
+	int i;
+
+	for (i = 0; i < nbdesc; i++) {
+		ddesc = priv->dd_rx + i;
+		if (!(ddesc->status & BIT(31)))
+			sun8i_emac_rx_from_ddesc(ndev, i);
+	}
+	return 0;
+}
+
+/* iterate over dma desc for finding completed xmit.
+ * Called from interrupt context, so no need to spinlock tx
+ */
+static int sun8i_emac_complete_xmit(struct net_device *ndev)
+{
+	struct sun8i_emac_priv *priv = netdev_priv(ndev);
+	struct dma_desc *ddesc;
+	int i, frame_len;
+
+	dev_dbg(priv->dev, "%s\n", __func__);
+
+	for (i = 0; i < nbdesc; i++) {
+		ddesc = priv->dd_tx + i;
+		if (ddesc->status & BIT(31))
+			continue;
+		if (ddesc->status != 0 || ddesc->st) {
+			frame_len = ddesc->st & 0x3FFF;
+			dev_dbg(priv->dev, "%s found slot to clean %d at %pad %x %x (len=%d)\n",
+				__func__, i, &ddesc, ddesc->status, ddesc->st,
+				frame_len);
+			if (priv->tx_sk[i]) {
+				dev_kfree_skb_irq(priv->tx_sk[i]);
+				priv->tx_sk[i] = NULL;
+				priv->ndev->stats.tx_packets++;
+			dma_unmap_single(priv->dev, ddesc->buf_addr,
+					 frame_len, DMA_TO_DEVICE);
+			} else {
+				dma_unmap_page(priv->dev, ddesc->buf_addr,
+					       frame_len, DMA_TO_DEVICE);
+			}
+			ddesc->status = 0;
+			ddesc->st = 0;
+			priv->ndev->stats.tx_bytes += frame_len;
+		}
+	}
+	return 0;
+}
+
+static int sun8i_mdio_read(struct mii_bus *bus, int phy_addr, int phy_reg)
+{
+	struct net_device *ndev = bus->priv;
+	struct sun8i_emac_priv *priv = netdev_priv(ndev);
+	int err;
+	u32 reg;
+
+	err = readl_poll_timeout(priv->base + SUN8I_EMAC_MDIO_CMD, reg,
+				 !(reg & MDIO_CMD_MII_BUSY), 100, 10000);
+	if (err) {
+		dev_err(priv->dev, "%s timeout %x\n", __func__, reg);
+		return err;
+	}
+
+	reg &= ~MDIO_CMD_MII_WRITE;
+	reg &= ~MDIO_CMD_MII_PHY_REG_ADDR_MASK;
+	reg |= (phy_reg << MDIO_CMD_MII_PHY_REG_ADDR_SHIFT) &
+		MDIO_CMD_MII_PHY_REG_ADDR_MASK;
+
+	reg &= ~MDIO_CMD_MII_PHY_ADDR_MASK;
+
+	reg |= (phy_addr << MDIO_CMD_MII_PHY_ADDR_SHIFT) &
+		MDIO_CMD_MII_PHY_ADDR_MASK;
+
+	reg |= MDIO_CMD_MII_BUSY;
+
+	writel(reg, priv->base + SUN8I_EMAC_MDIO_CMD);
+
+	err = readl_poll_timeout(priv->base + SUN8I_EMAC_MDIO_CMD, reg,
+				 !(reg & MDIO_CMD_MII_BUSY), 100, 10000);
+
+	if (err) {
+		dev_err(priv->dev, "%s timeout %x\n", __func__, reg);
+		return err;
+	}
+
+	return readl(priv->base + SUN8I_EMAC_MDIO_DATA);
+}
+
+static int sun8i_mdio_write(struct mii_bus *bus, int phy_addr, int phy_reg,
+			    u16 data)
+{
+	struct net_device *ndev = bus->priv;
+	struct sun8i_emac_priv *priv = netdev_priv(ndev);
+	u32 reg;
+	int err;
+
+	err = readl_poll_timeout(priv->base + SUN8I_EMAC_MDIO_CMD, reg,
+				 !(reg & MDIO_CMD_MII_BUSY), 100, 10000);
+	if (err) {
+		dev_err(priv->dev, "%s timeout %x\n", __func__, reg);
+		return err;
+	}
+
+	reg &= ~MDIO_CMD_MII_PHY_REG_ADDR_MASK;
+	reg |= (phy_reg << MDIO_CMD_MII_PHY_REG_ADDR_SHIFT) &
+		MDIO_CMD_MII_PHY_REG_ADDR_MASK;
+
+	reg &= ~MDIO_CMD_MII_PHY_ADDR_MASK;
+	reg |= (phy_addr << MDIO_CMD_MII_PHY_ADDR_SHIFT) &
+		MDIO_CMD_MII_PHY_ADDR_MASK;
+
+	reg |= MDIO_CMD_MII_WRITE;
+	reg |= MDIO_CMD_MII_BUSY;
+
+	writel(reg, priv->base + SUN8I_EMAC_MDIO_CMD);
+	writel(data, priv->base + SUN8I_EMAC_MDIO_DATA);
+	dev_dbg(priv->dev, "%s %d %d %x %x\n", __func__, phy_addr, phy_reg,
+		reg, data);
+
+	err = readl_poll_timeout(priv->base + SUN8I_EMAC_MDIO_CMD, reg,
+				 !(reg & MDIO_CMD_MII_BUSY), 100, 10000);
+	if (err) {
+		dev_err(priv->dev, "%s timeout %x\n", __func__, reg);
+		return err;
+	}
+
+	return 0;
+}
+
+static int sun8i_emac_mdio_register(struct net_device *ndev)
+{
+	struct sun8i_emac_priv *priv = netdev_priv(ndev);
+	struct mii_bus *bus;
+	int ret;
+
+	dev_info(priv->dev, "%s\n", __func__);
+
+	bus = devm_mdiobus_alloc(priv->dev);
+	if (!bus) {
+		netdev_err(ndev, "Failed to alloc new mdio bus\n");
+		return -ENOMEM;
+	}
+
+	bus->name = dev_name(priv->dev);
+	bus->read = &sun8i_mdio_read;
+	bus->write = &sun8i_mdio_write;
+	snprintf(bus->id, MII_BUS_ID_SIZE, "%s-%x", bus->name, 0);
+
+	bus->parent = priv->dev;
+	bus->priv = ndev;
+
+	ret = of_mdiobus_register(bus, priv->dev->of_node);
+	if (ret) {
+		netdev_err(ndev, "Could not register as MDIO bus: %d\n", ret);
+		return ret;
+	}
+
+	priv->mdio = bus;
+
+	return 0;
+}
+
+/* END of need to moved in phy/ */
+
+static void sun8i_emac_adjust_link(struct net_device *ndev)
+{
+	struct sun8i_emac_priv *priv = netdev_priv(ndev);
+	struct phy_device *phydev = ndev->phydev;
+	unsigned long flags;
+	int new_state = 0;
+
+	dev_dbg(priv->dev, "%s link=%x duplex=%x speed=%x\n", __func__,
+		phydev->link, phydev->duplex, phydev->speed);
+	if (!phydev)
+		return;
+
+	spin_lock_irqsave(&priv->lock, flags);
+
+	if (phydev->link) {
+		if (phydev->duplex != priv->duplex) {
+			new_state = 1;
+			priv->duplex = phydev->duplex;
+		}
+		if (phydev->pause)
+			sun8i_emac_flow_ctrl(priv, 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;
+		}
+
+		dev_info(priv->dev, "%s new=%d link=%d pause=%d\n",
+			 __func__, new_state, priv->link, phydev->pause);
+		if (new_state)
+			sun8i_emac_set_link_mode(priv);
+	} 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 sun8i_emac_init(struct net_device *ndev)
+{
+	struct sun8i_emac_priv *priv = netdev_priv(ndev);
+	struct device_node *node = priv->dev->of_node;
+	unsigned long rate;
+	u32 reg;
+	int ret;
+
+	dev_info(priv->dev, "%s\n", __func__);
+
+	priv->phy_interface = of_get_phy_mode(node);
+	if (priv->phy_interface < 0) {
+		netdev_err(ndev, "PHY interface mode node specified\n");
+		return priv->phy_interface;
+	}
+
+	priv->phy_node = of_parse_phandle(node, "phy", 0);
+	if (!priv->phy_node) {
+		netdev_err(ndev, "no associated PHY\n");
+		return -ENODEV;
+	}
+
+	ret = clk_prepare_enable(priv->ahb_clk);
+	if (ret) {
+		netdev_err(ndev, "Could not enable ahb clock");
+		return ret;
+	}
+
+	if (priv->rst) {
+		ret = reset_control_deassert(priv->rst);
+		if (ret) {
+			netdev_err(ndev, "Could not deassert reset\n");
+			goto err_disable_ahb_clk;
+		}
+	}
+
+	rate = clk_get_rate(priv->ahb_clk);
+	if (rate > 160000000)
+		reg = 0x3 << 20; /* AHB / 128 */
+	else if (rate > 80000000)
+		reg = 0x2 << 20; /* AHB / 64 */
+	else if (rate > 40000000)
+		reg = 0x1 << 20; /* AHB / 32 */
+	else
+		reg = 0x0 << 20; /* AHB / 16 */
+	dev_info(priv->dev, "MDC auto : %x\n", reg);
+	writel(reg, priv->base + SUN8I_EMAC_MDIO_CMD);
+
+	sun8i_ephy_hack(ndev);
+
+	ret = sun8i_emac_mdio_register(ndev);
+	if (ret)
+		goto err_assert_reset;
+
+	return 0;
+err_assert_reset:
+	if (priv->rst)
+		reset_control_assert(priv->rst);
+err_disable_ahb_clk:
+	clk_disable_unprepare(priv->ahb_clk);
+	return ret;
+}
+
+static void sun8i_emac_uninit(struct net_device *ndev)
+{
+	struct sun8i_emac_priv *priv = netdev_priv(ndev);
+
+	mdiobus_unregister(priv->mdio);
+	if (priv->rst)
+		reset_control_assert(priv->rst);
+
+	clk_disable_unprepare(priv->ahb_clk);
+}
+
+/* this function do lots of things that will be splited away (clk/phy) */
+static int sun8i_ephy_hack(struct net_device *ndev)
+{
+	struct sun8i_emac_priv *priv = netdev_priv(ndev);
+	int err;
+	void __iomem *sc;
+	u32 v;
+	int do_ephy_clk = 1;
+
+	dev_info(priv->dev, "%s\n", __func__);
+
+	/* find type of PHY */
+	priv->phy_interface = of_get_phy_mode(priv->dev->of_node);
+	dev_info(priv->dev, "%s phy_interface=%x\n", __func__,
+		 priv->phy_interface);
+
+	/* fallback to integrate MII */
+	switch (priv->phy_interface) {
+	case PHY_INTERFACE_MODE_MII:
+		dev_info(priv->dev, "%s interface PHY_INTERFACE_MODE_MII\n",
+			 __func__);
+		break;
+	case PHY_INTERFACE_MODE_RGMII:
+		dev_info(priv->dev, "%s interface PHY_INTERFACE_MODE_RGMII\n",
+			 __func__);
+		break;
+	case PHY_INTERFACE_MODE_RMII:
+		dev_info(priv->dev, "%s interface PHY_INTERFACE_MODE_RMII\n",
+			 __func__);
+		break;
+	case PHY_INTERFACE_MODE_GMII:
+		dev_info(priv->dev, "%s interface PHY_INTERFACE_MODE_GMII\n",
+			 __func__);
+		break;
+	default:
+		dev_info(priv->dev, "Fallback to MII\n");
+		priv->phy_interface = PHY_INTERFACE_MODE_MII;
+	}
+
+	/* systemcontrol */
+	/* TODO put that in phy clock */
+	sc = ioremap(0x01C00030, 0x20);
+	if (sc) {
+		v = readl(sc);
+		dev_info(priv->dev, "SystemControl %x\n", v);
+		/* crappy switch to be moved */
+		switch (v) {
+		case 0: /* A83T */
+			do_ephy_clk = 0;
+			switch (priv->phy_interface) {
+			case PHY_INTERFACE_MODE_MII:
+				v &= ~BIT(2);
+				break;
+			case PHY_INTERFACE_MODE_RGMII:
+				v |= BIT(1);
+				v |= BIT(2);
+				v |= BIT(15);
+				break;
+			case PHY_INTERFACE_MODE_GMII:
+				v &= ~BIT(2);
+				break;
+			default:
+				dev_err(priv->dev, "Unknown PHY type %d\n",
+					priv->phy_interface);
+			}
+			break;
+		case 0x58000: /* H3 */
+			switch (priv->phy_interface) {
+			case PHY_INTERFACE_MODE_MII:
+				/* PHY_SELECT: Internal PHY */
+				v |= BIT(15);
+				/* SHUTDOWN: Power up */
+				v &= ~BIT(16);
+				/* 24 Mhz */
+				/*v &= ~BIT(18);*/
+				/* LED POL */
+				v |= BIT(17);
+				break;
+			case PHY_INTERFACE_MODE_RGMII:
+				v |= BIT(1);
+				v |= BIT(2);
+				/* External PHY */
+				v &= ~BIT(15);
+				/* SHUTDOWN: Shutdown */
+				v |= BIT(16);
+				break;
+				/* TODO RMII */
+			default:
+				dev_err(priv->dev, "Unknown PHY type %d\n",
+					priv->phy_interface);
+			}
+			break;
+		default:
+			dev_err(priv->dev, "Unknown platform %x\n", v);
+		}
+		dev_info(priv->dev, "SystemControl %x\n", v);
+		writel(v, sc);
+		iounmap(sc);
+	}
+	/* end phy clock */
+
+	/* PWM */
+	sc = ioremap(0x01C21400, 0x20);
+	if (sc) {
+		v = readl(sc);
+		dev_info(priv->dev, "PWM %x\n", v);
+		v = readl(sc + 0x04);
+		dev_info(priv->dev, "PWM %x\n", v);
+		iounmap(sc);
+	}
+
+	if (do_ephy_clk == 1) {
+		priv->tx_clk = devm_clk_get(priv->dev, "bus_ephy");
+		if (IS_ERR(priv->tx_clk)) {
+			err = PTR_ERR(priv->tx_clk);
+			dev_err(priv->dev, "Cannot get MII clock err=%d\n",
+				err);
+			return err;
+		}
+		err = clk_prepare_enable(priv->tx_clk);
+		if (err != 0) {
+			dev_err(priv->dev, "Cannot prepare_enable PHY\n");
+			return err;
+		}
+		dev_info(priv->dev, "PHY clk is enabled\n");
+
+		priv->rst_phy = devm_reset_control_get(priv->dev, "ephy");
+		if (IS_ERR(priv->rst_phy)) {
+			err = PTR_ERR(priv->rst_phy);
+			dev_info(priv->dev, "no PHY reset control found %d\n",
+				 err);
+			priv->rst_phy = NULL;
+		}
+		if (priv->rst_phy) {
+			err = reset_control_deassert(priv->rst_phy);
+			if (err)
+				dev_err(priv->dev, "Cannot deassert PHY\n");
+			else
+				dev_info(priv->dev, "PHY is de-asserted\n");
+		}
+	}
+	return 0;
+}
+
+static int sun8i_emac_mdio_probe(struct net_device *ndev)
+{
+	struct sun8i_emac_priv *priv = netdev_priv(ndev);
+	struct phy_device *phydev = NULL;
+	int err;
+
+	phydev = of_phy_connect(ndev, priv->phy_node, &sun8i_emac_adjust_link,
+				0, priv->phy_interface);
+
+	if (IS_ERR(phydev)) {
+		err = PTR_ERR(phydev);
+		netdev_err(ndev, "Could not attach to PHY: %d\n", err);
+		return err;
+	}
+
+	netdev_info(ndev, "%s: PHY ID %08x at %d IRQ %s (%s)\n",
+		    ndev->name, phydev->phy_id, phydev->mdio.addr,
+		    "poll", phydev_name(phydev));
+
+	/* mask with MAC supported features */
+	phydev->supported &= PHY_GBIT_FEATURES;
+	phydev->advertising = phydev->supported;
+	phy_print_status(phydev);
+
+	priv->link = 0;
+	priv->speed = 0;
+	priv->duplex = -1;
+
+	return 0;
+}
+
+static int sun8i_emac_set_mac_address(struct net_device *ndev, void *p)
+{
+	struct sun8i_emac_priv *priv = netdev_priv(ndev);
+
+	dev_info(priv->dev, "%s\n", __func__);
+	sun8i_emac_set_macaddr(priv, p, 0);
+	return 0;
+}
+
+static int sun8i_emac_open(struct net_device *ndev)
+{
+	struct sun8i_emac_priv *priv = netdev_priv(ndev);
+	int err;
+	u32 v, dr, dt, vtxphy;
+	struct dma_desc *ddesc;
+	int i;
+	int timeout = 0;
+	unsigned long rate;
+
+	err = sun8i_emac_mdio_probe(ndev);
+	if (err)
+		return err;
+
+	/* Do SOFT RST */
+	v = readl(priv->base + SUN8I_EMAC_BASIC_CTL1);
+	writel(v | 0x01, priv->base + SUN8I_EMAC_BASIC_CTL1);
+
+	/* wait for reset to be ended */
+	do {
+		v = readl(priv->base + SUN8I_EMAC_BASIC_CTL1);
+	} while ((v & 0x01) != 0 && timeout++ < 50);
+	if (timeout >= 50)
+		dev_warn(priv->dev, "EMAC reset timeout\n");
+	/*TODO continue or die ? */
+
+	rate = clk_get_rate(priv->ahb_clk);
+	if (rate > 160000000)
+		v = 0x3 << 20; /* AHB / 128 */
+	else if (rate > 80000000)
+		v = 0x2 << 20; /* AHB / 64 */
+	else if (rate > 40000000)
+		v = 0x1 << 20; /* AHB / 32 */
+	else
+		v = 0x0 << 20; /* AHB / 16 */
+	dev_info(priv->dev, "MDC auto : %x\n", v);
+	writel(v, priv->base + SUN8I_EMAC_MDIO_CMD);
+
+	/* DMA */
+	v = (8 << 24);/* burst len */
+	writel(v, priv->base + SUN8I_EMAC_BASIC_CTL1);
+#define RX_INT                  BIT(8)
+#define TX_INT                  BIT(0)
+#define TX_UNF_INT              BIT(4)
+	writel(RX_INT | TX_INT | TX_UNF_INT, priv->base + SUN8I_EMAC_INT_EN);
+	v = readl(priv->base + SUN8I_EMAC_INT_EN);
+
+	v = readl(priv->base + SUN8I_EMAC_TX_CTL0);
+	/* TX_FRM_LEN_CL */
+	/*v |= BIT(30);*/
+	writel(v, priv->base + SUN8I_EMAC_TX_CTL0);
+
+	v = readl(priv->base + SUN8I_EMAC_RX_CTL0);
+	/* CHECK_CRC */
+	v |= BIT(27);
+	/* STRIP_FCS */
+	v |= BIT(28);
+	/* JUMBO_FRM_EN */
+	v |= BIT(29);
+	writel(v, priv->base + SUN8I_EMAC_RX_CTL0);
+
+	v = readl(priv->base + SUN8I_EMAC_TX_CTL1);
+	/* TX_MD Transmission starts after a full frame located in TX DMA FIFO*/
+	v |= BIT(1);
+	writel(v, priv->base + SUN8I_EMAC_TX_CTL1);
+
+	v = readl(priv->base + SUN8I_EMAC_RX_CTL1);
+	/* RX_MD RX DMA reads data from RX DMA FIFO to host memory after a
+	 * complete frame has been written to RX DMA FIFO
+	*/
+	v |= BIT(1);
+	writel(v, priv->base + SUN8I_EMAC_RX_CTL1);
+
+	sun8i_emac_set_mac_address(ndev, ndev->dev_addr);
+
+	priv->rx_sk = kcalloc(nbdesc, sizeof(struct sk_buff *), GFP_KERNEL);
+	if (!priv->rx_sk) {
+		err = -ENOMEM;
+		goto rx_sk_error;
+	}
+	priv->tx_sk = kcalloc(nbdesc, sizeof(struct sk_buff *), GFP_KERNEL);
+	if (!priv->tx_sk) {
+		err = -ENOMEM;
+		goto tx_sk_error;
+	}
+
+	priv->dd_rx = dma_alloc_coherent(priv->dev,
+			nbdesc * sizeof(struct dma_desc),
+			&priv->dd_rx_phy,
+			GFP_KERNEL);
+	if (!priv->dd_rx) {
+		dev_err(priv->dev, "ERROR: cannot DMA RX");
+		err = -ENOMEM;
+		goto dma_rx_error;
+	}
+	memset(priv->dd_rx, 0, nbdesc * sizeof(struct dma_desc));
+	ddesc = priv->dd_rx;
+	for (i = 0; i < nbdesc; i++) {
+		sun8i_emac_rx_sk(ndev, i);
+		ddesc->next = (u32)priv->dd_rx_phy + (i + 1)
+			* sizeof(struct dma_desc);
+		ddesc++;
+	}
+	/* last descriptor point back to first one */
+	ddesc--;
+	ddesc->next = (u32)priv->dd_rx_phy;
+
+	priv->dd_tx = dma_alloc_coherent(priv->dev,
+			nbdesc * sizeof(struct dma_desc),
+			&priv->dd_tx_phy,
+			GFP_KERNEL);
+	if (!priv->dd_tx) {
+		dev_err(priv->dev, "ERROR: cannot DMA TX");
+		err = -ENOMEM;
+		goto dma_tx_error;
+	}
+	memset(priv->dd_tx, 0, nbdesc * sizeof(struct dma_desc));
+	ddesc = priv->dd_tx;
+	for (i = 0; i < nbdesc; i++) {
+		ddesc->status = 0;
+		ddesc->st = 0;
+		ddesc->next = (u32)(priv->dd_tx_phy + (i + 1)
+			* sizeof(struct dma_desc));
+		ddesc++;
+	}
+	/* last descriptor point back to first one */
+	ddesc--;
+	ddesc->next = (u32)priv->dd_tx_phy;
+	i--;
+
+	if (ndev->phydev)
+		phy_start(ndev->phydev);
+
+	writel(priv->dd_rx_phy, priv->base + SUN8I_EMAC_RX_DESC_LIST);
+	v = readl(priv->base + SUN8I_EMAC_RX_CTL1);
+	v |= BIT(30);
+	/*v |= (1 << 31);*/
+	/*dev_info(priv->dev, "%s %lx %lx\n", __func__, 0x40000000, BIT(30));*/
+	writel(v, priv->base + SUN8I_EMAC_RX_CTL1);
+
+	writel(priv->dd_tx_phy, priv->base + SUN8I_EMAC_TX_DESC_LIST);
+	v = readl(priv->base + SUN8I_EMAC_TX_CTL1);
+	v |= BIT(30);
+/*	v |= (1 << 31);*/
+	writel(v, priv->base + SUN8I_EMAC_TX_CTL1);
+
+	/* activate TX */
+	v = readl(priv->base + SUN8I_EMAC_TX_CTL0);
+	v |= (1 << 31);
+	writel(v, priv->base + SUN8I_EMAC_TX_CTL0);
+
+	/* activate RX */
+	v = readl(priv->base + SUN8I_EMAC_RX_CTL0);
+	v |= (1 << 31);
+	writel(v, priv->base + SUN8I_EMAC_RX_CTL0);
+
+	writel(0x3FFF, priv->base + SUN8I_EMAC_INT_STA);
+	netif_start_queue(ndev);
+
+	v = readl(priv->base + SUN8I_EMAC_RX_DESC_LIST);
+	vtxphy = readl(priv->base + SUN8I_EMAC_TX_DESC_LIST);
+	dt = readl(priv->base + SUN8I_EMAC_TX_DMA_STA);
+	dr = readl(priv->base + SUN8I_EMAC_RX_DMA_STA);
+
+	return 0;
+dma_tx_error:
+	dma_free_coherent(priv->dev, nbdesc * sizeof(struct dma_desc),
+			  priv->dd_rx, priv->dd_rx_phy);
+dma_rx_error:
+	kfree(priv->tx_sk);
+tx_sk_error:
+	kfree(priv->rx_sk);
+rx_sk_error:
+	return err;
+}
+
+static int sun8i_emac_stop(struct net_device *ndev)
+{
+	struct sun8i_emac_priv *priv = netdev_priv(ndev);
+
+	dev_info(priv->dev, "%s\n", __func__);
+
+	/* Stop receiver */
+	writel(0, priv->base + SUN8I_EMAC_RX_CTL0);
+
+	netif_stop_queue(ndev);
+	netif_carrier_off(ndev);
+
+	phy_stop(ndev->phydev);
+	phy_disconnect(ndev->phydev);
+
+	return 0;
+}
+
+static netdev_tx_t sun8i_emac_xmit(struct sk_buff *skb, struct net_device *ndev)
+{
+	struct sun8i_emac_priv *priv = netdev_priv(ndev);
+	struct dma_desc *ddesc;
+	int i = 0, rbd_i;
+	unsigned int len;
+	u32 v;
+	int n;
+	int nf;
+	const skb_frag_t *frag;
+
+	len = skb_headlen(skb);
+
+	dev_dbg(priv->dev, "%s len=%u skblen=%u\n", __func__, len, skb->len);
+
+	spin_lock(&priv->tx_lock);
+	/* find first empty descriptor not optimized */
+	/* tx_slot is bad, better store address and follow ->next */
+	ddesc = priv->dd_tx + priv->tx_slot;
+	if (ddesc->status == 0) {
+		i = priv->tx_slot;
+		rb_inc(&priv->tx_slot);
+		dev_dbg(priv->dev, "%s found txslot %d at %pad (txslot=%d)\n",
+			__func__, i, &ddesc, priv->tx_slot);
+	} else {
+		while (i < nbdesc) {
+			ddesc = priv->dd_tx + i;
+			/*if ((ddesc->status & BIT(31)) == 0) {*/
+			if (ddesc->status == 0)
+				break;
+			i++;
+		}
+		dev_dbg(priv->dev, "%s found slot %d at %pad (txslot=%d)\n",
+			__func__, i, &ddesc, priv->tx_slot);
+	}
+
+	if (i >= nbdesc) {
+		dev_err(priv->dev, "ERROR: TX is full\n");
+		spin_unlock(&priv->tx_lock);
+		return NETDEV_TX_BUSY;
+	}
+
+	priv->tx_sk[i] = skb;
+	ddesc->buf_addr = dma_map_single(priv->dev, skb->data, len,
+					 DMA_TO_DEVICE);
+
+	if (dma_mapping_error(priv->dev, ddesc->buf_addr)) {
+		dev_err(priv->dev, "ERROR: Cannot dmamap buf\n");
+		return -EFAULT;
+	}
+
+	ddesc->st = len;
+	/* undocumented bit that make it works TODO */
+	ddesc->st |= BIT(24);
+	/* Checksum */
+	/* TODO conditional CRC according to ndev->hw_features */
+	ddesc->st |= BIT(27);
+	ddesc->st |= BIT(28);
+	/* frame begin */
+	ddesc->st |= BIT(29);
+	rbd_i = i;
+
+	/* handle fragmented skb */
+	n = skb_shinfo(skb)->nr_frags;
+	if (n > 0) {
+		for (nf = 0; nf < n; nf++) {
+			frag = &skb_shinfo(skb)->frags[nf];
+			rb_inc(&i);
+			ddesc = priv->dd_tx + i;
+			/* TODO check used*/
+			if (ddesc->st & BIT(31))
+				dev_err(priv->dev, "Already used\n");
+			ddesc->st = skb_frag_size(frag);
+			ddesc->st |= BIT(24);
+			ddesc->buf_addr = skb_frag_dma_map(priv->dev, frag, 0,
+							   skb_frag_size(frag), DMA_TO_DEVICE);
+			if (dma_mapping_error(priv->dev, ddesc->buf_addr)) {
+				dev_err(priv->dev, "DMA MAP ERROR\n");
+				/* TODO dma err*/
+			}
+		}
+	}
+
+	/* frame end */
+	ddesc->st |= BIT(30);
+	/* We want an interrupt after transmission */
+	ddesc->st |= BIT(31);
+
+	rb_inc(&i);
+	do {
+		ddesc = priv->dd_tx + rbd_i;
+		ddesc->st |= BIT(27);
+		ddesc->st |= BIT(28);
+		/* DMA can work on it */
+		ddesc->status = BIT(31);
+		dev_dbg(priv->dev, "xmit final %02d %x %x\n", rbd_i,
+			ddesc->status, ddesc->st);
+		rb_inc(&rbd_i);
+	} while (rbd_i != i);
+	priv->tx_slot = i;
+
+	v = readl(priv->base + SUN8I_EMAC_TX_CTL0);
+	v |= BIT(31);/* TODO do a define */
+	writel(v, priv->base + SUN8I_EMAC_TX_CTL0);
+
+	if (priv->tx_dma_start == 0) {
+		v = readl(priv->base + SUN8I_EMAC_TX_CTL1);
+		v |= BIT(31);/* mandatory */
+		v |= BIT(30);/* TODO do a define */
+		writel(v, priv->base + SUN8I_EMAC_TX_CTL1);
+		priv->tx_dma_start = 1;
+	}
+
+	spin_unlock(&priv->tx_lock);
+
+	return NETDEV_TX_OK;
+}
+
+static int sun8i_emac_change_mtu(struct net_device *ndev, int new_mtu)
+{
+	struct sun8i_emac_priv *priv = netdev_priv(ndev);
+	int max_mtu;
+
+	dev_info(priv->dev, "%s set MTU to %d\n", __func__, new_mtu);
+
+	if (netif_running(ndev)) {
+		dev_err(priv->dev, "%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 < 68) || (new_mtu > max_mtu)) {
+		dev_err(priv->dev, "%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 sun8i_emac_fix_features(struct net_device *ndev,
+						 netdev_features_t features)
+{
+	struct sun8i_emac_priv *priv = netdev_priv(ndev);
+
+	dev_info(priv->dev, "%s %llx\n", __func__, features);
+	return features;
+}
+
+static int sun8i_emac_set_features(struct net_device *ndev,
+				   netdev_features_t features)
+{
+	struct sun8i_emac_priv *priv = netdev_priv(ndev);
+	u32 v;
+
+	v = readl(priv->base + SUN8I_EMAC_BASIC_CTL0);
+	if (features & NETIF_F_LOOPBACK && netif_running(ndev)) {
+		dev_info(priv->dev, "Must set loopback\n");
+		v |= BIT(1);
+	} else {
+		dev_info(priv->dev, "Must unset loopback\n");
+		v &= ~BIT(1);
+	}
+	writel(v, priv->base + SUN8I_EMAC_BASIC_CTL0);
+
+	dev_info(priv->dev, "%s %llx %x\n", __func__, features, v);
+
+	return 0;
+}
+
+static void sun8i_emac_set_rx_mode(struct net_device *ndev)
+{
+	struct sun8i_emac_priv *priv = netdev_priv(ndev);
+	u32 v = 0;
+	int i = 0;
+	struct netdev_hw_addr *ha;
+
+	/* Disable address filter */
+	v |= BIT(31);
+	/* Receive all multicast frames */
+	v |= BIT(16);
+	/* Receive all control frames */
+	v |= BIT(13);
+	if (ndev->flags & IFF_PROMISC)
+		v |= BIT(1);
+	if (netdev_uc_count(ndev) > 7) {
+		v |= BIT(1);
+	} else {
+		netdev_for_each_uc_addr(ha, ndev) {
+			i++;
+			sun8i_emac_set_macaddr(priv, ha->addr, i);
+		}
+	}
+	writel(v, priv->base + SUN8I_EMAC_RX_FRM_FLT);
+}
+
+static void sun8i_emac_tx_timeout(struct net_device *ndev)
+{
+	struct sun8i_emac_priv *priv = netdev_priv(ndev);
+
+	dev_info(priv->dev, "%s\n", __func__);
+	/* TODO reset/re-init all (see stmmac)*/
+}
+
+static int sun8i_emac_ioctl(struct net_device *ndev, struct ifreq *rq, int cmd)
+{
+	struct phy_device *phydev = ndev->phydev;
+
+	if (!netif_running(ndev))
+		return -EINVAL;
+
+	if (!phydev)
+		return -ENODEV;
+
+	return phy_mii_ioctl(phydev, rq, cmd);
+}
+
+static int sun8i_emac_config(struct net_device *ndev, struct ifmap *map)
+{
+	struct sun8i_emac_priv *priv = netdev_priv(ndev);
+
+	dev_info(priv->dev, "%s\n", __func__);
+	return -EINVAL;
+}
+
+static void sun8i_emac_poll_controller(struct net_device *ndev)
+{
+	struct sun8i_emac_priv *priv = netdev_priv(ndev);
+
+	dev_info(priv->dev, "%s\n", __func__);
+}
+
+static int sun8i_emac_check_if_running(struct net_device *ndev)
+{
+	struct sun8i_emac_priv *priv = netdev_priv(ndev);
+
+	dev_info(priv->dev, "%s %d\n", __func__, netif_running(ndev));
+	if (!netif_running(ndev))
+		return -EBUSY;
+	return 0;
+}
+
+static int sun8i_emac_get_sset_count(struct net_device *ndev, int sset)
+{
+	int len;
+	struct sun8i_emac_priv *priv = netdev_priv(ndev);
+
+	dev_info(priv->dev, "%s\n", __func__);
+
+	switch (sset) {
+	case ETH_SS_STATS:
+		len = 0;
+		return len;
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int sun8i_emac_ethtool_get_settings(struct net_device *ndev,
+					   struct ethtool_cmd *cmd)
+{
+	struct phy_device *phy = ndev->phydev;
+	struct sun8i_emac_priv *priv = netdev_priv(ndev);
+	int rc;
+
+	dev_info(priv->dev, "%s\n", __func__);
+
+	if (!phy) {
+		netdev_err(ndev, "%s: %s: PHY is not registered\n",
+			   __func__, ndev->name);
+		return -ENODEV;
+	}
+
+	if (!netif_running(ndev)) {
+		dev_err(priv->dev, "interface disabled: we cannot track link speed / duplex setting\n");
+		return -EBUSY;
+	}
+
+	cmd->transceiver = XCVR_INTERNAL;
+	rc = phy_ethtool_gset(phy, cmd);
+
+	return rc;
+}
+
+static int sun8i_emac_ethtool_set_settings(struct net_device *ndev,
+					   struct ethtool_cmd *cmd)
+{
+	struct phy_device *phy = ndev->phydev;
+	struct sun8i_emac_priv *priv = netdev_priv(ndev);
+	int rc;
+
+	dev_info(priv->dev, "%s\n", __func__);
+
+	rc = phy_ethtool_sset(phy, cmd);
+
+	return rc;
+}
+
+static void sun8i_emac_ethtool_getdrvinfo(struct net_device *ndev,
+					  struct ethtool_drvinfo *info)
+{
+	struct sun8i_emac_priv *priv = netdev_priv(ndev);
+
+	dev_info(priv->dev, "%s\n", __func__);
+	strlcpy(info->driver, "sun8i_emac", sizeof(info->driver));
+	strcpy(info->version, "00");
+	info->fw_version[0] = '\0';
+}
+
+static const struct ethtool_ops sun8i_emac_ethtool_ops = {
+	.begin = sun8i_emac_check_if_running,
+	.get_settings = sun8i_emac_ethtool_get_settings,
+	.set_settings = sun8i_emac_ethtool_set_settings,
+	.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 = sun8i_emac_get_sset_count,
+	.get_drvinfo = sun8i_emac_ethtool_getdrvinfo,
+};
+
+static const struct net_device_ops sun8i_emac_netdev_ops = {
+	.ndo_init = sun8i_emac_init,
+	.ndo_uninit = sun8i_emac_uninit,
+	.ndo_open = sun8i_emac_open,
+	.ndo_start_xmit = sun8i_emac_xmit,
+	.ndo_stop = sun8i_emac_stop,
+	.ndo_change_mtu = sun8i_emac_change_mtu,
+	.ndo_fix_features = sun8i_emac_fix_features,
+	.ndo_set_rx_mode = sun8i_emac_set_rx_mode,
+	.ndo_tx_timeout = sun8i_emac_tx_timeout,
+	.ndo_do_ioctl = sun8i_emac_ioctl,
+	.ndo_set_config = sun8i_emac_config,
+#ifdef CONFIG_NET_POLL_CONTROLLER
+	.ndo_poll_controller = sun8i_emac_poll_controller,
+#endif
+	.ndo_set_mac_address = sun8i_emac_set_mac_address,
+	.ndo_set_features = sun8i_emac_set_features,
+};
+
+static irqreturn_t sun8i_emac_dma_interrupt(int irq, void *dev_id)
+{
+	struct net_device *ndev = (struct net_device *)dev_id;
+	struct sun8i_emac_priv *priv = netdev_priv(ndev);
+	u32 v, u;
+
+	v = readl(priv->base + SUN8I_EMAC_INT_STA);
+
+	dev_dbg(priv->dev, "%s %x\n", __func__, v);
+
+	/* When this bit is asserted, a frame transmission is completed. */
+	if (v & BIT(0))
+		sun8i_emac_complete_xmit(ndev);
+
+	/* When this bit is asserted, the TX DMA FSM is stopped. */
+	if (v & BIT(1))
+		priv->tx_dma_start = 0;
+
+	/* When this asserted, the TX DMA can not acquire next TX descriptor
+	 * and TX DMA FSM is suspended.
+	*/
+	if (v & BIT(2))
+		priv->tx_dma_start = 0;
+
+	if (v & BIT(3))
+		dev_info(priv->dev, "Unhandled interrupt TX TIMEOUT\n");
+	if (v & BIT(4))
+		dev_info(priv->dev, "Unhandled interrupt TX EARLY\n");
+
+	/* When this bit asserted , the frame is transmitted to FIFO totally. */
+	if (v & BIT(5))
+		dev_dbg(priv->dev, "Unhandled interrupt TX_EARLY_INT\n");
+
+	/* When this bit is asserted, a frame reception is completed  */
+	if (v & BIT(8))
+		sun8i_emac_receive_all(ndev);
+
+	/* When this asserted, the RX DMA can not acquire next TX descriptor
+	 * and TX DMA FSM is suspended.
+	*/
+	if (v & BIT(9)) {
+		u = readl(priv->base + SUN8I_EMAC_RX_CTL1);
+		dev_info(priv->dev, "Re-run RX DMA %x\n", u);
+		writel(u | BIT(31), priv->base + SUN8I_EMAC_RX_CTL1);
+	}
+
+	if (v & BIT(10))
+		dev_info(priv->dev, "Unhandled interrupt RX_DMA_STOPPED_INT\n");
+	if (v & BIT(11))
+		dev_info(priv->dev, "Unhandled interrupt RX_TIMEOUT\n");
+	if (v & BIT(12))
+		dev_info(priv->dev, "Unhandled interrupt RX OVERFLOW\n");
+	if (v & BIT(13))
+		dev_info(priv->dev, "Unhandled interrupt RX EARLY\n");
+	if (v & BIT(16))
+		dev_info(priv->dev, "Unhandled interrupt RGMII\n");
+
+	writel(v & 0x3FFF, priv->base + SUN8I_EMAC_INT_STA);
+
+	return IRQ_HANDLED;
+}
+
+static int sun8i_emac_probe(struct platform_device *pdev)
+{
+	struct resource *res;
+	struct sun8i_emac_priv *priv;
+	struct net_device *ndev;
+	int ret;
+
+	ndev = alloc_etherdev(sizeof(*priv));
+	if (!ndev)
+		return -ENOMEM;
+
+	SET_NETDEV_DEV(ndev, &pdev->dev);
+	priv = netdev_priv(ndev);
+	platform_set_drvdata(pdev, ndev);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	priv->base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(priv->base)) {
+		ret = PTR_ERR(priv->base);
+		dev_err(&pdev->dev, "Cannot request MMIO: %d\n", ret);
+		goto probe_err;
+	}
+
+	priv->irq = platform_get_irq(pdev, 0);
+	if (priv->irq < 0) {
+		ret = priv->irq;
+		dev_err(&pdev->dev, "Cannot claim IRQ: %d\n", ret);
+		goto probe_err;
+	}
+
+	ret = devm_request_irq(&pdev->dev, priv->irq, sun8i_emac_dma_interrupt,
+			       0, dev_name(&pdev->dev), ndev);
+	if (ret) {
+		dev_err(&pdev->dev, "Cannot request IRQ: %d\n", ret);
+		goto probe_err;
+	}
+
+	priv->ahb_clk = devm_clk_get(&pdev->dev, "bus_gmac");
+	if (IS_ERR(priv->ahb_clk)) {
+		ret = PTR_ERR(priv->ahb_clk);
+		dev_err(&pdev->dev, "Cannot get AHB clock err=%d\n", ret);
+		goto probe_err;
+	}
+
+	priv->rst = devm_reset_control_get_optional(&pdev->dev, "ahb");
+	if (IS_ERR(priv->rst)) {
+		ret = PTR_ERR(priv->rst);
+		dev_info(&pdev->dev, "no mac reset control found %d\n", ret);
+		priv->rst = NULL;
+	}
+
+	spin_lock_init(&priv->lock);
+	spin_lock_init(&priv->tx_lock);
+
+	priv->tx_slot = 0;
+
+	ndev->netdev_ops = &sun8i_emac_netdev_ops;
+	ndev->ethtool_ops = &sun8i_emac_ethtool_ops;
+
+	priv->ndev = ndev;
+	priv->dev = &pdev->dev;
+
+	ndev->base_addr = (unsigned long)priv->base;
+	ndev->irq = priv->irq;
+
+	ndev->hw_features = NETIF_F_SG | NETIF_F_HIGHDMA;
+	ndev->hw_features |= 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(5000);
+
+	ret = register_netdev(ndev);
+	if (ret) {
+		dev_err(&pdev->dev, "ERROR: Register %s failed\n", ndev->name);
+		goto probe_err;
+	}
+
+	sun8i_emac_set_macaddr(priv, ndev->dev_addr, 0);
+
+	return 0;
+probe_err:
+	free_netdev(ndev);
+	return ret;
+}
+
+static int sun8i_emac_remove(struct platform_device *pdev)
+{
+	struct net_device *ndev = platform_get_drvdata(pdev);
+	struct sun8i_emac_priv *priv = netdev_priv(ndev);
+
+	clk_disable_unprepare(priv->tx_clk);
+	if (priv->rst_phy)
+		reset_control_assert(priv->rst_phy);
+
+	unregister_netdev(ndev);
+	platform_set_drvdata(pdev, NULL);
+	free_netdev(ndev);
+	return 0;
+}
+
+static const struct of_device_id sun8i_emac_of_match_table[] = {
+	{ .compatible = "allwinner,sun8i-h3-emac" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, sun8i_emac_of_match_table);
+
+static struct platform_driver sun8i_emac_driver = {
+	.probe          = sun8i_emac_probe,
+	.remove         = sun8i_emac_remove,
+	.driver         = {
+		.name           = "sun8i-emac",
+		.of_match_table	= sun8i_emac_of_match_table,
+	},
+};
+
+module_platform_driver(sun8i_emac_driver);
+
+MODULE_DESCRIPTION("SUN8I Ethernet driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("LABBE Corentin <clabbe.montjoie@gmail.co");
-- 
2.7.3

