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.montj...@gmail.com> --- drivers/net/ethernet/allwinner/Kconfig | 14 + drivers/net/ethernet/allwinner/Makefile | 1 + drivers/net/ethernet/allwinner/sun8i-emac.c | 1493 +++++++++++++++++++++++++++ 3 files changed, 1508 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..a4e52cf --- /dev/null +++ b/drivers/net/ethernet/allwinner/sun8i-emac.c @@ -0,0 +1,1493 @@ +/* + * sun8i-h3-emac driver + * + * Copyright (C) 2015-2016 Corentin LABBE <clabbe.montj...@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/clk.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 <linux/err.h> +#include <linux/scatterlist.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/reset.h> +#include <linux/of_net.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_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 MII_BUSY BIT(0) +#define MII_WRITE BIT(1) + +#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 = 4; +struct sun8i_emac_priv { + void __iomem *base; + int irq; + struct device *dev; + struct net_device *ndev; + struct mii_bus *mii; + spinlock_t lock; + spinlock_t tx_lock; + int duplex; + int speed; + int link; + u32 phy_interface; + struct clk *busclk; + struct clk *miiclk; + u32 mdc; + + struct reset_control *rst_phy; + struct reset_control *rst_mac; + + 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; + + struct regulator *power; +}; + +/* allocate a sk in a dma descriptor +*/ +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)\n", + i, &ddesc, &sk->data, ddesc->buf_addr, ddesc->status, ddesc->st); + + return 0; +} + +/* Set MAC address for slot index + * */ +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 %x %x %x %x %x %x\n", __func__, + 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); +} + +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; + + frame_len = (ddesc->status >> 16) & 0x3FFF; + skb = priv->rx_sk[i]; + + dev_info(priv->dev, "%s from %02d %pad len=%d st=%x\n", + __func__, i, &ddesc, frame_len, 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__); + + 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\n", + __func__, i, &ddesc, ddesc->status, ddesc->st); + dma_unmap_single(priv->dev, ddesc->buf_addr, + frame_len, DMA_TO_DEVICE); + priv->tx_sk[i] = NULL; + ddesc->status = 0; + ddesc->st = 0; + } + } + + 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; +/* + ddesc = priv->dd_rx; + if (ddesc == NULL) + return 0; + + for (i = 0; i < nbdesc; i++) { + ddesc = priv->dd_rx + i; + len = (ddesc->status >> 16) & 0x3FFF; + if (!(ddesc->status & BIT(31))) { + ptr = (char *)priv->rx_sk[i]->data; + dev_info(priv->dev, "%x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x\n", + ptr[0], + ptr[1], + ptr[2], + ptr[3], + ptr[4], + ptr[5], + ptr[6], + ptr[7], + ptr[8], + ptr[9], + ptr[10], + ptr[11], + ptr[12], + ptr[13], + ptr[14], + ptr[15]); + rx_from_ddesc(ndev, i); + } + ddesc++; + } +*/ + ddesc = priv->dd_tx; + if (ddesc == NULL) + 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 phyaddr, int phyreg) +{ + u32 v, c, t, dt, dr, x = 0; + struct net_device *ndev = bus->priv; + struct sun8i_emac_priv *priv = netdev_priv(ndev); + struct dma_desc *ddesc; + u32 cur_rd, cur_td; + + dt = readl(priv->base + SUN8I_EMAC_TX_DMA_STA); + dr = readl(priv->base + SUN8I_EMAC_RX_DMA_STA); + debug_printall_desc(ndev); + + ddesc = priv->dd_rx; + if (ddesc != NULL) + x = ddesc->status; + + do { + v = readl(priv->base + SUN8I_EMAC_MDIO_CMD); + } while ((v & MII_BUSY) == 1); + /* TODO timeout */ + + c = 0; + c |= ((priv->mdc & 0x07) << 20); + c |= (((phyaddr << 12) & (0x0001F000)) | + ((phyreg << 4) & (0x000007F0)) | MII_BUSY); + writel(c, priv->base + SUN8I_EMAC_MDIO_CMD); + t = 0; + do { + v = readl(priv->base + SUN8I_EMAC_MDIO_CMD); + } while ((v & MII_BUSY) == 1 && t++ < 1500); + /* TODO timeout */ + + v = readl(priv->base + SUN8I_EMAC_MDIO_DATA); + cur_rd = readl(priv->base + 0xC4); + cur_td = readl(priv->base + SUN8I_EMAC_TX_CUR_DDESC); + + /*dev_info(priv->dev, "%s %d %d %x %x t=%u dmar=%x dmat=%x %x cur=%x %x\n", __func__, phyaddr, phyreg, c, v, t, dr, dt, x, cur_rd, cur_td);*/ + return (int)v; +} + +static int sun8i_mdio_write(struct mii_bus *bus, int phyaddr, int phyreg, u16 data) +{ + struct net_device *ndev = bus->priv; + struct sun8i_emac_priv *priv = netdev_priv(ndev); + u32 v; + + do { + v = readl(priv->base + SUN8I_EMAC_MDIO_CMD); + } while ((v & MII_BUSY) == 1); + /* TODO timeout */ + + /*v = readl(priv->base + SUN8I_EMAC_MDIO_CMD); + * */ + + v = ((0x07 << 20) & readl(priv->base + SUN8I_EMAC_MDIO_CMD)) | (priv->mdc << 20); + v |= (((phyaddr << 12) & (0x0001F000)) | + ((phyreg << 4) & (0x000007F0))) | MII_WRITE | MII_BUSY; + writel(data, priv->base + SUN8I_EMAC_MDIO_DATA); + writel(v, priv->base + SUN8I_EMAC_MDIO_CMD); + + dev_info(priv->dev, "%s %d %d %x ctl=%x\n", __func__, phyaddr, phyreg, data, v); + + do { + v = readl(priv->base + SUN8I_EMAC_MDIO_CMD); + } while ((v & MII_BUSY) == 1); + /* TODO timeout */ + + return 0; +} + +static int sun8i_mdio_reset(struct mii_bus *bus) +{ + struct net_device *ndev = bus->priv; + struct sun8i_emac_priv *priv = netdev_priv(ndev); + + dev_info(priv->dev, "%s\n", __func__); + + /* No datasheet, undocumented */ + /*writel((4 << 2), priv->base + SUN8I_EMAC_MDIO_CMD);*/ + return 0; +} +/* END of need to moved in phy/ */ + +static void sun8i_phy_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); + + for (i = 0 ; i < 0xd0; i += 16) + dev_info(priv->dev, "%x %x %x %x %x %x %x %x", + readl(priv->base + i), + readl(priv->base + i + 4), + readl(priv->base + i + 8), + readl(priv->base + i + 12), + i, i + 4, i + 8, i + 12); + + spin_unlock_irqrestore(&priv->lock, flags); +} + +/* this function do lots of things that will be splited away (clk/phy) */ +static int sun8i_phy_init(struct net_device *ndev) +{ + int value; + struct mii_bus *new_bus; + struct sun8i_emac_priv *priv = netdev_priv(ndev); + struct phy_device *phydev = NULL; + int err; + void __iomem *sc; + u32 v; + int timeout; + 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 %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; + } + + /* A83T power TODO: no regulator yet */ + priv->power = regulator_get(NULL, "vc-ephy-io"); + if (IS_ERR(priv->power)) { + dev_err(priv->dev, "Cannot get regulator\n"); + } else { + err = regulator_set_voltage(priv->power, 3300000, 3300000); + if (err) { + dev_err(priv->dev, "Cannot set regulator voltage\n"); + } else { + err = regulator_enable(priv->power); + if (err) + dev_err(priv->dev, "Cannot enable regulator\n"); + else + dev_info(priv->dev, "Enabled regulator\n"); + + } + } + + /* 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->miiclk = devm_clk_get(priv->dev, "ahb1_ephy"); + if (IS_ERR(priv->miiclk)) { + err = PTR_ERR(priv->miiclk); + dev_err(priv->dev, "Cannot get MII clock err=%d\n", err); + return err; + } + err = clk_prepare_enable(priv->miiclk); + 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"); + } + msleep(300); + } + + 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 = &sun8i_mdio_read; + new_bus->write = &sun8i_mdio_write; + new_bus->reset = &sun8i_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; + + err = mdiobus_register(new_bus); + if (err) { + dev_err(priv->dev, "%s: Cannot register as MDIO bus\n", + new_bus->name); + goto mdiobus_err; + } + + priv->mii = new_bus; + + phydev = phy_find_first(new_bus); + if (!phydev) { + netdev_err(ndev, "No PHY found!\n"); + err = PTR_ERR(phydev); + goto mdiobus_unreg; + } + + phydev->irq = PHY_POLL; + + /*priv->phy_interface = PHY_INTERFACE_MODE_MII;*/ + +/* phydev = of_phy_connect(ndev, */ + phydev = phy_connect(ndev, dev_name(&phydev->dev), + &sun8i_phy_adjust_link, priv->phy_interface); + + if (IS_ERR(phydev)) { + netdev_err(ndev, "Could not attach to PHY\n"); + err = PTR_ERR(phydev); + goto mdiobus_unreg; + } + + netdev_info(ndev, "%s: PHY ID %08x at %d IRQ %s (%s)\n", + ndev->name, phydev->phy_id, phydev->addr, + "poll", dev_name(&phydev->dev)); + + 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); + + return 0; + +mdiobus_unreg: + mdiobus_unregister(new_bus); +mdiobus_err: + mdiobus_free(new_bus); + return err; +} + +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_phy_init(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 MDC must be set according to clock */ + priv->mdc = 0x02; + writel((priv->mdc << 20), priv->base + SUN8I_EMAC_MDIO_CMD); /* MDC_DIV_RATIO */ + + 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; + + 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__); + + /* TODO */ + + 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; + + p = skb->data; + len = skb_headlen(skb); + + dev_info(priv->dev, "%s xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx len=%u %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x\n", __func__, len, + p[0], p[1], p[2], p[3], + p[4], p[5], p[6], p[7], + p[8], p[9], p[10], p[11], + p[12], p[13], p[14], p[15] + ); + + spin_lock(&priv->tx_lock); + /* find first empty descriptor not optimized */ + while (i < nbdesc) { + ddesc = priv->dd_tx + i; + /*if ((ddesc->status & BIT(31)) == 0) {*/ + if (ddesc->status == 0) { + dev_info(priv->dev, "%s found slot %d at %pad\n", + __func__, i, &ddesc); + break; + } + i++; + } + 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; + /* TODO remove/test 12 - 28 */ + ddesc->st |= BIT(12); + ddesc->st |= BIT(13); + ddesc->st |= BIT(14); + ddesc->st |= BIT(15); + ddesc->st |= BIT(16); + ddesc->st |= BIT(17); + ddesc->st |= BIT(18); + ddesc->st |= BIT(19); + ddesc->st |= BIT(20); + ddesc->st |= BIT(21); + ddesc->st |= BIT(22); + ddesc->st |= BIT(23); + ddesc->st |= BIT(24); + ddesc->st |= BIT(25); + ddesc->st |= BIT(26); + 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_INT_STA); + writel(v & 0x3FFF, priv->base + SUN8I_EMAC_INT_STA); +*/ + 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; +} + +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); + + dev_info(priv->dev, "%s\n", __func__); + /* TODO netdev_for_each_mc_addr and IFF_PROMISC */ +} + +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); + int ret = 0; + + dev_info(priv->dev, "%s %x\n", __func__, cmd); + + if (!netif_running(ndev)) + return -EINVAL; + + switch (cmd) { + case SIOCGMIIPHY: + case SIOCGMIIREG: + case SIOCSMIIREG: + if (!ndev->phydev) + return -EINVAL; + ret = phy_mii_ioctl(ndev->phydev, rq, cmd); + break; + case SIOCSHWTSTAMP: + /*ret = stmmac_hwtstamp_ioctl(dev, rq);*/ + return -EINVAL; + default: + break; + } + + return ret; +} + +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 = NULL, + .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); + + 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->busclk = devm_clk_get(&pdev->dev, "ahb2_gmac"); + if (IS_ERR(priv->busclk)) { + ret = PTR_ERR(priv->busclk); + dev_err(&pdev->dev, "Cannot get AHB clock err=%d\n", ret); + return ret; + } + ret = clk_prepare_enable(priv->busclk); + if (ret != 0) { + dev_err(&pdev->dev, "Cannot prepare_enable busclk\n"); + return ret; + } +/* + cr = clk_get_rate(priv->miiclk); + dev_info(&pdev->dev, "Current MII clkrate %lu\n", cr); + + ret = clk_set_rate(priv->miiclk, cr / 4); + if (ret) + dev_err(&pdev->dev, "Cannot change ephy clock speed %d", ret); + ret = clk_set_rate(priv->busclk, 125 * 1000 * 1000); + if (ret) + dev_err(&pdev->dev, "Cannot change emac clock speed %d", ret); +*/ + priv->rst_mac = devm_reset_control_get_optional(&pdev->dev, "ahb"); + if (IS_ERR(priv->rst_mac)) { + ret = PTR_ERR(priv->rst_mac); + dev_info(&pdev->dev, "no mac reset control found %d\n", ret); + priv->rst_mac = NULL; + } + if (priv->rst_mac) { + ret = reset_control_deassert(priv->rst_mac); + if (ret) + dev_info(&pdev->dev, "Cannot deassert mac\n"); + } + + spin_lock_init(&priv->lock); + spin_lock_init(&priv->tx_lock); + + 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->busclk); + clk_disable_unprepare(priv->miiclk); + if (priv->rst_mac) + reset_control_assert(priv->rst_mac); + if (priv->rst_phy) + reset_control_assert(priv->rst_phy); + + 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.montj...@gmail.co"); -- 2.4.10 -- You received this message because you are subscribed to the Google Groups "linux-sunxi" group. To unsubscribe from this group and stop receiving emails from it, send an email to linux-sunxi+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.