From c9869459825598ad1597ea263514d0fe6a8d9695 Mon Sep 17 00:00:00 2001 From: LABBE Corentin Date: Thu, 14 Jan 2016 11:40:23 +0100 Subject: [PATCH 5/5] 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 --- drivers/net/ethernet/allwinner/Kconfig | 14 + drivers/net/ethernet/allwinner/Makefile | 1 + drivers/net/ethernet/allwinner/sun8i-emac.c | 1565 +++++++++++++++++++++++++++ 3 files changed, 1580 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..43cf91f 100644 --- a/drivers/net/ethernet/allwinner/Kconfig +++ b/drivers/net/ethernet/allwinner/Kconfig @@ -33,4 +33,18 @@ 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 + select MDIO_SUN4I + ---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..013cc2e --- /dev/null +++ b/drivers/net/ethernet/allwinner/sun8i-emac.c @@ -0,0 +1,1565 @@ +/* + * sun8i-h3-emac driver + * + * Copyright (C) 2015-2016 Corentin LABBE + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +/* for A83T */ +#include + +#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; +} __attribute__((packed, aligned(4))); + +static int flow_ctrl; +static int pause = 0x400; + +static int nbdesc = 8; +struct sun8i_emac_priv { + void __iomem *base; + int irq; + struct device *dev; + struct net_device *ndev; + struct mii_bus *mdio; + spinlock_t lock; + spinlock_t tx_lock; + 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; + +}; + +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; + + sk = netdev_alloc_skb_ip_align(ndev, ndev->mtu); + 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 |= ndev->mtu; + ddesc->status = BIT(31); + + dev_info(priv->dev, "Init ddesc %02d at %pad buff=%p %x status=(%x %x) len=%d\n", + i, &ddesc, &sk->data, ddesc->buf_addr, ddesc->status, + ddesc->st, ndev->mtu); + + 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, "%s slot %d %x %x %x %x %x %x\n", __func__, index, + 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); + dev_info(priv->dev, "Set macaddr %x\n", v); + v = (addr[3] << 24) | (addr[2] << 16) | (addr[1] << 8) | addr[0]; + writel(v, priv->base + SUN8I_EMAC_MACADDR_HI + index * 8); + dev_info(priv->dev, "Set macaddr %x\n", v); +} + +void sun8i_emac_set_link_mode(struct sun8i_emac_priv *priv) +{ + u32 v; + + dev_info(priv->dev, "%s duplex=%x\n", __func__, priv->duplex); + + switch (priv->phy_interface) { + case PHY_INTERFACE_MODE_MII: + 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); + break; + case PHY_INTERFACE_MODE_RGMII: + v = readl(priv->base + SUN8I_EMAC_RGMII_STA); + + if (!priv->duplex) + v &= ~BIT(0); + else + v |= BIT(0); + + v &= ~0x06; + switch (priv->speed) { + case 1000: + v |= BIT(2); + break; + case 100: + v |= BIT(1); + break; + case 10: + break; + } + + writel(v, priv->base + SUN8I_EMAC_RGMII_STA); + break; + default: + dev_err(priv->dev, "Unknown PHY type %d\n", priv->phy_interface); + return; + } + + dev_info(priv->dev, "%s set %x\n", __func__, v); +} + +static void sun8i_emac_flow_ctrl(struct sun8i_emac_priv *priv, int duplex, int fc, + int pause) +{ + u32 flow = 0; + + dev_info(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 +*/ +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; + + if ((ddesc->status & BIT(9)) == 0) { + dev_warn(priv->dev, "Multi frame not implemented\n"); + } + if ((ddesc->status & BIT(8)) == 0) { + dev_warn(priv->dev, "Multi frame not implemented\n"); + } + /* possible erros */ + if ((ddesc->status & BIT(0)) > 0) { + dev_warn(priv->dev, "the checksum or length of received frame’s payload is wrong.\n"); + } + if ((ddesc->status & BIT(7)) > 0) { + dev_warn(priv->dev, "RX_HEADER_ERR\n"); + } + + + frame_len = (ddesc->status >> 16) & 0x3FFF; + skb = priv->rx_sk[i]; + + dev_info(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; + + netif_rx(skb); + + priv->rx_sk[i] = NULL; + + sun8i_emac_rx_sk(ndev, i); + + return 0; +} + +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 */ +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_info(priv->dev, "%s\n", __func__); + + /*spin_lock(&priv->tx_lock);*/ + 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 & 0x3FF; + dev_info(priv->dev, "%s found slot to clean %d at %pad %x %x (len=%d)\n", + __func__, i, &ddesc, ddesc->status, ddesc->st, + frame_len); + dma_unmap_single(priv->dev, ddesc->buf_addr, + frame_len, DMA_TO_DEVICE); + priv->tx_sk[i] = NULL; + ddesc->status = 0; + ddesc->st = 0; + } + } + /*spin_unlock(&priv->tx_lock);*/ + + return 0; +} + +static int debug_printall_desc(struct net_device *ndev) +{ + struct sun8i_emac_priv *priv = netdev_priv(ndev); + struct dma_desc *ddesc; + int i; + int len; + /*TODO hex_dump_to_buffer*/ + + return 0; + ddesc = priv->dd_rx; + if (!ddesc) + return 0; + len = 0; + for (i = 0; i < nbdesc; i++) { + ddesc = priv->dd_rx + i; + dev_info(priv->dev, "rx%02d status=%x %x d=%x len=%d n=%x", i, ddesc->status, ddesc->st, ddesc->buf_addr, len, ddesc->next); + } + ddesc = priv->dd_tx; + if (!ddesc) + return 0; + for (i = 0; i < nbdesc; i++) { + ddesc = priv->dd_tx + i; + len = ddesc->st & 0x7FF; + dev_info(priv->dev, "tx%02d status=%x %x d=%x len=%d n=%x", i, ddesc->status, ddesc->st, ddesc->buf_addr, len, ddesc->next); + } + 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; + } + + /* TODO the MDC value is ... */ + 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_info(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; + /*int i;*/ + + dev_info(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); + /*priv->phy_interface = PHY_INTERFACE_MODE_RGMII;*/ + + /* 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; + } else { + 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; + int timeout, value; + + phydev = phy_find_first(priv->mdio); + if (!phydev) { + netdev_err(ndev, "No PHY found!\n"); + err = PTR_ERR(phydev); + return err; + } + + phydev->irq = PHY_POLL; + + /*priv->phy_interface = PHY_INTERFACE_MODE_MII;*/ + +/* phydev = of_phy_connect(ndev, */ + phydev = phy_connect(ndev, phydev_name(phydev), + &sun8i_emac_adjust_link, 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)); + + phy_write(phydev, MII_BMCR, BMCR_RESET); + timeout = 0; + while (BMCR_RESET & phy_read(phydev, MII_BMCR) && timeout++ < 15) + msleep(3); + if (timeout >= 15) + dev_warn(priv->dev, "PHY reset timeout\n"); + + value = phy_read(phydev, MII_BMCR); + phy_write(phydev, MII_BMCR, (value & ~BMCR_PDOWN)); + + dev_info(priv->dev, "PHY supported %x\n", phydev->supported); + phydev->supported &= PHY_GBIT_FEATURES; + phydev->advertising = phydev->supported; + phy_print_status(phydev); + + ndev->phydev = phydev; /* unnecessary ? (done by a sub function of phy_connect */ + 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; + + dev_info(priv->dev, "%s\n", __func__); + + 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"); + + /* 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); + dev_info(priv->dev, "INT %x\n", v); + + 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); + dev_info(priv->dev, "SUN8I_EMAC_RX_CTL0 %x\n", v); + + /* TODO removing that line make PHY to not work. why (since MDC is configured elsewhere)? */ + priv->mdc = 0x02; + writel((priv->mdc << 20), priv->base + SUN8I_EMAC_MDIO_CMD); + + 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); + dev_info(priv->dev, "%d %pad to %x (base is %pad)", + i, &ddesc, ddesc->next, &priv->dd_rx); + 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)); + dev_info(priv->dev, "TXdesc %02d %p to %x", + i, ddesc, ddesc->next); + ddesc++; + } + /* last descriptor point back to first one */ + ddesc--; + ddesc->next = (u32)priv->dd_tx_phy; + i--; + dev_info(priv->dev, "TXdesc %02d %p to %x", i, ddesc, ddesc->next); + + if (ndev->phydev) { + dev_info(priv->dev, "on start le phy\n"); + 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); + dev_info(priv->dev, "SUN8I_EMAC_RX_CTL1 %x\n", v); + + 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); + dev_info(priv->dev, "SUN8I_EMAC_TX_CTL1 %x\n", v); + + /* activate TX */ + v = readl(priv->base + SUN8I_EMAC_TX_CTL0); + v |= (1 << 31); + writel(v, priv->base + SUN8I_EMAC_TX_CTL0); + dev_info(priv->dev, "SUN8I_EMAC_TX_CTL0 %x\n", v); + + /* activate RX */ + v = readl(priv->base + SUN8I_EMAC_RX_CTL0); + v |= (1 << 31); + writel(v, priv->base + SUN8I_EMAC_RX_CTL0); + dev_info(priv->dev, "SUN8I_EMAC_RX_CTL0 %x\n", v); + + 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); + dev_info(priv->dev, "Verify DMA RX LIST %x %x %x %x TXphy=%x/%x\n", v, priv->dd_rx_phy, dr, dt, vtxphy, priv->dd_tx_phy); + + 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__); + + 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; + unsigned int len; + char *p; + u32 v; + + /* TODO len > mtu */ + + p = skb->data; + len = skb_headlen(skb); + + dev_info(priv->dev, "%s xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx len=%u\n", __func__, len); + /*hex_dump_to_buffer(*/ + + 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; + priv->tx_slot++; + if (priv->tx_slot >= nbdesc) + priv->tx_slot = 0; + } else { + while (i < nbdesc) { + ddesc = priv->dd_tx + i; + /*if ((ddesc->status & BIT(31)) == 0) {*/ + if (ddesc->status == 0) + break; + i++; + } + } + dev_info(priv->dev, "%s found slot %d at %pad (slot=%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_OK; /* TODO DEBUG */ + 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 + * ndev->hw_features & NETIF_F_IP_CSUM/NETIF_F_IPV6_CSUM */ + ddesc->st |= BIT(27); + ddesc->st |= BIT(28); + /* frame begin */ + ddesc->st |= BIT(29); + /* frame end */ + ddesc->st |= BIT(30); + /* We want an interrupt after transmission */ + ddesc->st |= BIT(31); + /* DMA can work on it */ + ddesc->status = BIT(31); + + v = readl(priv->base + SUN8I_EMAC_TX_CTL0); + v |= BIT(31);/* TODO do a define */ + writel(v, priv->base + SUN8I_EMAC_TX_CTL0); + 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); + + ndev->stats.tx_bytes += skb->len; + + 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\n", __func__); + + 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; + + dev_info(priv->dev, "%s\n", __func__); + /* 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 sun8i_emac_priv *priv = netdev_priv(ndev); + struct phy_device *phydev = ndev->phydev; + + dev_info(priv->dev, "%s %x\n", __func__, cmd); + + 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; +/* struct dma_desc *ddesc;*/ + + v = readl(priv->base + SUN8I_EMAC_INT_STA); + + dev_info(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)) { + u = readl(priv->base + SUN8I_EMAC_TX_CUR_DDESC); + dev_info(priv->dev, "TX DMA currddesc=%x\n", u); + u = readl(priv->base + SUN8I_EMAC_TX_CTL1); + dev_info(priv->dev, "Re-start TX DMA %x\n", u); + writel(u | BIT(31), priv->base + SUN8I_EMAC_TX_CTL1); + } + /* When this asserted, the TX DMA can not acquire next TX descriptor + * and TX DMA FSM is suspended. + */ + if (v & BIT(2)) { + u = readl(priv->base + SUN8I_EMAC_TX_CUR_DDESC); + dev_info(priv->dev, "TX DMA currddesc=%x\n", u); + /*ddesc = priv->dd_tx; + ddesc->status = BIT(31);*/ + /*writel(u + 0x10, priv->base + SUN8I_EMAC_TX_DESC_LIST);*/ + + u = readl(priv->base + SUN8I_EMAC_TX_CTL1); + dev_info(priv->dev, "Re-run TX DMA %x\n", u); + writel(u | BIT(31), priv->base + SUN8I_EMAC_TX_CTL1); + } + + 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_info(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); + + debug_printall_desc(ndev); + + 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; + + if (!pdev->dev.of_node) + return -ENODEV; + + 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)) { + dev_err(&pdev->dev, "Cannot request MMIO\n"); + return PTR_ERR(priv->base); + } + + priv->irq = platform_get_irq(pdev, 0); + if (priv->irq < 0) { + dev_err(&pdev->dev, "Cannot claim IRQ\n"); + return priv->irq; + } + + 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\n"); + return ret; + } + + 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); + return ret; + } +/* ret = clk_prepare_enable(priv->ahb_clk); + if (ret != 0) { + dev_err(&pdev->dev, "Cannot prepare_enable ahb_clk\n"); + return ret; + }*/ + 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; + } + /* + if (priv->rst) { + ret = reset_control_deassert(priv->rst); + if (ret) + dev_info(&pdev->dev, "Cannot deassert mac\n"); + } +*/ + spin_lock_init(&priv->lock); + spin_lock_init(&priv->tx_lock); + + priv->tx_slot = 0; + + ether_setup(ndev); + 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); + + /*stmmac_set_ethtool_ops(ndev);*/ + + ret = register_netdev(ndev); + if (ret) { + dev_err(&pdev->dev, "ERROR: Register %s failed\n", ndev->name); + return ret; + } + + sun8i_emac_set_macaddr(priv, ndev->dev_addr, 0); + + return 0; +} + +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->ahb_clk); + clk_disable_unprepare(priv->tx_clk); + if (priv->rst) + reset_control_assert(priv->rst); + 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