mirror of
https://github.com/Fishwaldo/build.git
synced 2025-03-26 16:51:48 +00:00
1622 lines
42 KiB
Diff
1622 lines
42 KiB
Diff
From c9869459825598ad1597ea263514d0fe6a8d9695 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 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 <clabbe.montjoie@gmail.com>
|
|
---
|
|
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 <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;
|
|
+} __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 <clabbe.montjoie@gmail.co");
|
|
--
|
|
2.4.10
|
|
|