This patch add support for sun8i-emac ethernet MAC hardware. It could be found in Allwinner H3/A83T/A64 SoCs. It supports 10/100/1000 Mbit/s speed with half/full duplex. It can use an internal PHY (MII 10/100) or an external PHY via RGMII/RMII. Signed-off-by: LABBE Corentin <clabbe.montjoie@xxxxxxxxx> --- drivers/net/ethernet/allwinner/Kconfig | 13 + drivers/net/ethernet/allwinner/Makefile | 1 + drivers/net/ethernet/allwinner/sun8i-emac.c | 1943 +++++++++++++++++++++++++++ 3 files changed, 1957 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..226499d 100644 --- a/drivers/net/ethernet/allwinner/Kconfig +++ b/drivers/net/ethernet/allwinner/Kconfig @@ -33,4 +33,17 @@ config SUN4I_EMAC To compile this driver as a module, choose M here. The module will be called sun4i-emac. +config SUN8I_EMAC + tristate "Allwinner sun8i EMAC support" + depends on ARCH_SUNXI || COMPILE_TEST + depends on OF + select MII + select PHYLIB + ---help--- + This driver support the sun8i EMAC ethernet driver present on + H3/A83T/A64 Allwinner SoCs. + + 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..179aa61 --- /dev/null +++ b/drivers/net/ethernet/allwinner/sun8i-emac.c @@ -0,0 +1,1943 @@ +/* + * sun8i-emac driver + * + * Copyright (C) 2015-2016 Corentin LABBE <clabbe.montjoie@xxxxxxxxx> + * + * This is the driver for Allwinner Ethernet MAC found in H3/A83T/A64 SoC + * + * TODO: + * - NAPI + * - MAC filtering + * - Jumbo frame + * - features rx-all (NETIF_F_RXALL_BIT) + */ +#include <linux/bitops.h> +#include <linux/clk.h> +#include <linux/dma-mapping.h> +#include <linux/etherdevice.h> +#include <linux/interrupt.h> +#include <linux/iopoll.h> +#include <linux/mii.h> +#include <linux/module.h> +#include <linux/netdevice.h> +#include <linux/of_device.h> +#include <linux/of_mdio.h> +#include <linux/of_net.h> +#include <linux/phy.h> +#include <linux/pinctrl/consumer.h> +#include <linux/pinctrl/pinctrl.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/reset.h> +#include <linux/scatterlist.h> +#include <linux/skbuff.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_DESC 0xB4 +#define SUN8I_EMAC_TX_CUR_BUF 0xB8 +#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_EMAC_TX_DO_CRC (BIT(27) | BIT(28)) +#define SUN8I_EMAC_RX_DO_CRC BIT(27) +#define SUN8I_EMAC_RX_STRIP_FCS BIT(28) + +#define SUN8I_COULD_BE_USED_BY_DMA BIT(31) + +#define FLOW_RX 1 +#define FLOW_TX 2 + +/* describe how data from skb are DMA mapped */ +#define MAP_SINGLE 1 +#define MAP_PAGE 2 + +enum emac_variant { + A83T_EMAC, + H3_EMAC, + A64_EMAC, +}; + +struct ethtool_str { + char name[ETH_GSTRING_LEN]; +}; + +static const struct ethtool_str estats_str[] = { + /* errors */ + { "rx_payload_error" }, + { "rx_CRC_error" }, + { "rx_phy_error" }, + { "rx_length_error" }, + { "rx_col_error" }, + { "rx_header_error" }, + { "rx_overflow_error" }, + { "rx_saf_error" }, + { "rx_daf_error" }, + { "rx_buf_error" }, + /* misc infos */ + { "tx_stop_queue" }, + { "rx_dma_ua" }, + { "rx_dma_stop" }, + { "tx_dma_ua" }, + { "tx_dma_stop" }, + { "rx_hw_csum" }, + { "tx_hw_csum" }, + /* interrupts */ + { "rx_early_int" }, + { "tx_early_int" }, + { "tx_underflow_int" }, + /* debug */ + { "tx_used_desc" }, +}; + +struct sun8i_emac_stats { + u64 rx_payload_error; + u64 rx_crc_error; + u64 rx_phy_error; + u64 rx_length_error; + u64 rx_col_error; + u64 rx_header_error; + u64 rx_overflow_error; + u64 rx_saf_fail; + u64 rx_daf_fail; + u64 rx_buf_error; + + u64 tx_stop_queue; + u64 rx_dma_ua; + u64 rx_dma_stop; + u64 tx_dma_ua; + u64 tx_dma_stop; + u64 rx_hw_csum; + u64 tx_hw_csum; + + u64 rx_early_int; + u64 tx_early_int; + u64 tx_underflow_int; + u64 tx_used_desc; +}; + +/* The datasheet said that each descriptor can transfers up to 4096bytes + * But latter, a register documentation reduce that value to 2048 + * Anyway using 2048 cause strange behaviours and even BSP driver use 2047 + */ +#define DESC_BUF_MAX 2044 +#if (DESC_BUF_MAX < (ETH_FRAME_LEN + 4)) +#error "DESC_BUF_MAX must be set at minimum to ETH_FRAME_LEN + 4" +#endif + +/* MAGIC value for knowing if a descriptor is available or not */ +#define DCLEAN (BIT(16) | BIT(14) | BIT(12) | BIT(10) | BIT(9)) + +/* Structure of DMA descriptor used by the hardware */ +struct dma_desc { + u32 status; /* status of the descriptor */ + u32 st; /* Information on the frame */ + u32 buf_addr; /* physical address of the frame data */ + u32 next; /* physical address of next dma_desc */ +} __packed __aligned(4); + +/* Benched on OPIPC with 100M, setting more than 256 does not give any + * perf boost + */ +static int nbdesc_tx = 256; +module_param(nbdesc_tx, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(nbdesc_tx, "Number of descriptors in the TX list"); +static int nbdesc_rx = 128; +module_param(nbdesc_rx, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(nbdesc_rx, "Number of descriptors in the RX list"); + +struct sun8i_emac_priv { + void __iomem *base; + void __iomem *syscon; + int irq; + struct device *dev; + struct net_device *ndev; + struct mii_bus *mdio; + spinlock_t lock;/* for adjust_link */ + spinlock_t tx_lock;/* control the access of transmit descriptors */ + int duplex; + int speed; + int link; + int phy_interface; + enum emac_variant variant; + struct device_node *phy_node; + struct clk *ahb_clk; + struct clk *ephy_clk; + bool use_internal_phy; + + struct reset_control *rst; + struct reset_control *rst_ephy; + + struct dma_desc *dd_rx __aligned(4); + dma_addr_t dd_rx_phy __aligned(4); + struct dma_desc *dd_tx __aligned(4); + dma_addr_t dd_tx_phy __aligned(4); + struct sk_buff **rx_sk; + struct sk_buff **tx_sk; + int *tx_map; + + int tx_slot; + int tx_dirty; + int rx_dirty; + struct sun8i_emac_stats estats; + u32 msg_enable; + int flow_ctrl; + int pause; +}; + +static void rb_inc(int *p, const int max) +{ + (*p)++; + if (*p >= max) + *p = 0; +} + +/* Return the number of contiguous free descriptors + * starting from tx_slot + */ +static int rb_tx_numfreedesc(struct net_device *ndev) +{ + struct sun8i_emac_priv *priv = netdev_priv(ndev); + + if (priv->tx_slot < priv->tx_dirty) + return priv->tx_dirty - priv->tx_slot; + + return (nbdesc_tx - priv->tx_slot) + priv->tx_dirty; +} + +/* Allocate a skb 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, DESC_BUF_MAX); + if (!sk) + return -ENOMEM; + + /* should not happen */ + if (unlikely(priv->rx_sk[i])) + dev_warn(priv->dev, "BUG: Leaking a skbuff\n"); + + priv->rx_sk[i] = sk; + + ddesc->buf_addr = dma_map_single(priv->dev, sk->data, + DESC_BUF_MAX, DMA_FROM_DEVICE); + if (dma_mapping_error(priv->dev, ddesc->buf_addr)) { + dev_err(priv->dev, "ERROR: Cannot dma_map RX buffer\n"); + dev_kfree_skb(sk); + return -EFAULT; + } + ddesc->st |= DESC_BUF_MAX; + ddesc->status = BIT(31); + + return 0; +} + +/* Set MAC address for slot index + * @addr: the MAC address to set + * @index: The index of slot where to set address. + * The slot 0 is the main MACaddr + */ +static void sun8i_emac_set_macaddr(struct sun8i_emac_priv *priv, + const u8 *addr, int index) +{ + u32 v; + + dev_info(priv->dev, "device MAC address slot %d %02x:%02x:%02x:%02x:%02x:%02x\n", + 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); + v = (addr[3] << 24) | (addr[2] << 16) | (addr[1] << 8) | addr[0]; + writel(v, priv->base + SUN8I_EMAC_MACADDR_LO + index * 8); +} + +static void sun8i_emac_set_link_mode(struct sun8i_emac_priv *priv) +{ + u32 v; + + v = readl(priv->base + SUN8I_EMAC_BASIC_CTL0); + + if (priv->duplex) + v |= BIT(0); + else + v &= ~BIT(0); + + v &= ~0x0C; + switch (priv->speed) { + case 1000: + break; + case 100: + v |= BIT(2); + v |= BIT(3); + break; + case 10: + v |= BIT(3); + break; + } + + writel(v, priv->base + SUN8I_EMAC_BASIC_CTL0); +} + +static void sun8i_emac_flow_ctrl(struct sun8i_emac_priv *priv, int duplex, + int fc, int pause) +{ + u32 flow = 0; + + netif_dbg(priv, link, priv->ndev, "%s %d %d %d\n", __func__, + duplex, fc, pause); + + flow = readl(priv->base + SUN8I_EMAC_RX_CTL0); + if (fc & FLOW_RX) + flow |= BIT(16); + else + flow &= ~BIT(16); + writel(flow, priv->base + SUN8I_EMAC_RX_CTL0); + + flow = readl(priv->base + SUN8I_EMAC_TX_FLOW_CTL); + if (fc & FLOW_TX) + flow |= BIT(0); + else + flow &= ~BIT(0); + writel(flow, priv->base + SUN8I_EMAC_TX_FLOW_CTL); +} + +/* Grab a frame into a skb from descriptor number i */ +static int sun8i_emac_rx_from_ddesc(struct net_device *ndev, int i) +{ + struct sk_buff *skb; + struct sun8i_emac_priv *priv = netdev_priv(ndev); + struct dma_desc *ddesc = priv->dd_rx + i; + int frame_len; + int crc_checked = 0; + + if (ndev->features & NETIF_F_RXCSUM) + crc_checked = 1; + + /* bit0/bit7 work only on IPv4/IPv6 TCP traffic, + * (not on ARP for example) so we dont raise rx_errors/discard frame + */ + /* the checksum or length of received frame's payload is wrong*/ + if (ddesc->status & BIT(0)) { + priv->estats.rx_payload_error++; + crc_checked = 0; + } + if (ddesc->status & BIT(1)) { + priv->ndev->stats.rx_errors++; + priv->ndev->stats.rx_crc_errors++; + priv->estats.rx_crc_error++; + goto discard_frame; + } + if ((ddesc->status & BIT(3))) { + priv->ndev->stats.rx_errors++; + priv->estats.rx_phy_error++; + goto discard_frame; + } + if ((ddesc->status & BIT(4))) { + priv->ndev->stats.rx_errors++; + priv->ndev->stats.rx_length_errors++; + priv->estats.rx_length_error++; + goto discard_frame; + } + if ((ddesc->status & BIT(6))) { + priv->ndev->stats.rx_errors++; + priv->estats.rx_col_error++; + goto discard_frame; + } + if ((ddesc->status & BIT(7))) { + priv->estats.rx_header_error++; + crc_checked = 0; + } + if ((ddesc->status & BIT(11))) { + priv->ndev->stats.rx_over_errors++; + priv->estats.rx_overflow_error++; + goto discard_frame; + } + if ((ddesc->status & BIT(14))) { + priv->ndev->stats.rx_errors++; + priv->estats.rx_buf_error++; + goto discard_frame; + } + + if ((ddesc->status & BIT(9)) == 0) { + /* begin of a Jumbo frame */ + dev_warn(priv->dev, "This should not happen\n"); + goto discard_frame; + } + frame_len = (ddesc->status >> 16) & 0x3FFF; + if (!(ndev->features & NETIF_F_RXFCS)) + frame_len -= ETH_FCS_LEN; + + skb = priv->rx_sk[i]; + + netif_dbg(priv, rx_status, priv->ndev, + "%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, DESC_BUF_MAX, + DMA_FROM_DEVICE); + skb->protocol = eth_type_trans(skb, priv->ndev); + if (crc_checked) { + skb->ip_summed = CHECKSUM_UNNECESSARY; + priv->estats.rx_hw_csum++; + } else { + skb->ip_summed = CHECKSUM_PARTIAL; + } + skb->dev = priv->ndev; + + priv->ndev->stats.rx_packets++; + priv->ndev->stats.rx_bytes += frame_len; + priv->rx_sk[i] = NULL; + + /* this frame is not the last */ + if ((ddesc->status & BIT(8)) == 0) { + dev_warn(priv->dev, "Multi frame not implemented currlen=%d\n", + frame_len); + } + + sun8i_emac_rx_sk(ndev, i); + + netif_rx(skb); + + return 0; + /* If the frame need to be dropped, we simply reuse the buffer */ +discard_frame: + ddesc->st = DESC_BUF_MAX; + ddesc->status = BIT(31); + return 0; +} + +/* Cycle over RX DMA descriptors for finding frame to receive + */ +static int sun8i_emac_receive_all(struct net_device *ndev) +{ + struct sun8i_emac_priv *priv = netdev_priv(ndev); + struct dma_desc *ddesc; + + ddesc = priv->dd_rx + priv->rx_dirty; + while (!(ddesc->status & BIT(31))) { + sun8i_emac_rx_from_ddesc(ndev, priv->rx_dirty); + rb_inc(&priv->rx_dirty, nbdesc_rx); + ddesc = priv->dd_rx + priv->rx_dirty; + }; + + return 0; +} + +/* iterate over dma_desc for finding completed xmit. + * Called from interrupt context, so no need to spinlock tx + * + * The problem is: how to know that a descriptor is sent and not just in + * preparation. + * Need to have status=0 and st set but this is the state of first frame just + * before setting the own-by-DMA bit. + * The solution is to used the artificial value DCLEAN. + */ +static int sun8i_emac_complete_xmit(struct net_device *ndev) +{ + struct sun8i_emac_priv *priv = netdev_priv(ndev); + struct dma_desc *ddesc; + int frame_len; + + do { + ddesc = priv->dd_tx + priv->tx_dirty; + + if (ddesc->status & BIT(31)) { + dev_info(priv->dev, "BUG: DMA still set %d %d\n", + priv->tx_dirty, priv->tx_slot); + return 0; + } + + if (ddesc->status == DCLEAN) + return 0; + + if (ddesc->status == 0 && !ddesc->st) { + dev_info(priv->dev, "BUG: reached the void %d %d\n", + priv->tx_dirty, priv->tx_slot); + return 0; + } + + /* TX_UNDERFLOW_ERR */ + if (ddesc->status & BIT(1)) + priv->ndev->stats.tx_errors++; + /* TX_DEFER_ERR */ + if (ddesc->status & BIT(2)) + priv->ndev->stats.tx_errors++; + /* BIT 6:3 numbers of collisions */ + if (ddesc->status & 0x78) + priv->ndev->stats.collisions += + (ddesc->status & 0x78) >> 3; + /* TX_COL_ERR_1 */ + if (ddesc->status & BIT(8)) + priv->ndev->stats.tx_errors++; + /* TX_COL_ERR_0 */ + if (ddesc->status & BIT(9)) + priv->ndev->stats.tx_errors++; + /* TX_CRS_ERR */ + if (ddesc->status & BIT(10)) + priv->ndev->stats.tx_carrier_errors++; + /* TX_PAYLOAD_ERR */ + if (ddesc->status & BIT(12)) + priv->ndev->stats.tx_errors++; + /* TX_LENGTH_ERR */ + if (ddesc->status & BIT(14)) + priv->ndev->stats.tx_errors++; + /* TX_HEADER_ERR */ + if (ddesc->status & BIT(16)) + priv->ndev->stats.tx_errors++; + frame_len = ddesc->st & 0x3FFF; + if (priv->tx_map[priv->tx_dirty] == MAP_SINGLE) + dma_unmap_single(priv->dev, ddesc->buf_addr, + frame_len, DMA_TO_DEVICE); + else + dma_unmap_page(priv->dev, ddesc->buf_addr, + frame_len, DMA_TO_DEVICE); + /* we can free skb only on last frame */ + if (priv->tx_sk[priv->tx_dirty] && (ddesc->st & BIT(30))) + dev_kfree_skb_irq(priv->tx_sk[priv->tx_dirty]); + + priv->tx_sk[priv->tx_dirty] = NULL; + priv->tx_map[priv->tx_dirty] = 0; + ddesc->status = DCLEAN; + ddesc->st = 0; + + rb_inc(&priv->tx_dirty, nbdesc_tx); + ddesc = priv->dd_tx + priv->tx_dirty; + } while (ddesc->st && !(ddesc->status & BIT(31))); + + if (netif_queue_stopped(ndev) && + rb_tx_numfreedesc(ndev) > MAX_SKB_FRAGS + 1) + netif_wake_queue(ndev); + + return 0; +} + +static int sun8i_mdio_read(struct mii_bus *bus, int phy_addr, int phy_reg) +{ + struct net_device *ndev = bus->priv; + struct sun8i_emac_priv *priv = netdev_priv(ndev); + int err; + u32 reg; + + err = readl_poll_timeout(priv->base + SUN8I_EMAC_MDIO_CMD, reg, + !(reg & MDIO_CMD_MII_BUSY), 100, 10000); + if (err) { + dev_err(priv->dev, "%s timeout %x\n", __func__, reg); + return err; + } + + reg &= ~MDIO_CMD_MII_WRITE; + reg &= ~MDIO_CMD_MII_PHY_REG_ADDR_MASK; + reg |= (phy_reg << MDIO_CMD_MII_PHY_REG_ADDR_SHIFT) & + MDIO_CMD_MII_PHY_REG_ADDR_MASK; + + reg &= ~MDIO_CMD_MII_PHY_ADDR_MASK; + + reg |= (phy_addr << MDIO_CMD_MII_PHY_ADDR_SHIFT) & + MDIO_CMD_MII_PHY_ADDR_MASK; + + reg |= MDIO_CMD_MII_BUSY; + + writel(reg, priv->base + SUN8I_EMAC_MDIO_CMD); + + err = readl_poll_timeout(priv->base + SUN8I_EMAC_MDIO_CMD, reg, + !(reg & MDIO_CMD_MII_BUSY), 100, 10000); + + if (err) { + dev_err(priv->dev, "%s timeout %x\n", __func__, reg); + return err; + } + + return readl(priv->base + SUN8I_EMAC_MDIO_DATA); +} + +static int sun8i_mdio_write(struct mii_bus *bus, int phy_addr, int phy_reg, + u16 data) +{ + struct net_device *ndev = bus->priv; + struct sun8i_emac_priv *priv = netdev_priv(ndev); + u32 reg; + int err; + + err = readl_poll_timeout(priv->base + SUN8I_EMAC_MDIO_CMD, reg, + !(reg & MDIO_CMD_MII_BUSY), 100, 10000); + if (err) { + dev_err(priv->dev, "%s timeout %x\n", __func__, reg); + return err; + } + + reg &= ~MDIO_CMD_MII_PHY_REG_ADDR_MASK; + reg |= (phy_reg << MDIO_CMD_MII_PHY_REG_ADDR_SHIFT) & + MDIO_CMD_MII_PHY_REG_ADDR_MASK; + + reg &= ~MDIO_CMD_MII_PHY_ADDR_MASK; + reg |= (phy_addr << MDIO_CMD_MII_PHY_ADDR_SHIFT) & + MDIO_CMD_MII_PHY_ADDR_MASK; + + reg |= MDIO_CMD_MII_WRITE; + reg |= MDIO_CMD_MII_BUSY; + + writel(reg, priv->base + SUN8I_EMAC_MDIO_CMD); + writel(data, priv->base + SUN8I_EMAC_MDIO_DATA); + dev_dbg(priv->dev, "%s %d %d %x %x\n", __func__, phy_addr, phy_reg, + reg, data); + + err = readl_poll_timeout(priv->base + SUN8I_EMAC_MDIO_CMD, reg, + !(reg & MDIO_CMD_MII_BUSY), 100, 10000); + if (err) { + dev_err(priv->dev, "%s timeout %x\n", __func__, reg); + return err; + } + + return 0; +} + +static int sun8i_emac_mdio_register(struct net_device *ndev) +{ + struct sun8i_emac_priv *priv = netdev_priv(ndev); + struct mii_bus *bus; + int ret; + + bus = devm_mdiobus_alloc(priv->dev); + if (!bus) { + netdev_err(ndev, "Failed to allocate 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; +} + +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; + + netif_dbg(priv, link, priv->ndev, + "%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, + priv->flow_ctrl, priv->pause); + + if (phydev->speed != priv->speed) { + new_state = 1; + priv->speed = phydev->speed; + } + + if (priv->link == 0) { + new_state = 1; + priv->link = phydev->link; + } + + netif_dbg(priv, link, priv->ndev, + "%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); +} + +/* H3 specific bits for EPHY */ +#define H3_EPHY_ADDR_SHIFT 20 +#define H3_EPHY_LED_POL BIT(17) /* 1: active low, 0: active high */ +#define H3_EPHY_SHUTDOWN BIT(16) /* 1: shutdown, 0: power up */ +#define H3_EPHY_SELECT BIT(15) /* 1: internal PHY, 0: external PHY */ +#define H3_EPHY_DEFAULT_VALUE 0x58000 +#define H3_EPHY_DEFAULT_MASK GENMASK(31, 15) + +/* H3/A64 specific bits */ +#define SC_RMII_EN BIT(13) /* 1: enable RMII (overrides EPIT) */ + +/* Generic system control EMAC_CLK bits */ +#define SC_ETXDC_MASK GENMASK(2, 0) +#define SC_ETXDC_SHIFT 10 +#define SC_ERXDC_MASK GENMASK(4, 0) +#define SC_ERXDC_SHIFT 5 +#define SC_EPIT BIT(2) /* 1: RGMII, 0: MII */ +#define SC_ETCS_MASK GENMASK(1, 0) +#define SC_ETCS_MII 0x0 +#define SC_ETCS_EXT_GMII 0x1 +#define SC_ETCS_INT_GMII 0x2 + +static int sun8i_emac_set_syscon_ephy(struct net_device *ndev, u32 *reg) +{ + struct sun8i_emac_priv *priv = netdev_priv(ndev); + struct device_node *node = priv->dev->of_node; + int ret; + + *reg &= ~H3_EPHY_DEFAULT_MASK; + *reg |= H3_EPHY_DEFAULT_VALUE; + + if (!priv->use_internal_phy) { + /* switch to external PHY interface */ + *reg &= ~H3_EPHY_SELECT; + return 0; + } + + if (priv->phy_interface != PHY_INTERFACE_MODE_MII) { + netdev_warn(ndev, + "Internal PHY requested, forcing MII mode.\n"); + priv->phy_interface = PHY_INTERFACE_MODE_MII; + } + + *reg |= H3_EPHY_SELECT; + *reg &= ~H3_EPHY_SHUTDOWN; + + if (of_property_read_bool(node, "allwinner,leds-active-low")) + *reg |= H3_EPHY_LED_POL; + + ret = of_mdio_parse_addr(priv->dev, priv->phy_node); + if (ret < 0) + return ret; + + /* of_mdio_parse_addr returns a valid (0 ~ 31) PHY + * address. No need to mask it again. + */ + *reg |= ret << H3_EPHY_ADDR_SHIFT; + + return 0; +} + +static int sun8i_emac_set_syscon(struct net_device *ndev) +{ + struct sun8i_emac_priv *priv = netdev_priv(ndev); + struct device_node *node = priv->dev->of_node; + int ret; + u32 reg, val; + + reg = readl(priv->syscon); + + if (priv->variant == H3_EMAC) { + ret = sun8i_emac_set_syscon_ephy(ndev, ®); + if (ret) + return ret; + } + + if (!of_property_read_u32(node, "allwinner,tx-delay", &val)) { + if (val <= SC_ETXDC_MASK) { + reg &= ~(SC_ETXDC_MASK << SC_ETXDC_SHIFT); + reg |= (val << SC_ETXDC_SHIFT); + } else { + netdev_warn(ndev, "invalid TX clock delay: %d\n", val); + } + } + + if (!of_property_read_u32(node, "allwinner,rx-delay", &val)) { + if (val <= SC_ERXDC_MASK) { + reg &= ~(SC_ERXDC_MASK << SC_ERXDC_SHIFT); + reg |= (val << SC_ERXDC_SHIFT); + } else { + netdev_warn(ndev, "invalid RX clock delay: %d\n", val); + } + } + + /* Clear interface mode bits */ + reg &= ~(SC_ETCS_MASK | SC_EPIT); + if (priv->variant == H3_EMAC || priv->variant == A64_EMAC) + reg &= ~SC_RMII_EN; + + switch (priv->phy_interface) { + case PHY_INTERFACE_MODE_MII: + /* default */ + break; + case PHY_INTERFACE_MODE_RGMII: + reg |= SC_EPIT | SC_ETCS_INT_GMII; + break; + case PHY_INTERFACE_MODE_RMII: + if (priv->variant == H3_EMAC || priv->variant == A64_EMAC) { + reg |= SC_RMII_EN | SC_ETCS_EXT_GMII; + break; + } + /* RMII not supported on A83T */ + default: + netdev_err(ndev, "unsupported interface mode: %s", + phy_modes(priv->phy_interface)); + return -EINVAL; + } + + writel(reg, priv->syscon); + + return 0; +} + +static void sun8i_emac_unset_syscon(struct net_device *ndev) +{ + struct sun8i_emac_priv *priv = netdev_priv(ndev); + u32 reg = 0; + + if (priv->variant == H3_EMAC) + reg = H3_EPHY_DEFAULT_VALUE; + + writel(reg, priv->syscon); +} + +static void sun8i_emac_set_mdc(struct net_device *ndev) +{ + struct sun8i_emac_priv *priv = netdev_priv(ndev); + unsigned long rate; + u32 reg; + + 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 */ + netif_dbg(priv, link, ndev, "MDC auto : %x\n", reg); + writel(reg, priv->base + SUN8I_EMAC_MDIO_CMD); +} + +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; + const u8 *addr; + int ret; + + /* Try to get MAC address from DT, or assign a random one */ + addr = of_get_mac_address(node); + if (addr) + ether_addr_copy(ndev->dev_addr, addr); + else + eth_hw_addr_random(ndev); + + priv->phy_node = of_parse_phandle(node, "phy", 0); + if (!priv->phy_node) { + netdev_err(ndev, "no associated PHY\n"); + return -ENODEV; + } + + priv->phy_interface = of_get_phy_mode(node); + if (priv->phy_interface < 0) { + netdev_err(ndev, "PHY interface mode node unspecified\n"); + return priv->phy_interface; + } + + /* Set interface mode (and configure internal PHY on H3) */ + ret = sun8i_emac_set_syscon(ndev); + if (ret) + return ret; + + ret = clk_prepare_enable(priv->ahb_clk); + if (ret) { + netdev_err(ndev, "Could not enable ahb clock\n"); + goto err_clk; + } + + if (priv->rst) { + ret = reset_control_deassert(priv->rst); + if (ret) { + netdev_err(ndev, "Could not deassert reset\n"); + goto err_reset; + } + } + + if (priv->ephy_clk) { + ret = clk_prepare_enable(priv->ephy_clk); + if (ret) { + netdev_err(ndev, "Could not enable EPHY clock\n"); + goto err_ephy_clk; + } + } + + if (priv->rst_ephy) { + ret = reset_control_deassert(priv->rst_ephy); + if (ret) { + netdev_err(ndev, "Could not deassert EPHY reset\n"); + goto err_ephy_reset; + } + } + + sun8i_emac_set_mdc(ndev); + + ret = sun8i_emac_mdio_register(ndev); + if (ret) + goto err_mdio_register; + + return 0; + +err_mdio_register: + if (priv->rst_ephy) + reset_control_assert(priv->rst_ephy); +err_ephy_reset: + if (priv->ephy_clk) + clk_disable_unprepare(priv->ephy_clk); +err_ephy_clk: + if (priv->rst) + reset_control_assert(priv->rst); +err_reset: + clk_disable_unprepare(priv->ahb_clk); +err_clk: + sun8i_emac_unset_syscon(ndev); + 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_ephy) + reset_control_assert(priv->rst_ephy); + + if (priv->ephy_clk) + clk_disable_unprepare(priv->ephy_clk); + + if (priv->rst) + reset_control_assert(priv->rst); + + clk_disable_unprepare(priv->ahb_clk); + + sun8i_emac_unset_syscon(ndev); +} + +static int sun8i_emac_mdio_probe(struct net_device *ndev) +{ + struct sun8i_emac_priv *priv = netdev_priv(ndev); + struct phy_device *phydev = NULL; + + phydev = of_phy_connect(ndev, priv->phy_node, &sun8i_emac_adjust_link, + 0, priv->phy_interface); + + if (!phydev) { + netdev_err(ndev, "Could not attach to PHY\n"); + return -ENODEV; + } + + phy_attached_info(phydev); + + /* mask with MAC supported features */ + phydev->supported &= PHY_GBIT_FEATURES; + phydev->advertising = phydev->supported; + + priv->link = 0; + priv->speed = 0; + priv->duplex = -1; + + return 0; +} + +static int sun8i_emac_open(struct net_device *ndev) +{ + struct sun8i_emac_priv *priv = netdev_priv(ndev); + int err; + u32 v; + struct dma_desc *ddesc; + int i; + + if (nbdesc_tx < MAX_SKB_FRAGS + 1) { + dev_err(priv->dev, "The number of TX descriptors is too low"); + return -EINVAL; + } + + 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); + + err = readl_poll_timeout(priv->base + SUN8I_EMAC_BASIC_CTL1, v, + !(v & 0x01), 100, 10000); + if (err) { + dev_err(priv->dev, "EMAC reset timeout\n"); + err = -EFAULT; + goto err_emac_timeout; + } + + sun8i_emac_set_mdc(ndev); + + /* DMA */ + v = (8 << 24);/* burst len */ + writel(v, priv->base + SUN8I_EMAC_BASIC_CTL1); + + /* it seems that hardware complety ignore interrupt configuration */ +#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_RX_CTL0); + /* CHECK_CRC */ + if (ndev->features & NETIF_F_RXCSUM) + v |= SUN8I_EMAC_RX_DO_CRC; + else + v &= ~SUN8I_EMAC_RX_DO_CRC; + /* STRIP_FCS */ + if (ndev->features & NETIF_F_RXFCS) + v &= ~SUN8I_EMAC_RX_STRIP_FCS; + else + v |= SUN8I_EMAC_RX_STRIP_FCS; + writel(v, priv->base + SUN8I_EMAC_RX_CTL0); + + v = readl(priv->base + SUN8I_EMAC_TX_CTL1); + /* TX_MD Transmission starts after a full frame located in TX DMA FIFO*/ + v |= BIT(1); + writel(v, priv->base + SUN8I_EMAC_TX_CTL1); + + v = readl(priv->base + SUN8I_EMAC_RX_CTL1); + /* RX_MD RX DMA reads data from RX DMA FIFO to host memory after a + * complete frame has been written to RX DMA FIFO + */ + v |= BIT(1); + writel(v, priv->base + SUN8I_EMAC_RX_CTL1); + + sun8i_emac_set_macaddr(priv, ndev->dev_addr, 0); + + priv->tx_slot = 0; + priv->tx_dirty = 0; + priv->rx_dirty = 0; + + priv->rx_sk = kcalloc(nbdesc_rx, sizeof(struct sk_buff *), GFP_KERNEL); + if (!priv->rx_sk) { + err = -ENOMEM; + goto rx_sk_error; + } + priv->tx_sk = kcalloc(nbdesc_tx, sizeof(struct sk_buff *), GFP_KERNEL); + if (!priv->tx_sk) { + err = -ENOMEM; + goto tx_sk_error; + } + priv->tx_map = kcalloc(nbdesc_tx, sizeof(int), GFP_KERNEL); + if (!priv->tx_map) { + err = -ENOMEM; + goto tx_map_error; + } + + priv->dd_rx = dma_alloc_coherent(priv->dev, + nbdesc_rx * 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_rx * sizeof(struct dma_desc)); + ddesc = priv->dd_rx; + for (i = 0; i < nbdesc_rx; i++) { + sun8i_emac_rx_sk(ndev, i); + ddesc->next = (u32)priv->dd_rx_phy + (i + 1) + * sizeof(struct dma_desc); + ddesc++; + } + /* last descriptor point back to first one */ + ddesc--; + ddesc->next = (u32)priv->dd_rx_phy; + + priv->dd_tx = dma_alloc_coherent(priv->dev, + nbdesc_tx * 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_tx * sizeof(struct dma_desc)); + ddesc = priv->dd_tx; + for (i = 0; i < nbdesc_tx; i++) { + ddesc->status = DCLEAN; + ddesc->st = 0; + ddesc->next = (u32)(priv->dd_tx_phy + (i + 1) + * sizeof(struct dma_desc)); + ddesc++; + } + /* last descriptor point back to first one */ + ddesc--; + ddesc->next = (u32)priv->dd_tx_phy; + i--; + + if (ndev->phydev) + phy_start(ndev->phydev); + + /* write start of rx ring descriptor */ + writel(priv->dd_rx_phy, priv->base + SUN8I_EMAC_RX_DESC_LIST); + /* start RX DMA */ + v = readl(priv->base + SUN8I_EMAC_RX_CTL1); + v |= BIT(30); + writel(v, priv->base + SUN8I_EMAC_RX_CTL1); + + /* write start of tx ring descriptor */ + writel(priv->dd_tx_phy, priv->base + SUN8I_EMAC_TX_DESC_LIST); + /* start TX DMA */ + v = readl(priv->base + SUN8I_EMAC_TX_CTL1); + v |= BIT(30); + writel(v, priv->base + SUN8I_EMAC_TX_CTL1); + + /* activate transmitter */ + v = readl(priv->base + SUN8I_EMAC_TX_CTL0); + v |= BIT(31); + writel(v, priv->base + SUN8I_EMAC_TX_CTL0); + + /* activate receiver */ + v = readl(priv->base + SUN8I_EMAC_RX_CTL0); + v |= BIT(31); + writel(v, priv->base + SUN8I_EMAC_RX_CTL0); + + netif_start_queue(ndev); + + return 0; +dma_tx_error: + dma_free_coherent(priv->dev, nbdesc_rx * sizeof(struct dma_desc), + priv->dd_rx, priv->dd_rx_phy); +dma_rx_error: + kfree(priv->tx_map); +tx_map_error: + kfree(priv->tx_sk); +tx_sk_error: + kfree(priv->rx_sk); +rx_sk_error: +err_emac_timeout: + phy_disconnect(ndev->phydev); + return err; +} + +/* Clean the tx ring of any accepted skb for xmit */ +static void sun8i_emac_tx_clean(struct net_device *ndev) +{ + struct sun8i_emac_priv *priv = netdev_priv(ndev); + int i; + struct dma_desc *ddesc; + int frame_len; + + spin_lock(&priv->tx_lock); + + for (i = 0; i < nbdesc_tx; i++) { + if (priv->tx_sk[i]) { + ddesc = priv->dd_tx + i; + frame_len = ddesc->st & 0x3FFF; + switch (priv->tx_map[i]) { + case MAP_SINGLE: + dma_unmap_single(priv->dev, ddesc->buf_addr, + frame_len, DMA_TO_DEVICE); + break; + case MAP_PAGE: + dma_unmap_page(priv->dev, ddesc->buf_addr, + frame_len, DMA_TO_DEVICE); + break; + default: + dev_err(priv->dev, "Trying to free an empty slot\n"); + continue; + } + dev_kfree_skb_any(priv->tx_sk[i]); + priv->tx_sk[i] = NULL; + ddesc->st = 0; + ddesc->status = DCLEAN; + } + } + priv->tx_slot = 0; + priv->tx_dirty = 0; + + spin_unlock(&priv->tx_lock); +} + +static int sun8i_emac_stop(struct net_device *ndev) +{ + struct sun8i_emac_priv *priv = netdev_priv(ndev); + int i; + struct dma_desc *ddesc; + + /* Stop receiver */ + writel(0, priv->base + SUN8I_EMAC_RX_CTL0); + writel(0, priv->base + SUN8I_EMAC_RX_CTL1); + /* Stop transmitter */ + writel(0, priv->base + SUN8I_EMAC_TX_CTL0); + writel(0, priv->base + SUN8I_EMAC_TX_CTL1); + + netif_stop_queue(ndev); + netif_carrier_off(ndev); + + phy_stop(ndev->phydev); + phy_disconnect(ndev->phydev); + + /* clean RX ring */ + for (i = 0; i < nbdesc_rx; i++) + if (priv->rx_sk[i]) { + ddesc = priv->dd_rx + i; + dma_unmap_single(priv->dev, ddesc->buf_addr, + DESC_BUF_MAX, DMA_FROM_DEVICE); + dev_kfree_skb_any(priv->rx_sk[i]); + priv->rx_sk[i] = NULL; + } + sun8i_emac_tx_clean(ndev); + + kfree(priv->rx_sk); + kfree(priv->tx_sk); + kfree(priv->tx_map); + + dma_free_coherent(priv->dev, nbdesc_rx * sizeof(struct dma_desc), + priv->dd_rx, priv->dd_rx_phy); + dma_free_coherent(priv->dev, nbdesc_tx * sizeof(struct dma_desc), + priv->dd_tx, priv->dd_tx_phy); + + 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; + struct dma_desc *first; + int i = 0, rbd_first; + unsigned int len, fraglen; + u32 v; + int n; + int nf; + const skb_frag_t *frag; + int do_csum = 0; + + len = skb_headlen(skb); + if (len < ETH_ZLEN) { + if (skb_padto(skb, ETH_ZLEN)) + return NETDEV_TX_OK; + len = ETH_ZLEN; + } + n = skb_shinfo(skb)->nr_frags; + + if (skb->ip_summed == CHECKSUM_PARTIAL) { + do_csum = 1; + priv->estats.tx_hw_csum++; + } + netif_dbg(priv, tx_queued, ndev, "%s len=%u skblen=%u %x\n", __func__, + len, skb->len, + (skb->ip_summed == CHECKSUM_PARTIAL)); + + spin_lock(&priv->tx_lock); + + /* check for contigous space + * We need at least 1(skb->data) + n(numfrags) + 1(one clean slot) + */ + if (rb_tx_numfreedesc(ndev) < n + 2) { + dev_err_ratelimited(priv->dev, "BUG!: TX is full %d %d\n", + priv->tx_dirty, priv->tx_slot); + netif_stop_queue(ndev); + spin_unlock(&priv->tx_lock); + return NETDEV_TX_BUSY; + } + i = priv->tx_slot; + + ddesc = priv->dd_tx + i; + first = priv->dd_tx + i; + rbd_first = i; + + priv->tx_slot = (i + 1 + n) % nbdesc_tx; + + 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"); + goto xmit_error; + } + priv->tx_map[i] = MAP_SINGLE; + priv->tx_sk[i] = skb; + priv->ndev->stats.tx_packets++; + priv->ndev->stats.tx_bytes += len; + + ddesc->st = len; + /* undocumented bit that make it works */ + ddesc->st |= BIT(24); + if (do_csum) + ddesc->st |= SUN8I_EMAC_TX_DO_CRC; + + /* handle fragmented skb, one descriptor per fragment */ + for (nf = 0; nf < n; nf++) { + frag = &skb_shinfo(skb)->frags[nf]; + rb_inc(&i, nbdesc_tx); + priv->tx_sk[i] = skb; + ddesc = priv->dd_tx + i; + fraglen = skb_frag_size(frag); + ddesc->st = fraglen; + priv->ndev->stats.tx_bytes += fraglen; + ddesc->st |= BIT(24); + if (do_csum) + ddesc->st |= SUN8I_EMAC_TX_DO_CRC; + + ddesc->buf_addr = skb_frag_dma_map(priv->dev, frag, 0, + fraglen, DMA_TO_DEVICE); + if (dma_mapping_error(priv->dev, ddesc->buf_addr)) { + dev_err(priv->dev, "DMA MAP ERROR\n"); + goto xmit_error; + } + priv->tx_map[i] = MAP_PAGE; + ddesc->status = BIT(31); + } + + /* frame end */ + ddesc->st |= BIT(30); + /* We want an interrupt after transmission */ + ddesc->st |= BIT(31); + + rb_inc(&i, nbdesc_tx); + + /* frame begin */ + first->st |= BIT(29); + first->status = BIT(31); + priv->tx_slot = i; + + /* Trying to optimize this (recording DMA start/stop) seems + * to lead to errors. So we always start DMA. + */ + v = readl(priv->base + SUN8I_EMAC_TX_CTL1); + /* TX DMA START */ + v |= BIT(31); + /* Start an run DMA */ + v |= BIT(30); + writel(v, priv->base + SUN8I_EMAC_TX_CTL1); + + if (rb_tx_numfreedesc(ndev) < MAX_SKB_FRAGS + 1) { + netif_stop_queue(ndev); + priv->estats.tx_stop_queue++; + } + priv->estats.tx_used_desc = rb_tx_numfreedesc(ndev); + + spin_unlock(&priv->tx_lock); + + return NETDEV_TX_OK; + +xmit_error: + /* destroy skb and return TX OK Documentation/DMA-API-HOWTO.txt */ + /* clean descritors from rbd_first to i */ + ddesc->st = 0; + ddesc->status = DCLEAN; + do { + ddesc = priv->dd_tx + rbd_first; + ddesc->st = 0; + ddesc->status = DCLEAN; + rb_inc(&rbd_first, nbdesc_tx); + } while (rbd_first != i); + spin_unlock(&priv->tx_lock); + dev_kfree_skb_any(skb); + return NETDEV_TX_OK; +} + +static int sun8i_emac_change_mtu(struct net_device *ndev, int new_mtu) +{ + struct sun8i_emac_priv *priv = netdev_priv(ndev); + int max_mtu; + + dev_info(priv->dev, "%s set MTU to %d\n", __func__, new_mtu); + + if (netif_running(ndev)) { + dev_err(priv->dev, "%s: must be stopped to change its MTU\n", + ndev->name); + return -EBUSY; + } + + max_mtu = SKB_MAX_HEAD(NET_SKB_PAD + NET_IP_ALIGN); + + if ((new_mtu < 68) || (new_mtu > max_mtu)) { + dev_err(priv->dev, "%s: invalid MTU, max MTU is: %d\n", + ndev->name, max_mtu); + return -EINVAL; + } + + ndev->mtu = new_mtu; + netdev_update_features(ndev); + return 0; +} + +static netdev_features_t sun8i_emac_fix_features(struct net_device *ndev, + netdev_features_t features) +{ + struct sun8i_emac_priv *priv = netdev_priv(ndev); + + netif_dbg(priv, drv, ndev, "%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)) { + netif_info(priv, hw, ndev, "Set loopback features"); + v |= BIT(1); + } else { + netif_info(priv, hw, ndev, "Unset loopback features"); + v &= ~BIT(1); + } + writel(v, priv->base + SUN8I_EMAC_BASIC_CTL0); + + v = readl(priv->base + SUN8I_EMAC_RX_CTL0); + if (features & NETIF_F_RXCSUM) { + v |= SUN8I_EMAC_RX_DO_CRC; + netif_info(priv, hw, ndev, "Doing RX CRC check by hardware"); + } else { + v &= ~SUN8I_EMAC_RX_DO_CRC; + netif_info(priv, hw, ndev, "No RX CRC check by hardware"); + } + if (features & NETIF_F_RXFCS) { + v &= ~SUN8I_EMAC_RX_STRIP_FCS; + netif_info(priv, hw, ndev, "Keep FCS"); + } else { + v |= SUN8I_EMAC_RX_STRIP_FCS; + netif_info(priv, hw, ndev, "Strip FCS"); + } + writel(v, priv->base + SUN8I_EMAC_RX_CTL0); + + netif_dbg(priv, drv, ndev, "%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; + + /* 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); + u32 v; + + dev_info(priv->dev, "%s\n", __func__); + netif_stop_queue(ndev); + + v = readl(priv->base + SUN8I_EMAC_TX_CTL0); + v &= ~BIT(31); + writel(v, priv->base + SUN8I_EMAC_TX_CTL0); + + v = readl(priv->base + SUN8I_EMAC_TX_CTL1); + v &= ~BIT(31); + v &= ~BIT(30); + writel(v, priv->base + SUN8I_EMAC_TX_CTL1); + + sun8i_emac_tx_clean(ndev); + + /* write start of tx ring descriptor */ + writel(priv->dd_tx_phy, priv->base + SUN8I_EMAC_TX_DESC_LIST); + + v = readl(priv->base + SUN8I_EMAC_TX_CTL0); + v |= BIT(31); + writel(v, priv->base + SUN8I_EMAC_TX_CTL0); + + v = readl(priv->base + SUN8I_EMAC_TX_CTL1); + v |= BIT(31); + v |= BIT(30); + writel(v, priv->base + SUN8I_EMAC_TX_CTL1); + + netdev_reset_queue(ndev); + + ndev->stats.tx_errors++; + netif_wake_queue(ndev); +} + +static int sun8i_emac_ioctl(struct net_device *ndev, struct ifreq *rq, int cmd) +{ + struct phy_device *phydev = ndev->phydev; + + if (!netif_running(ndev)) + return -EINVAL; + + if (!phydev) + return -ENODEV; + + return phy_mii_ioctl(phydev, rq, cmd); +} + +static int sun8i_emac_check_if_running(struct net_device *ndev) +{ + if (!netif_running(ndev)) + return -EBUSY; + return 0; +} + +static int sun8i_emac_get_sset_count(struct net_device *ndev, int sset) +{ + switch (sset) { + case ETH_SS_STATS: + return ARRAY_SIZE(estats_str); + } + 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); + + 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; + return phy_ethtool_gset(phy, cmd); +} + +static int sun8i_emac_ethtool_set_settings(struct net_device *ndev, + struct ethtool_cmd *cmd) +{ + struct phy_device *phy = ndev->phydev; + + return phy_ethtool_sset(phy, cmd); +} + +static void sun8i_emac_ethtool_getdrvinfo(struct net_device *ndev, + struct ethtool_drvinfo *info) +{ + strlcpy(info->driver, "sun8i_emac", sizeof(info->driver)); + strcpy(info->version, "00"); + info->fw_version[0] = '\0'; +} + +static void sun8i_emac_ethtool_stats(struct net_device *ndev, + struct ethtool_stats *dummy, u64 *data) +{ + struct sun8i_emac_priv *priv = netdev_priv(ndev); + + memcpy(data, &priv->estats, + sun8i_emac_get_sset_count(ndev, ETH_SS_STATS) * sizeof(u64)); +} + +static void sun8i_emac_ethtool_strings(struct net_device *dev, u32 stringset, + u8 *buffer) +{ + switch (stringset) { + case ETH_SS_STATS: + memcpy(buffer, &estats_str, + sun8i_emac_get_sset_count(dev, ETH_SS_STATS) * + sizeof(struct ethtool_str)); + break; + } +} + +static u32 sun8i_emac_ethtool_getmsglevel(struct net_device *ndev) +{ + struct sun8i_emac_priv *priv = netdev_priv(ndev); + + return priv->msg_enable; +} + +static void sun8i_emac_ethtool_setmsglevel(struct net_device *ndev, u32 level) +{ + struct sun8i_emac_priv *priv = netdev_priv(ndev); + + priv->msg_enable = level; +} + +static void sun8i_emac_get_pauseparam(struct net_device *ndev, + struct ethtool_pauseparam *pause) +{ + struct sun8i_emac_priv *priv = netdev_priv(ndev); + + pause->rx_pause = 0; + pause->tx_pause = 0; + pause->autoneg = ndev->phydev->autoneg; + + if (priv->flow_ctrl & FLOW_RX) + pause->rx_pause = 1; + if (priv->flow_ctrl & FLOW_TX) + pause->tx_pause = 1; +} + +static int sun8i_emac_set_pauseparam(struct net_device *ndev, + struct ethtool_pauseparam *pause) +{ + struct sun8i_emac_priv *priv = netdev_priv(ndev); + struct phy_device *phy = ndev->phydev; + int new_pause = 0; + int ret = 0; + + if (pause->rx_pause) + new_pause |= FLOW_RX; + if (pause->tx_pause) + new_pause |= FLOW_TX; + + priv->flow_ctrl = new_pause; + phy->autoneg = pause->autoneg; + + if (phy->autoneg) { + if (netif_running(ndev)) + ret = phy_start_aneg(phy); + } else { + sun8i_emac_flow_ctrl(priv, phy->duplex, priv->flow_ctrl, + priv->pause); + } + return ret; +} + +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 = sun8i_emac_get_pauseparam, + .set_pauseparam = sun8i_emac_set_pauseparam, + .get_ethtool_stats = sun8i_emac_ethtool_stats, + .get_strings = sun8i_emac_ethtool_strings, + .get_wol = NULL, + .set_wol = NULL, + .get_sset_count = sun8i_emac_get_sset_count, + .get_drvinfo = sun8i_emac_ethtool_getdrvinfo, + .get_msglevel = sun8i_emac_ethtool_getmsglevel, + .set_msglevel = sun8i_emac_ethtool_setmsglevel, +}; + +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_mac_address = eth_mac_addr, + .ndo_set_features = sun8i_emac_set_features, +}; + +static irqreturn_t sun8i_emac_dma_interrupt(int irq, void *dev_id) +{ + struct net_device *ndev = (struct net_device *)dev_id; + struct sun8i_emac_priv *priv = netdev_priv(ndev); + u32 v, u; + + v = readl(priv->base + SUN8I_EMAC_INT_STA); + + netif_info(priv, intr, ndev, "%s %x\n", __func__, v); + + /* When this bit is asserted, a frame transmission is completed. */ + if (v & BIT(0)) + sun8i_emac_complete_xmit(ndev); + + /* When this bit is asserted, the TX DMA FSM is stopped. */ + if (v & BIT(1)) + priv->estats.tx_dma_stop++; + + /* When this asserted, the TX DMA can not acquire next TX descriptor + * and TX DMA FSM is suspended. + */ + if (v & BIT(2)) + priv->estats.tx_dma_ua++; + + if (v & BIT(3)) + netif_dbg(priv, intr, ndev, "Unhandled interrupt TX TIMEOUT\n"); + + if (v & BIT(4)) { + netif_dbg(priv, intr, ndev, "Unhandled interrupt TX underflow\n"); + priv->estats.tx_underflow_int++; + } + + /* When this bit asserted , the frame is transmitted to FIFO totally. */ + if (v & BIT(5)) { + netif_dbg(priv, intr, ndev, "Unhandled interrupt TX_EARLY_INT\n"); + priv->estats.tx_early_int++; + } + + /* 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 RX 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); + priv->estats.rx_dma_ua++; + } + + if (v & BIT(10)) { + netif_dbg(priv, intr, ndev, "Unhandled interrupt RX_DMA_STOPPED_INT\n"); + priv->estats.rx_dma_stop++; + } + if (v & BIT(11)) + netif_dbg(priv, intr, ndev, "Unhandled interrupt RX_TIMEOUT\n"); + if (v & BIT(12)) + netif_dbg(priv, intr, ndev, "Unhandled interrupt RX OVERFLOW\n"); + if (v & BIT(13)) { + netif_dbg(priv, intr, ndev, "Unhandled interrupt RX EARLY\n"); + priv->estats.rx_early_int++; + } + if (v & BIT(16)) + netif_dbg(priv, intr, ndev, "Unhandled interrupt RGMII\n"); + + /* the datasheet state those register as read-only + * but nothing work without writing to it + */ + 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 device_node *np = pdev->dev.of_node; + struct sun8i_emac_priv *priv; + struct net_device *ndev; + int ret; + + ndev = alloc_etherdev(sizeof(*priv)); + if (!ndev) + return -ENOMEM; + + SET_NETDEV_DEV(ndev, &pdev->dev); + priv = netdev_priv(ndev); + platform_set_drvdata(pdev, ndev); + + priv->variant = (enum emac_variant)of_device_get_match_data(&pdev->dev); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(priv->base)) { + ret = PTR_ERR(priv->base); + dev_err(&pdev->dev, "Cannot request MMIO: %d\n", ret); + goto probe_err; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "syscon"); + priv->syscon = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(priv->syscon)) { + ret = PTR_ERR(priv->syscon); + dev_err(&pdev->dev, + "Cannot map system control registers: %d\n", ret); + goto probe_err; + } + + priv->irq = platform_get_irq(pdev, 0); + if (priv->irq < 0) { + ret = priv->irq; + dev_err(&pdev->dev, "Cannot claim IRQ: %d\n", ret); + goto probe_err; + } + + ret = devm_request_irq(&pdev->dev, priv->irq, sun8i_emac_dma_interrupt, + 0, dev_name(&pdev->dev), ndev); + if (ret) { + dev_err(&pdev->dev, "Cannot request IRQ: %d\n", ret); + goto probe_err; + } + + priv->ahb_clk = devm_clk_get(&pdev->dev, "ahb"); + if (IS_ERR(priv->ahb_clk)) { + ret = PTR_ERR(priv->ahb_clk); + dev_err(&pdev->dev, "Cannot get AHB clock err=%d\n", ret); + goto probe_err; + } + + priv->rst = devm_reset_control_get_optional(&pdev->dev, "ahb"); + if (IS_ERR(priv->rst)) { + ret = PTR_ERR(priv->rst); + if (ret == -EPROBE_DEFER) + return -EPROBE_DEFER; + dev_info(&pdev->dev, "no mac reset control found %d\n", ret); + priv->rst = NULL; + } + + if (priv->variant == H3_EMAC) + priv->use_internal_phy = + of_property_read_bool(np, "allwinner,use-internal-phy"); + + if (priv->use_internal_phy) { + priv->ephy_clk = devm_clk_get(&pdev->dev, "ephy"); + if (IS_ERR(priv->ephy_clk)) { + ret = PTR_ERR(priv->ephy_clk); + dev_err(&pdev->dev, "Cannot get EPHY clock err=%d\n", + ret); + goto probe_err; + } + + priv->rst_ephy = devm_reset_control_get_optional(&pdev->dev, + "ephy"); + if (IS_ERR(priv->rst_ephy)) { + ret = PTR_ERR(priv->rst_ephy); + if (ret == -EPROBE_DEFER) + goto probe_err; + dev_info(&pdev->dev, + "no EPHY reset control found %d\n", ret); + priv->rst_ephy = NULL; + } + } + + spin_lock_init(&priv->lock); + spin_lock_init(&priv->tx_lock); + + 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_RXFCS; + ndev->hw_features |= NETIF_F_RXALL; + ndev->hw_features |= NETIF_F_LOOPBACK; + ndev->priv_flags |= IFF_UNICAST_FLT; + + ndev->watchdog_timeo = msecs_to_jiffies(5000); + + ret = register_netdev(ndev); + if (ret) { + dev_err(&pdev->dev, "ERROR: Register %s failed\n", ndev->name); + goto probe_err; + } + + return 0; + +probe_err: + free_netdev(ndev); + return ret; +} + +static int sun8i_emac_remove(struct platform_device *pdev) +{ + struct net_device *ndev = platform_get_drvdata(pdev); + + 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-a83t-emac", + .data = (void *)A83T_EMAC }, + { .compatible = "allwinner,sun8i-h3-emac", + .data = (void *)H3_EMAC }, + { .compatible = "allwinner,sun50i-a64-emac", + .data = (void *)A64_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@xxxxxxxxx"); -- 2.7.3 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html