]> git.proxmox.com Git - mirror_ubuntu-eoan-kernel.git/commitdiff
net: sh_eth: add support for Renesas SuperH Ethernet
authorNobuhiro Iwamatsu <iwamatsu.nobuhiro@renesas.com>
Mon, 9 Jun 2008 23:33:56 +0000 (16:33 -0700)
committerJeff Garzik <jgarzik@redhat.com>
Thu, 12 Jun 2008 01:58:25 +0000 (21:58 -0400)
Add support for Renesas SuperH Ethernet controller.  This driver supports
SH7710 and SH7712.

[akpm@linux-foundation.org: coding-style fixes]
Signed-off-by: Yoshihiro Shimoda <shimoda.yoshihiro@renesas.com>
Signed-off-by: Nobuhiro Iwamatsu <iwamatsu.nobuhiro@renesas.com>
Cc: Paul Mundt <lethal@linux-sh.org>
Cc: Jeff Garzik <jeff@garzik.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Jeff Garzik <jgarzik@redhat.com>
drivers/net/Kconfig
drivers/net/Makefile
drivers/net/sh_eth.c [new file with mode: 0644]
drivers/net/sh_eth.h [new file with mode: 0644]

index 8c5e8405fe0901cb54bf7f3ebb7dc0cf0ddb98bc..40eb24d6d7553df597a392d9a9692f6f22b6181f 100644 (file)
@@ -524,6 +524,18 @@ config STNIC
 
          If unsure, say N.
 
+config SH_ETH
+       tristate "Renesas SuperH Ethernet support"
+       depends on SUPERH && \
+               (CPU_SUBTYPE_SH7710 || CPU_SUBTYPE_SH7712)
+       select CRC32
+       select MII
+       select MDIO_BITBANG
+       select PHYLIB
+       help
+         Renesas SuperH Ethernet device driver.
+         This driver support SH7710 and SH7712.
+
 config SUNLANCE
        tristate "Sun LANCE support"
        depends on SBUS
index dcbfe8421154b25a83fd50ab7bf946da955eb65a..c52738a3aaab8dade8d52fa6e7789022d788a4b2 100644 (file)
@@ -80,6 +80,7 @@ obj-$(CONFIG_VIA_RHINE) += via-rhine.o
 obj-$(CONFIG_VIA_VELOCITY) += via-velocity.o
 obj-$(CONFIG_ADAPTEC_STARFIRE) += starfire.o
 obj-$(CONFIG_RIONET) += rionet.o
+obj-$(CONFIG_SH_ETH) += sh_eth.o
 
 #
 # end link order section
diff --git a/drivers/net/sh_eth.c b/drivers/net/sh_eth.c
new file mode 100644 (file)
index 0000000..f64d987
--- /dev/null
@@ -0,0 +1,1174 @@
+/*
+ *  SuperH Ethernet device driver
+ *
+ *  Copyright (C) 2006,2007 Nobuhiro Iwamatsu
+ *  Copyright (C) 2008 Renesas Solutions Corp.
+ *
+ *  This program is free software; you can redistribute it and/or modify it
+ *  under the terms and conditions of the GNU General Public License,
+ *  version 2, as published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope it will be useful, but WITHOUT
+ *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ *  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ *  more details.
+ *  You should have received a copy of the GNU General Public License along with
+ *  this program; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ *  The full GNU General Public License is included in this distribution in
+ *  the file called "COPYING".
+ */
+
+#include <linux/version.h>
+#include <linux/init.h>
+#include <linux/dma-mapping.h>
+#include <linux/etherdevice.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/mdio-bitbang.h>
+#include <linux/netdevice.h>
+#include <linux/phy.h>
+#include <linux/cache.h>
+#include <linux/io.h>
+
+#include "sh_eth.h"
+
+/*
+ * Program the hardware MAC address from dev->dev_addr.
+ */
+static void update_mac_address(struct net_device *ndev)
+{
+       u32 ioaddr = ndev->base_addr;
+
+       ctrl_outl((ndev->dev_addr[0] << 24) | (ndev->dev_addr[1] << 16) |
+                 (ndev->dev_addr[2] << 8) | (ndev->dev_addr[3]),
+                 ioaddr + MAHR);
+       ctrl_outl((ndev->dev_addr[4] << 8) | (ndev->dev_addr[5]),
+                 ioaddr + MALR);
+}
+
+/*
+ * Get MAC address from SuperH MAC address register
+ *
+ * SuperH's Ethernet device doesn't have 'ROM' to MAC address.
+ * This driver get MAC address that use by bootloader(U-boot or sh-ipl+g).
+ * When you want use this device, you must set MAC address in bootloader.
+ *
+ */
+static void read_mac_address(struct net_device *ndev)
+{
+       u32 ioaddr = ndev->base_addr;
+
+       ndev->dev_addr[0] = (ctrl_inl(ioaddr + MAHR) >> 24);
+       ndev->dev_addr[1] = (ctrl_inl(ioaddr + MAHR) >> 16) & 0xFF;
+       ndev->dev_addr[2] = (ctrl_inl(ioaddr + MAHR) >> 8) & 0xFF;
+       ndev->dev_addr[3] = (ctrl_inl(ioaddr + MAHR) & 0xFF);
+       ndev->dev_addr[4] = (ctrl_inl(ioaddr + MALR) >> 8) & 0xFF;
+       ndev->dev_addr[5] = (ctrl_inl(ioaddr + MALR) & 0xFF);
+}
+
+struct bb_info {
+       struct mdiobb_ctrl ctrl;
+       u32 addr;
+       u32 mmd_msk;/* MMD */
+       u32 mdo_msk;
+       u32 mdi_msk;
+       u32 mdc_msk;
+};
+
+/* PHY bit set */
+static void bb_set(u32 addr, u32 msk)
+{
+       ctrl_outl(ctrl_inl(addr) | msk, addr);
+}
+
+/* PHY bit clear */
+static void bb_clr(u32 addr, u32 msk)
+{
+       ctrl_outl((ctrl_inl(addr) & ~msk), addr);
+}
+
+/* PHY bit read */
+static int bb_read(u32 addr, u32 msk)
+{
+       return (ctrl_inl(addr) & msk) != 0;
+}
+
+/* Data I/O pin control */
+static void sh_mmd_ctrl(struct mdiobb_ctrl *ctrl, int bit)
+{
+       struct bb_info *bitbang = container_of(ctrl, struct bb_info, ctrl);
+       if (bit)
+               bb_set(bitbang->addr, bitbang->mmd_msk);
+       else
+               bb_clr(bitbang->addr, bitbang->mmd_msk);
+}
+
+/* Set bit data*/
+static void sh_set_mdio(struct mdiobb_ctrl *ctrl, int bit)
+{
+       struct bb_info *bitbang = container_of(ctrl, struct bb_info, ctrl);
+
+       if (bit)
+               bb_set(bitbang->addr, bitbang->mdo_msk);
+       else
+               bb_clr(bitbang->addr, bitbang->mdo_msk);
+}
+
+/* Get bit data*/
+static int sh_get_mdio(struct mdiobb_ctrl *ctrl)
+{
+       struct bb_info *bitbang = container_of(ctrl, struct bb_info, ctrl);
+       return bb_read(bitbang->addr, bitbang->mdi_msk);
+}
+
+/* MDC pin control */
+static void sh_mdc_ctrl(struct mdiobb_ctrl *ctrl, int bit)
+{
+       struct bb_info *bitbang = container_of(ctrl, struct bb_info, ctrl);
+
+       if (bit)
+               bb_set(bitbang->addr, bitbang->mdc_msk);
+       else
+               bb_clr(bitbang->addr, bitbang->mdc_msk);
+}
+
+/* mdio bus control struct */
+static struct mdiobb_ops bb_ops = {
+       .owner = THIS_MODULE,
+       .set_mdc = sh_mdc_ctrl,
+       .set_mdio_dir = sh_mmd_ctrl,
+       .set_mdio_data = sh_set_mdio,
+       .get_mdio_data = sh_get_mdio,
+};
+
+static void sh_eth_reset(struct net_device *ndev)
+{
+       u32 ioaddr = ndev->base_addr;
+
+       ctrl_outl(ctrl_inl(ioaddr + EDMR) | EDMR_SRST, ioaddr + EDMR);
+       mdelay(3);
+       ctrl_outl(ctrl_inl(ioaddr + EDMR) & ~EDMR_SRST, ioaddr + EDMR);
+}
+
+/* free skb and descriptor buffer */
+static void sh_eth_ring_free(struct net_device *ndev)
+{
+       struct sh_eth_private *mdp = netdev_priv(ndev);
+       int i;
+
+       /* Free Rx skb ringbuffer */
+       if (mdp->rx_skbuff) {
+               for (i = 0; i < RX_RING_SIZE; i++) {
+                       if (mdp->rx_skbuff[i])
+                               dev_kfree_skb(mdp->rx_skbuff[i]);
+               }
+       }
+       kfree(mdp->rx_skbuff);
+
+       /* Free Tx skb ringbuffer */
+       if (mdp->tx_skbuff) {
+               for (i = 0; i < TX_RING_SIZE; i++) {
+                       if (mdp->tx_skbuff[i])
+                               dev_kfree_skb(mdp->tx_skbuff[i]);
+               }
+       }
+       kfree(mdp->tx_skbuff);
+}
+
+/* format skb and descriptor buffer */
+static void sh_eth_ring_format(struct net_device *ndev)
+{
+       struct sh_eth_private *mdp = netdev_priv(ndev);
+       int i;
+       struct sk_buff *skb;
+       struct sh_eth_rxdesc *rxdesc = NULL;
+       struct sh_eth_txdesc *txdesc = NULL;
+       int rx_ringsize = sizeof(*rxdesc) * RX_RING_SIZE;
+       int tx_ringsize = sizeof(*txdesc) * TX_RING_SIZE;
+
+       mdp->cur_rx = mdp->cur_tx = 0;
+       mdp->dirty_rx = mdp->dirty_tx = 0;
+
+       memset(mdp->rx_ring, 0, rx_ringsize);
+
+       /* build Rx ring buffer */
+       for (i = 0; i < RX_RING_SIZE; i++) {
+               /* skb */
+               mdp->rx_skbuff[i] = NULL;
+               skb = dev_alloc_skb(mdp->rx_buf_sz);
+               mdp->rx_skbuff[i] = skb;
+               if (skb == NULL)
+                       break;
+               skb->dev = ndev;        /* Mark as being used by this device. */
+               skb_reserve(skb, RX_OFFSET);
+
+               /* RX descriptor */
+               rxdesc = &mdp->rx_ring[i];
+               rxdesc->addr = (u32)skb->data & ~0x3UL;
+               rxdesc->status = cpu_to_le32(RD_RACT | RD_RFP);
+
+               /* The size of the buffer is 16 byte boundary. */
+               rxdesc->buffer_length = (mdp->rx_buf_sz + 16) & ~0x0F;
+       }
+
+       mdp->dirty_rx = (u32) (i - RX_RING_SIZE);
+
+       /* Mark the last entry as wrapping the ring. */
+       rxdesc->status |= cpu_to_le32(RC_RDEL);
+
+       memset(mdp->tx_ring, 0, tx_ringsize);
+
+       /* build Tx ring buffer */
+       for (i = 0; i < TX_RING_SIZE; i++) {
+               mdp->tx_skbuff[i] = NULL;
+               txdesc = &mdp->tx_ring[i];
+               txdesc->status = cpu_to_le32(TD_TFP);
+               txdesc->buffer_length = 0;
+       }
+
+       txdesc->status |= cpu_to_le32(TD_TDLE);
+}
+
+/* Get skb and descriptor buffer */
+static int sh_eth_ring_init(struct net_device *ndev)
+{
+       struct sh_eth_private *mdp = netdev_priv(ndev);
+       int rx_ringsize, tx_ringsize, ret = 0;
+
+       /*
+        * +26 gets the maximum ethernet encapsulation, +7 & ~7 because the
+        * card needs room to do 8 byte alignment, +2 so we can reserve
+        * the first 2 bytes, and +16 gets room for the status word from the
+        * card.
+        */
+       mdp->rx_buf_sz = (ndev->mtu <= 1492 ? PKT_BUF_SZ :
+                         (((ndev->mtu + 26 + 7) & ~7) + 2 + 16));
+
+       /* Allocate RX and TX skb rings */
+       mdp->rx_skbuff = kmalloc(sizeof(*mdp->rx_skbuff) * RX_RING_SIZE,
+                               GFP_KERNEL);
+       if (!mdp->rx_skbuff) {
+               printk(KERN_ERR "%s: Cannot allocate Rx skb\n", ndev->name);
+               ret = -ENOMEM;
+               return ret;
+       }
+
+       mdp->tx_skbuff = kmalloc(sizeof(*mdp->tx_skbuff) * TX_RING_SIZE,
+                               GFP_KERNEL);
+       if (!mdp->tx_skbuff) {
+               printk(KERN_ERR "%s: Cannot allocate Tx skb\n", ndev->name);
+               ret = -ENOMEM;
+               goto skb_ring_free;
+       }
+
+       /* Allocate all Rx descriptors. */
+       rx_ringsize = sizeof(struct sh_eth_rxdesc) * RX_RING_SIZE;
+       mdp->rx_ring = dma_alloc_coherent(NULL, rx_ringsize, &mdp->rx_desc_dma,
+                       GFP_KERNEL);
+
+       if (!mdp->rx_ring) {
+               printk(KERN_ERR "%s: Cannot allocate Rx Ring (size %d bytes)\n",
+                       ndev->name, rx_ringsize);
+               ret = -ENOMEM;
+               goto desc_ring_free;
+       }
+
+       mdp->dirty_rx = 0;
+
+       /* Allocate all Tx descriptors. */
+       tx_ringsize = sizeof(struct sh_eth_txdesc) * TX_RING_SIZE;
+       mdp->tx_ring = dma_alloc_coherent(NULL, tx_ringsize, &mdp->tx_desc_dma,
+                       GFP_KERNEL);
+       if (!mdp->tx_ring) {
+               printk(KERN_ERR "%s: Cannot allocate Tx Ring (size %d bytes)\n",
+                       ndev->name, tx_ringsize);
+               ret = -ENOMEM;
+               goto desc_ring_free;
+       }
+       return ret;
+
+desc_ring_free:
+       /* free DMA buffer */
+       dma_free_coherent(NULL, rx_ringsize, mdp->rx_ring, mdp->rx_desc_dma);
+
+skb_ring_free:
+       /* Free Rx and Tx skb ring buffer */
+       sh_eth_ring_free(ndev);
+
+       return ret;
+}
+
+static int sh_eth_dev_init(struct net_device *ndev)
+{
+       int ret = 0;
+       struct sh_eth_private *mdp = netdev_priv(ndev);
+       u32 ioaddr = ndev->base_addr;
+       u_int32_t rx_int_var, tx_int_var;
+       u32 val;
+
+       /* Soft Reset */
+       sh_eth_reset(ndev);
+
+       ctrl_outl(RPADIR_PADS1, ioaddr + RPADIR);       /* SH7712-DMA-RX-PAD2 */
+
+       /* all sh_eth int mask */
+       ctrl_outl(0, ioaddr + EESIPR);
+
+       /* FIFO size set */
+       ctrl_outl(0, ioaddr + EDMR);    /* Endian change */
+
+       ctrl_outl((FIFO_SIZE_T | FIFO_SIZE_R), ioaddr + FDR);
+       ctrl_outl(0, ioaddr + TFTR);
+
+       ctrl_outl(RMCR_RST, ioaddr + RMCR);
+
+       rx_int_var = mdp->rx_int_var = DESC_I_RINT8 | DESC_I_RINT5;
+       tx_int_var = mdp->tx_int_var = DESC_I_TINT2;
+       ctrl_outl(rx_int_var | tx_int_var, ioaddr + TRSCER);
+
+       ctrl_outl((FIFO_F_D_RFF | FIFO_F_D_RFD), ioaddr + FCFTR);
+       ctrl_outl(0, ioaddr + TRIMD);
+
+       /* Descriptor format */
+       sh_eth_ring_format(ndev);
+
+       ctrl_outl((u32)mdp->rx_ring, ioaddr + RDLAR);
+       ctrl_outl((u32)mdp->tx_ring, ioaddr + TDLAR);
+
+       ctrl_outl(ctrl_inl(ioaddr + EESR), ioaddr + EESR);
+       ctrl_outl((DMAC_M_RFRMER | DMAC_M_ECI | 0x003fffff), ioaddr + EESIPR);
+
+       /* PAUSE Prohibition */
+       val = (ctrl_inl(ioaddr + ECMR) & ECMR_DM) |
+               ECMR_ZPF | (mdp->duplex ? ECMR_DM : 0) | ECMR_TE | ECMR_RE;
+
+       ctrl_outl(val, ioaddr + ECMR);
+       ctrl_outl(ECSR_BRCRX | ECSR_PSRTO | ECSR_LCHNG | ECSR_ICD |
+                 ECSIPR_MPDIP, ioaddr + ECSR);
+       ctrl_outl(ECSIPR_BRCRXIP | ECSIPR_PSRTOIP | ECSIPR_LCHNGIP |
+                 ECSIPR_ICDIP | ECSIPR_MPDIP, ioaddr + ECSIPR);
+
+       /* Set MAC address */
+       update_mac_address(ndev);
+
+       /* mask reset */
+#if defined(CONFIG_CPU_SUBTYPE_SH7710)
+       ctrl_outl(APR_AP, ioaddr + APR);
+       ctrl_outl(MPR_MP, ioaddr + MPR);
+       ctrl_outl(TPAUSER_UNLIMITED, ioaddr + TPAUSER);
+       ctrl_outl(BCFR_UNLIMITED, ioaddr + BCFR);
+#endif
+       /* Setting the Rx mode will start the Rx process. */
+       ctrl_outl(EDRRR_R, ioaddr + EDRRR);
+
+       netif_start_queue(ndev);
+
+       return ret;
+}
+
+/* free Tx skb function */
+static int sh_eth_txfree(struct net_device *ndev)
+{
+       struct sh_eth_private *mdp = netdev_priv(ndev);
+       struct sh_eth_txdesc *txdesc;
+       int freeNum = 0;
+       int entry = 0;
+
+       for (; mdp->cur_tx - mdp->dirty_tx > 0; mdp->dirty_tx++) {
+               entry = mdp->dirty_tx % TX_RING_SIZE;
+               txdesc = &mdp->tx_ring[entry];
+               if (txdesc->status & cpu_to_le32(TD_TACT))
+                       break;
+               /* Free the original skb. */
+               if (mdp->tx_skbuff[entry]) {
+                       dev_kfree_skb_irq(mdp->tx_skbuff[entry]);
+                       mdp->tx_skbuff[entry] = NULL;
+                       freeNum++;
+               }
+               txdesc->status = cpu_to_le32(TD_TFP);
+               if (entry >= TX_RING_SIZE - 1)
+                       txdesc->status |= cpu_to_le32(TD_TDLE);
+
+               mdp->stats.tx_packets++;
+               mdp->stats.tx_bytes += txdesc->buffer_length;
+       }
+       return freeNum;
+}
+
+/* Packet receive function */
+static int sh_eth_rx(struct net_device *ndev)
+{
+       struct sh_eth_private *mdp = netdev_priv(ndev);
+       struct sh_eth_rxdesc *rxdesc;
+
+       int entry = mdp->cur_rx % RX_RING_SIZE;
+       int boguscnt = (mdp->dirty_rx + RX_RING_SIZE) - mdp->cur_rx;
+       struct sk_buff *skb;
+       u16 pkt_len = 0;
+       u32 desc_status;
+
+       rxdesc = &mdp->rx_ring[entry];
+       while (!(rxdesc->status & cpu_to_le32(RD_RACT))) {
+               desc_status = le32_to_cpu(rxdesc->status);
+               pkt_len = rxdesc->frame_length;
+
+               if (--boguscnt < 0)
+                       break;
+
+               if (!(desc_status & RDFEND))
+                       mdp->stats.rx_length_errors++;
+
+               if (desc_status & (RD_RFS1 | RD_RFS2 | RD_RFS3 | RD_RFS4 |
+                                  RD_RFS5 | RD_RFS6 | RD_RFS10)) {
+                       mdp->stats.rx_errors++;
+                       if (desc_status & RD_RFS1)
+                               mdp->stats.rx_crc_errors++;
+                       if (desc_status & RD_RFS2)
+                               mdp->stats.rx_frame_errors++;
+                       if (desc_status & RD_RFS3)
+                               mdp->stats.rx_length_errors++;
+                       if (desc_status & RD_RFS4)
+                               mdp->stats.rx_length_errors++;
+                       if (desc_status & RD_RFS6)
+                               mdp->stats.rx_missed_errors++;
+                       if (desc_status & RD_RFS10)
+                               mdp->stats.rx_over_errors++;
+               } else {
+                       swaps((char *)(rxdesc->addr & ~0x3), pkt_len + 2);
+                       skb = mdp->rx_skbuff[entry];
+                       mdp->rx_skbuff[entry] = NULL;
+                       skb_put(skb, pkt_len);
+                       skb->protocol = eth_type_trans(skb, ndev);
+                       netif_rx(skb);
+                       ndev->last_rx = jiffies;
+                       mdp->stats.rx_packets++;
+                       mdp->stats.rx_bytes += pkt_len;
+               }
+               rxdesc->status |= cpu_to_le32(RD_RACT);
+               entry = (++mdp->cur_rx) % RX_RING_SIZE;
+       }
+
+       /* Refill the Rx ring buffers. */
+       for (; mdp->cur_rx - mdp->dirty_rx > 0; mdp->dirty_rx++) {
+               entry = mdp->dirty_rx % RX_RING_SIZE;
+               rxdesc = &mdp->rx_ring[entry];
+               if (mdp->rx_skbuff[entry] == NULL) {
+                       skb = dev_alloc_skb(mdp->rx_buf_sz);
+                       mdp->rx_skbuff[entry] = skb;
+                       if (skb == NULL)
+                               break;  /* Better luck next round. */
+                       skb->dev = ndev;
+                       skb_reserve(skb, RX_OFFSET);
+                       rxdesc->addr = (u32)skb->data & ~0x3UL;
+               }
+               /* The size of the buffer is 16 byte boundary. */
+               rxdesc->buffer_length = (mdp->rx_buf_sz + 16) & ~0x0F;
+               if (entry >= RX_RING_SIZE - 1)
+                       rxdesc->status |=
+                       cpu_to_le32(RD_RACT | RD_RFP | RC_RDEL);
+               else
+                       rxdesc->status |=
+                       cpu_to_le32(RD_RACT | RD_RFP);
+       }
+
+       /* Restart Rx engine if stopped. */
+       /* If we don't need to check status, don't. -KDU */
+       ctrl_outl(EDRRR_R, ndev->base_addr + EDRRR);
+
+       return 0;
+}
+
+/* error control function */
+static void sh_eth_error(struct net_device *ndev, int intr_status)
+{
+       struct sh_eth_private *mdp = netdev_priv(ndev);
+       u32 ioaddr = ndev->base_addr;
+       u32 felic_stat;
+
+       if (intr_status & EESR_ECI) {
+               felic_stat = ctrl_inl(ioaddr + ECSR);
+               ctrl_outl(felic_stat, ioaddr + ECSR);   /* clear int */
+               if (felic_stat & ECSR_ICD)
+                       mdp->stats.tx_carrier_errors++;
+               if (felic_stat & ECSR_LCHNG) {
+                       /* Link Changed */
+                       u32 link_stat = (ctrl_inl(ioaddr + PSR));
+                       if (!(link_stat & PHY_ST_LINK)) {
+                               /* Link Down : disable tx and rx */
+                               ctrl_outl(ctrl_inl(ioaddr + ECMR) &
+                                         ~(ECMR_RE | ECMR_TE), ioaddr + ECMR);
+                       } else {
+                               /* Link Up */
+                               ctrl_outl(ctrl_inl(ioaddr + EESIPR) &
+                                         ~DMAC_M_ECI, ioaddr + EESIPR);
+                               /*clear int */
+                               ctrl_outl(ctrl_inl(ioaddr + ECSR),
+                                         ioaddr + ECSR);
+                               ctrl_outl(ctrl_inl(ioaddr + EESIPR) |
+                                         DMAC_M_ECI, ioaddr + EESIPR);
+                               /* enable tx and rx */
+                               ctrl_outl(ctrl_inl(ioaddr + ECMR) |
+                                         (ECMR_RE | ECMR_TE), ioaddr + ECMR);
+                       }
+               }
+       }
+
+       if (intr_status & EESR_TWB) {
+               /* Write buck end. unused write back interrupt */
+               if (intr_status & EESR_TABT)    /* Transmit Abort int */
+                       mdp->stats.tx_aborted_errors++;
+       }
+
+       if (intr_status & EESR_RABT) {
+               /* Receive Abort int */
+               if (intr_status & EESR_RFRMER) {
+                       /* Receive Frame Overflow int */
+                       mdp->stats.rx_frame_errors++;
+                       printk(KERN_ERR "Receive Frame Overflow\n");
+               }
+       }
+
+       if (intr_status & EESR_ADE) {
+               if (intr_status & EESR_TDE) {
+                       if (intr_status & EESR_TFE)
+                               mdp->stats.tx_fifo_errors++;
+               }
+       }
+
+       if (intr_status & EESR_RDE) {
+               /* Receive Descriptor Empty int */
+               mdp->stats.rx_over_errors++;
+
+               if (ctrl_inl(ioaddr + EDRRR) ^ EDRRR_R)
+                       ctrl_outl(EDRRR_R, ioaddr + EDRRR);
+               printk(KERN_ERR "Receive Descriptor Empty\n");
+       }
+       if (intr_status & EESR_RFE) {
+               /* Receive FIFO Overflow int */
+               mdp->stats.rx_fifo_errors++;
+               printk(KERN_ERR "Receive FIFO Overflow\n");
+       }
+       if (intr_status &
+           (EESR_TWB | EESR_TABT | EESR_ADE | EESR_TDE | EESR_TFE)) {
+               /* Tx error */
+               u32 edtrr = ctrl_inl(ndev->base_addr + EDTRR);
+               /* dmesg */
+               printk(KERN_ERR "%s:TX error. status=%8.8x cur_tx=%8.8x ",
+                               ndev->name, intr_status, mdp->cur_tx);
+               printk(KERN_ERR "dirty_tx=%8.8x state=%8.8x EDTRR=%8.8x.\n",
+                               mdp->dirty_tx, (u32) ndev->state, edtrr);
+               /* dirty buffer free */
+               sh_eth_txfree(ndev);
+
+               /* SH7712 BUG */
+               if (edtrr ^ EDTRR_TRNS) {
+                       /* tx dma start */
+                       ctrl_outl(EDTRR_TRNS, ndev->base_addr + EDTRR);
+               }
+               /* wakeup */
+               netif_wake_queue(ndev);
+       }
+}
+
+static irqreturn_t sh_eth_interrupt(int irq, void *netdev)
+{
+       struct net_device *ndev = netdev;
+       struct sh_eth_private *mdp = netdev_priv(ndev);
+       u32 ioaddr, boguscnt = RX_RING_SIZE;
+       u32 intr_status = 0;
+
+       ioaddr = ndev->base_addr;
+       spin_lock(&mdp->lock);
+
+       intr_status = ctrl_inl(ioaddr + EESR);
+       /* Clear interrupt */
+       ctrl_outl(intr_status, ioaddr + EESR);
+
+       if (intr_status & (EESR_FRC | EESR_RINT8 |
+                          EESR_RINT5 | EESR_RINT4 | EESR_RINT3 | EESR_RINT2 |
+                          EESR_RINT1))
+               sh_eth_rx(ndev);
+       if (intr_status & (EESR_FTC |
+                          EESR_TINT4 | EESR_TINT3 | EESR_TINT2 | EESR_TINT1)) {
+
+               sh_eth_txfree(ndev);
+               netif_wake_queue(ndev);
+       }
+
+       if (intr_status & EESR_ERR_CHECK)
+               sh_eth_error(ndev, intr_status);
+
+       if (--boguscnt < 0) {
+               printk(KERN_WARNING
+                      "%s: Too much work at interrupt, status=0x%4.4x.\n",
+                      ndev->name, intr_status);
+       }
+
+       spin_unlock(&mdp->lock);
+
+       return IRQ_HANDLED;
+}
+
+static void sh_eth_timer(unsigned long data)
+{
+       struct net_device *ndev = (struct net_device *)data;
+       struct sh_eth_private *mdp = netdev_priv(ndev);
+
+       mod_timer(&mdp->timer, jiffies + (10 * HZ));
+}
+
+/* PHY state control function */
+static void sh_eth_adjust_link(struct net_device *ndev)
+{
+       struct sh_eth_private *mdp = netdev_priv(ndev);
+       struct phy_device *phydev = mdp->phydev;
+       u32 ioaddr = ndev->base_addr;
+       int new_state = 0;
+
+       if (phydev->link != PHY_DOWN) {
+               if (phydev->duplex != mdp->duplex) {
+                       new_state = 1;
+                       mdp->duplex = phydev->duplex;
+               }
+
+               if (phydev->speed != mdp->speed) {
+                       new_state = 1;
+                       mdp->speed = phydev->speed;
+               }
+               if (mdp->link == PHY_DOWN) {
+                       ctrl_outl((ctrl_inl(ioaddr + ECMR) & ~ECMR_TXF)
+                                       | ECMR_DM, ioaddr + ECMR);
+                       new_state = 1;
+                       mdp->link = phydev->link;
+                       netif_schedule(ndev);
+                       netif_carrier_on(ndev);
+                       netif_start_queue(ndev);
+               }
+       } else if (mdp->link) {
+               new_state = 1;
+               mdp->link = PHY_DOWN;
+               mdp->speed = 0;
+               mdp->duplex = -1;
+               netif_stop_queue(ndev);
+               netif_carrier_off(ndev);
+       }
+
+       if (new_state)
+               phy_print_status(phydev);
+}
+
+/* PHY init function */
+static int sh_eth_phy_init(struct net_device *ndev)
+{
+       struct sh_eth_private *mdp = netdev_priv(ndev);
+       char phy_id[BUS_ID_SIZE];
+       struct phy_device *phydev = NULL;
+
+       snprintf(phy_id, BUS_ID_SIZE, PHY_ID_FMT,
+               mdp->mii_bus->id , mdp->phy_id);
+
+       mdp->link = PHY_DOWN;
+       mdp->speed = 0;
+       mdp->duplex = -1;
+
+       /* Try connect to PHY */
+       phydev = phy_connect(ndev, phy_id, &sh_eth_adjust_link,
+                               0, PHY_INTERFACE_MODE_MII);
+       if (IS_ERR(phydev)) {
+               dev_err(&ndev->dev, "phy_connect failed\n");
+               return PTR_ERR(phydev);
+       }
+       dev_info(&ndev->dev, "attached phy %i to driver %s\n",
+       phydev->addr, phydev->drv->name);
+
+       mdp->phydev = phydev;
+
+       return 0;
+}
+
+/* PHY control start function */
+static int sh_eth_phy_start(struct net_device *ndev)
+{
+       struct sh_eth_private *mdp = netdev_priv(ndev);
+       int ret;
+
+       ret = sh_eth_phy_init(ndev);
+       if (ret)
+               return ret;
+
+       /* reset phy - this also wakes it from PDOWN */
+       phy_write(mdp->phydev, MII_BMCR, BMCR_RESET);
+       phy_start(mdp->phydev);
+
+       return 0;
+}
+
+/* network device open function */
+static int sh_eth_open(struct net_device *ndev)
+{
+       int ret = 0;
+       struct sh_eth_private *mdp = netdev_priv(ndev);
+
+       ret = request_irq(ndev->irq, &sh_eth_interrupt, 0, ndev->name, ndev);
+       if (ret) {
+               printk(KERN_ERR "Can not assign IRQ number to %s\n", CARDNAME);
+               return ret;
+       }
+
+       /* Descriptor set */
+       ret = sh_eth_ring_init(ndev);
+       if (ret)
+               goto out_free_irq;
+
+       /* device init */
+       ret = sh_eth_dev_init(ndev);
+       if (ret)
+               goto out_free_irq;
+
+       /* PHY control start*/
+       ret = sh_eth_phy_start(ndev);
+       if (ret)
+               goto out_free_irq;
+
+       /* Set the timer to check for link beat. */
+       init_timer(&mdp->timer);
+       mdp->timer.expires = (jiffies + (24 * HZ)) / 10;/* 2.4 sec. */
+       setup_timer(&mdp->timer, sh_eth_timer, ndev);
+
+       return ret;
+
+out_free_irq:
+       free_irq(ndev->irq, ndev);
+       return ret;
+}
+
+/* Timeout function */
+static void sh_eth_tx_timeout(struct net_device *ndev)
+{
+       struct sh_eth_private *mdp = netdev_priv(ndev);
+       u32 ioaddr = ndev->base_addr;
+       struct sh_eth_rxdesc *rxdesc;
+       int i;
+
+       netif_stop_queue(ndev);
+
+       /* worning message out. */
+       printk(KERN_WARNING "%s: transmit timed out, status %8.8x,"
+              " resetting...\n", ndev->name, (int)ctrl_inl(ioaddr + EESR));
+
+       /* tx_errors count up */
+       mdp->stats.tx_errors++;
+
+       /* timer off */
+       del_timer_sync(&mdp->timer);
+
+       /* Free all the skbuffs in the Rx queue. */
+       for (i = 0; i < RX_RING_SIZE; i++) {
+               rxdesc = &mdp->rx_ring[i];
+               rxdesc->status = 0;
+               rxdesc->addr = 0xBADF00D0;
+               if (mdp->rx_skbuff[i])
+                       dev_kfree_skb(mdp->rx_skbuff[i]);
+               mdp->rx_skbuff[i] = NULL;
+       }
+       for (i = 0; i < TX_RING_SIZE; i++) {
+               if (mdp->tx_skbuff[i])
+                       dev_kfree_skb(mdp->tx_skbuff[i]);
+               mdp->tx_skbuff[i] = NULL;
+       }
+
+       /* device init */
+       sh_eth_dev_init(ndev);
+
+       /* timer on */
+       mdp->timer.expires = (jiffies + (24 * HZ)) / 10;/* 2.4 sec. */
+       add_timer(&mdp->timer);
+}
+
+/* Packet transmit function */
+static int sh_eth_start_xmit(struct sk_buff *skb, struct net_device *ndev)
+{
+       struct sh_eth_private *mdp = netdev_priv(ndev);
+       struct sh_eth_txdesc *txdesc;
+       u32 entry;
+       int flags;
+
+       spin_lock_irqsave(&mdp->lock, flags);
+       if ((mdp->cur_tx - mdp->dirty_tx) >= (TX_RING_SIZE - 4)) {
+               if (!sh_eth_txfree(ndev)) {
+                       netif_stop_queue(ndev);
+                       spin_unlock_irqrestore(&mdp->lock, flags);
+                       return 1;
+               }
+       }
+       spin_unlock_irqrestore(&mdp->lock, flags);
+
+       entry = mdp->cur_tx % TX_RING_SIZE;
+       mdp->tx_skbuff[entry] = skb;
+       txdesc = &mdp->tx_ring[entry];
+       txdesc->addr = (u32)(skb->data);
+       /* soft swap. */
+       swaps((char *)(txdesc->addr & ~0x3), skb->len + 2);
+       /* write back */
+       __flush_purge_region(skb->data, skb->len);
+       if (skb->len < ETHERSMALL)
+               txdesc->buffer_length = ETHERSMALL;
+       else
+               txdesc->buffer_length = skb->len;
+
+       if (entry >= TX_RING_SIZE - 1)
+               txdesc->status |= cpu_to_le32(TD_TACT | TD_TDLE);
+       else
+               txdesc->status |= cpu_to_le32(TD_TACT);
+
+       mdp->cur_tx++;
+
+       ctrl_outl(EDTRR_TRNS, ndev->base_addr + EDTRR);
+       ndev->trans_start = jiffies;
+
+       return 0;
+}
+
+/* device close function */
+static int sh_eth_close(struct net_device *ndev)
+{
+       struct sh_eth_private *mdp = netdev_priv(ndev);
+       u32 ioaddr = ndev->base_addr;
+       int ringsize;
+
+       netif_stop_queue(ndev);
+
+       /* Disable interrupts by clearing the interrupt mask. */
+       ctrl_outl(0x0000, ioaddr + EESIPR);
+
+       /* Stop the chip's Tx and Rx processes. */
+       ctrl_outl(0, ioaddr + EDTRR);
+       ctrl_outl(0, ioaddr + EDRRR);
+
+       /* PHY Disconnect */
+       if (mdp->phydev) {
+               phy_stop(mdp->phydev);
+               phy_disconnect(mdp->phydev);
+       }
+
+       free_irq(ndev->irq, ndev);
+
+       del_timer_sync(&mdp->timer);
+
+       /* Free all the skbuffs in the Rx queue. */
+       sh_eth_ring_free(ndev);
+
+       /* free DMA buffer */
+       ringsize = sizeof(struct sh_eth_rxdesc) * RX_RING_SIZE;
+       dma_free_coherent(NULL, ringsize, mdp->rx_ring, mdp->rx_desc_dma);
+
+       /* free DMA buffer */
+       ringsize = sizeof(struct sh_eth_txdesc) * TX_RING_SIZE;
+       dma_free_coherent(NULL, ringsize, mdp->tx_ring, mdp->tx_desc_dma);
+
+       return 0;
+}
+
+static struct net_device_stats *sh_eth_get_stats(struct net_device *ndev)
+{
+       struct sh_eth_private *mdp = netdev_priv(ndev);
+       u32 ioaddr = ndev->base_addr;
+
+       mdp->stats.tx_dropped += ctrl_inl(ioaddr + TROCR);
+       ctrl_outl(0, ioaddr + TROCR);   /* (write clear) */
+       mdp->stats.collisions += ctrl_inl(ioaddr + CDCR);
+       ctrl_outl(0, ioaddr + CDCR);    /* (write clear) */
+       mdp->stats.tx_carrier_errors += ctrl_inl(ioaddr + LCCR);
+       ctrl_outl(0, ioaddr + LCCR);    /* (write clear) */
+       mdp->stats.tx_carrier_errors += ctrl_inl(ioaddr + CNDCR);
+       ctrl_outl(0, ioaddr + CNDCR);   /* (write clear) */
+
+       return &mdp->stats;
+}
+
+/* ioctl to device funciotn*/
+static int sh_eth_do_ioctl(struct net_device *ndev, struct ifreq *rq,
+                               int cmd)
+{
+       struct sh_eth_private *mdp = netdev_priv(ndev);
+       struct phy_device *phydev = mdp->phydev;
+
+       if (!netif_running(ndev))
+               return -EINVAL;
+
+       if (!phydev)
+               return -ENODEV;
+
+       return phy_mii_ioctl(phydev, if_mii(rq), cmd);
+}
+
+
+/* Multicast reception directions set */
+static void sh_eth_set_multicast_list(struct net_device *ndev)
+{
+       u32 ioaddr = ndev->base_addr;
+
+       if (ndev->flags & IFF_PROMISC) {
+               /* Set promiscuous. */
+               ctrl_outl((ctrl_inl(ioaddr + ECMR) & ~ECMR_MCT) | ECMR_PRM,
+                         ioaddr + ECMR);
+       } else {
+               /* Normal, unicast/broadcast-only mode. */
+               ctrl_outl((ctrl_inl(ioaddr + ECMR) & ~ECMR_PRM) | ECMR_MCT,
+                         ioaddr + ECMR);
+       }
+}
+
+/* SuperH's TSU register init function */
+static void sh_eth_tsu_init(u32 ioaddr)
+{
+       ctrl_outl(0, ioaddr + TSU_FWEN0);       /* Disable forward(0->1) */
+       ctrl_outl(0, ioaddr + TSU_FWEN1);       /* Disable forward(1->0) */
+       ctrl_outl(0, ioaddr + TSU_FCM); /* forward fifo 3k-3k */
+       ctrl_outl(0xc, ioaddr + TSU_BSYSL0);
+       ctrl_outl(0xc, ioaddr + TSU_BSYSL1);
+       ctrl_outl(0, ioaddr + TSU_PRISL0);
+       ctrl_outl(0, ioaddr + TSU_PRISL1);
+       ctrl_outl(0, ioaddr + TSU_FWSL0);
+       ctrl_outl(0, ioaddr + TSU_FWSL1);
+       ctrl_outl(TSU_FWSLC_POSTENU | TSU_FWSLC_POSTENL, ioaddr + TSU_FWSLC);
+       ctrl_outl(0, ioaddr + TSU_QTAGM0);      /* Disable QTAG(0->1) */
+       ctrl_outl(0, ioaddr + TSU_QTAGM1);      /* Disable QTAG(1->0) */
+       ctrl_outl(0, ioaddr + TSU_FWSR);        /* all interrupt status clear */
+       ctrl_outl(0, ioaddr + TSU_FWINMK);      /* Disable all interrupt */
+       ctrl_outl(0, ioaddr + TSU_TEN); /* Disable all CAM entry */
+       ctrl_outl(0, ioaddr + TSU_POST1);       /* Disable CAM entry [ 0- 7] */
+       ctrl_outl(0, ioaddr + TSU_POST2);       /* Disable CAM entry [ 8-15] */
+       ctrl_outl(0, ioaddr + TSU_POST3);       /* Disable CAM entry [16-23] */
+       ctrl_outl(0, ioaddr + TSU_POST4);       /* Disable CAM entry [24-31] */
+}
+
+/* MDIO bus release function */
+static int sh_mdio_release(struct net_device *ndev)
+{
+       struct mii_bus *bus = dev_get_drvdata(&ndev->dev);
+
+       /* unregister mdio bus */
+       mdiobus_unregister(bus);
+
+       /* remove mdio bus info from net_device */
+       dev_set_drvdata(&ndev->dev, NULL);
+
+       /* free bitbang info */
+       free_mdio_bitbang(bus);
+
+       return 0;
+}
+
+/* MDIO bus init function */
+static int sh_mdio_init(struct net_device *ndev, int id)
+{
+       int ret, i;
+       struct bb_info *bitbang;
+       struct sh_eth_private *mdp = netdev_priv(ndev);
+
+       /* create bit control struct for PHY */
+       bitbang = kzalloc(sizeof(struct bb_info), GFP_KERNEL);
+       if (!bitbang) {
+               ret = -ENOMEM;
+               goto out;
+       }
+
+       /* bitbang init */
+       bitbang->addr = ndev->base_addr + PIR;
+       bitbang->mdi_msk = 0x08;
+       bitbang->mdo_msk = 0x04;
+       bitbang->mmd_msk = 0x02;/* MMD */
+       bitbang->mdc_msk = 0x01;
+       bitbang->ctrl.ops = &bb_ops;
+
+       /* MII contorller setting */
+       mdp->mii_bus = alloc_mdio_bitbang(&bitbang->ctrl);
+       if (!mdp->mii_bus) {
+               ret = -ENOMEM;
+               goto out_free_bitbang;
+       }
+
+       /* Hook up MII support for ethtool */
+       mdp->mii_bus->name = "sh_mii";
+       mdp->mii_bus->dev = &ndev->dev;
+       mdp->mii_bus->id = id;
+
+       /* PHY IRQ */
+       mdp->mii_bus->irq = kmalloc(sizeof(int)*PHY_MAX_ADDR, GFP_KERNEL);
+       if (!mdp->mii_bus->irq) {
+               ret = -ENOMEM;
+               goto out_free_bus;
+       }
+
+       for (i = 0; i < PHY_MAX_ADDR; i++)
+               mdp->mii_bus->irq[i] = PHY_POLL;
+
+       /* regist mdio bus */
+       ret = mdiobus_register(mdp->mii_bus);
+       if (ret)
+               goto out_free_irq;
+
+       dev_set_drvdata(&ndev->dev, mdp->mii_bus);
+
+       return 0;
+
+out_free_irq:
+       kfree(mdp->mii_bus->irq);
+
+out_free_bus:
+       kfree(mdp->mii_bus);
+
+out_free_bitbang:
+       kfree(bitbang);
+
+out:
+       return ret;
+}
+
+static int sh_eth_drv_probe(struct platform_device *pdev)
+{
+       int ret, i, devno = 0;
+       struct resource *res;
+       struct net_device *ndev = NULL;
+       struct sh_eth_private *mdp;
+
+       /* get base addr */
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (unlikely(res == NULL)) {
+               dev_err(&pdev->dev, "invalid resource\n");
+               ret = -EINVAL;
+               goto out;
+       }
+
+       ndev = alloc_etherdev(sizeof(struct sh_eth_private));
+       if (!ndev) {
+               printk(KERN_ERR "%s: could not allocate device.\n", CARDNAME);
+               ret = -ENOMEM;
+               goto out;
+       }
+
+       /* The sh Ether-specific entries in the device structure. */
+       ndev->base_addr = res->start;
+       devno = pdev->id;
+       if (devno < 0)
+               devno = 0;
+
+       ndev->dma = -1;
+       ndev->irq = platform_get_irq(pdev, 0);
+       if (ndev->irq < 0) {
+               ret = -ENODEV;
+               goto out_release;
+       }
+
+       SET_NETDEV_DEV(ndev, &pdev->dev);
+
+       /* Fill in the fields of the device structure with ethernet values. */
+       ether_setup(ndev);
+
+       mdp = netdev_priv(ndev);
+       spin_lock_init(&mdp->lock);
+
+       /* get PHY ID */
+       mdp->phy_id = (int)pdev->dev.platform_data;
+
+       /* set function */
+       ndev->open = sh_eth_open;
+       ndev->hard_start_xmit = sh_eth_start_xmit;
+       ndev->stop = sh_eth_close;
+       ndev->get_stats = sh_eth_get_stats;
+       ndev->set_multicast_list = sh_eth_set_multicast_list;
+       ndev->do_ioctl = sh_eth_do_ioctl;
+       ndev->tx_timeout = sh_eth_tx_timeout;
+       ndev->watchdog_timeo = TX_TIMEOUT;
+
+       mdp->post_rx = POST_RX >> (devno << 1);
+       mdp->post_fw = POST_FW >> (devno << 1);
+
+       /* read and set MAC address */
+       read_mac_address(ndev);
+
+       /* First device only init */
+       if (!devno) {
+               /* reset device */
+               ctrl_outl(ARSTR_ARSTR, ndev->base_addr + ARSTR);
+               mdelay(1);
+
+               /* TSU init (Init only)*/
+               sh_eth_tsu_init(SH_TSU_ADDR);
+       }
+
+       /* network device register */
+       ret = register_netdev(ndev);
+       if (ret)
+               goto out_release;
+
+       /* mdio bus init */
+       ret = sh_mdio_init(ndev, pdev->id);
+       if (ret)
+               goto out_unregister;
+
+       /* pritnt device infomation */
+       printk(KERN_INFO "%s: %s at 0x%x, ",
+              ndev->name, CARDNAME, (u32) ndev->base_addr);
+
+       for (i = 0; i < 5; i++)
+               printk(KERN_INFO "%2.2x:", ndev->dev_addr[i]);
+       printk(KERN_INFO "%2.2x, IRQ %d.\n", ndev->dev_addr[i], ndev->irq);
+
+       platform_set_drvdata(pdev, ndev);
+
+       return ret;
+
+out_unregister:
+       unregister_netdev(ndev);
+
+out_release:
+       /* net_dev free */
+       if (ndev)
+               free_netdev(ndev);
+
+out:
+       return ret;
+}
+
+static int sh_eth_drv_remove(struct platform_device *pdev)
+{
+       struct net_device *ndev = platform_get_drvdata(pdev);
+
+       sh_mdio_release(ndev);
+       unregister_netdev(ndev);
+       flush_scheduled_work();
+
+       free_netdev(ndev);
+       platform_set_drvdata(pdev, NULL);
+
+       return 0;
+}
+
+static struct platform_driver sh_eth_driver = {
+       .probe = sh_eth_drv_probe,
+       .remove = sh_eth_drv_remove,
+       .driver = {
+                  .name = CARDNAME,
+       },
+};
+
+static int __init sh_eth_init(void)
+{
+       return platform_driver_register(&sh_eth_driver);
+}
+
+static void __exit sh_eth_cleanup(void)
+{
+       platform_driver_unregister(&sh_eth_driver);
+}
+
+module_init(sh_eth_init);
+module_exit(sh_eth_cleanup);
+
+MODULE_AUTHOR("Nobuhiro Iwamatsu, Yoshihiro Shimoda");
+MODULE_DESCRIPTION("Renesas SuperH Ethernet driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/sh_eth.h b/drivers/net/sh_eth.h
new file mode 100644 (file)
index 0000000..ca2db6b
--- /dev/null
@@ -0,0 +1,464 @@
+/*
+ *  SuperH Ethernet device driver
+ *
+ *  Copyright (C) 2006-2008 Nobuhiro Iwamatsu
+ *  Copyright (C) 2008 Renesas Solutions Corp.
+ *
+ *  This program is free software; you can redistribute it and/or modify it
+ *  under the terms and conditions of the GNU General Public License,
+ *  version 2, as published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope it will be useful, but WITHOUT
+ *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ *  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ *  more details.
+ *  You should have received a copy of the GNU General Public License along with
+ *  this program; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ *  The full GNU General Public License is included in this distribution in
+ *  the file called "COPYING".
+ */
+
+#ifndef __SH_ETH_H__
+#define __SH_ETH_H__
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+#include <linux/netdevice.h>
+#include <linux/phy.h>
+
+#define CARDNAME       "sh-eth"
+#define TX_TIMEOUT     (5*HZ)
+
+#define TX_RING_SIZE   128     /* Tx ring size */
+#define RX_RING_SIZE   128     /* Rx ring size */
+#define RX_OFFSET              2       /* skb offset */
+#define ETHERSMALL             60
+#define PKT_BUF_SZ             1538
+
+/* Chip Base Address */
+#define SH_ETH0_BASE 0xA7000000
+#define SH_ETH1_BASE 0xA7000400
+#define SH_TSU_ADDR 0xA7000804
+
+/* Chip Registers */
+/* E-DMAC */
+#define EDMR   0x0000
+#define EDTRR  0x0004
+#define EDRRR  0x0008
+#define TDLAR  0x000C
+#define RDLAR  0x0010
+#define EESR   0x0014
+#define EESIPR 0x0018
+#define TRSCER 0x001C
+#define RMFCR  0x0020
+#define TFTR   0x0024
+#define FDR            0x0028
+#define RMCR   0x002C
+#define EDOCR  0x0030
+#define FCFTR  0x0034
+#define RPADIR 0x0038
+#define TRIMD  0x003C
+#define RBWAR  0x0040
+#define RDFAR  0x0044
+#define TBRAR  0x004C
+#define TDFAR  0x0050
+/* Ether Register */
+#define ECMR   0x0160
+#define ECSR   0x0164
+#define ECSIPR 0x0168
+#define PIR            0x016C
+#define MAHR   0x0170
+#define MALR   0x0174
+#define RFLR   0x0178
+#define PSR            0x017C
+#define TROCR  0x0180
+#define CDCR   0x0184
+#define LCCR   0x0188
+#define CNDCR  0x018C
+#define CEFCR  0x0194
+#define FRECR  0x0198
+#define TSFRCR 0x019C
+#define TLFRCR 0x01A0
+#define RFCR   0x01A4
+#define MAFCR  0x01A8
+#define IPGR   0x01B4
+#if defined(CONFIG_CPU_SUBTYPE_SH7710)
+#define APR            0x01B8
+#define MPR    0x01BC
+#define TPAUSER 0x1C4
+#define BCFR   0x1CC
+#endif /* CONFIG_CPU_SH7710 */
+
+#define ARSTR  0x0800
+
+/* TSU */
+#define TSU_CTRST      0x004
+#define TSU_FWEN0      0x010
+#define TSU_FWEN1      0x014
+#define TSU_FCM                0x018
+#define TSU_BSYSL0     0x020
+#define TSU_BSYSL1     0x024
+#define TSU_PRISL0     0x028
+#define TSU_PRISL1     0x02C
+#define TSU_FWSL0      0x030
+#define TSU_FWSL1      0x034
+#define TSU_FWSLC      0x038
+#define TSU_QTAGM0     0x040
+#define TSU_QTAGM1     0x044
+#define TSU_ADQT0      0x048
+#define TSU_ADQT1      0x04C
+#define TSU_FWSR       0x050
+#define TSU_FWINMK     0x054
+#define TSU_ADSBSY     0x060
+#define TSU_TEN                0x064
+#define TSU_POST1      0x070
+#define TSU_POST2      0x074
+#define TSU_POST3      0x078
+#define TSU_POST4      0x07C
+#define TXNLCR0                0x080
+#define TXALCR0                0x084
+#define RXNLCR0                0x088
+#define RXALCR0                0x08C
+#define FWNLCR0                0x090
+#define FWALCR0                0x094
+#define TXNLCR1                0x0A0
+#define TXALCR1                0x0A4
+#define RXNLCR1                0x0A8
+#define RXALCR1                0x0AC
+#define FWNLCR1                0x0B0
+#define FWALCR1                0x0B4
+
+#define TSU_ADRH0      0x0100
+#define TSU_ADRL0      0x0104
+#define TSU_ADRL31     0x01FC
+
+/* Register's bits */
+
+/* EDMR */
+enum DMAC_M_BIT {
+       EDMR_DL1 = 0x20, EDMR_DL0 = 0x10, EDMR_SRST = 0x01,
+};
+
+/* EDTRR */
+enum DMAC_T_BIT {
+       EDTRR_TRNS = 0x01,
+};
+
+/* EDRRR*/
+enum EDRRR_R_BIT {
+       EDRRR_R = 0x01,
+};
+
+/* TPAUSER */
+enum TPAUSER_BIT {
+       TPAUSER_TPAUSE = 0x0000ffff,
+       TPAUSER_UNLIMITED = 0,
+};
+
+/* BCFR */
+enum BCFR_BIT {
+       BCFR_RPAUSE = 0x0000ffff,
+       BCFR_UNLIMITED = 0,
+};
+
+/* PIR */
+enum PIR_BIT {
+       PIR_MDI = 0x08, PIR_MDO = 0x04, PIR_MMD = 0x02, PIR_MDC = 0x01,
+};
+
+/* PSR */
+enum PHY_STATUS_BIT { PHY_ST_LINK = 0x01, };
+
+/* EESR */
+enum EESR_BIT {
+       EESR_TWB = 0x40000000, EESR_TABT = 0x04000000,
+       EESR_RABT = 0x02000000, EESR_RFRMER = 0x01000000,
+       EESR_ADE = 0x00800000, EESR_ECI = 0x00400000,
+       EESR_FTC = 0x00200000, EESR_TDE = 0x00100000,
+       EESR_TFE = 0x00080000, EESR_FRC = 0x00040000,
+       EESR_RDE = 0x00020000, EESR_RFE = 0x00010000,
+       EESR_TINT4 = 0x00000800, EESR_TINT3 = 0x00000400,
+       EESR_TINT2 = 0x00000200, EESR_TINT1 = 0x00000100,
+       EESR_RINT8 = 0x00000080, EESR_RINT5 = 0x00000010,
+       EESR_RINT4 = 0x00000008, EESR_RINT3 = 0x00000004,
+       EESR_RINT2 = 0x00000002, EESR_RINT1 = 0x00000001,
+};
+
+#define EESR_ERR_CHECK (EESR_TWB | EESR_TABT | EESR_RABT | EESR_RDE \
+               | EESR_RFRMER | EESR_ADE | EESR_TFE | EESR_TDE | EESR_ECI)
+
+/* EESIPR */
+enum DMAC_IM_BIT {
+       DMAC_M_TWB = 0x40000000, DMAC_M_TABT = 0x04000000,
+       DMAC_M_RABT = 0x02000000,
+       DMAC_M_RFRMER = 0x01000000, DMAC_M_ADF = 0x00800000,
+       DMAC_M_ECI = 0x00400000, DMAC_M_FTC = 0x00200000,
+       DMAC_M_TDE = 0x00100000, DMAC_M_TFE = 0x00080000,
+       DMAC_M_FRC = 0x00040000, DMAC_M_RDE = 0x00020000,
+       DMAC_M_RFE = 0x00010000, DMAC_M_TINT4 = 0x00000800,
+       DMAC_M_TINT3 = 0x00000400, DMAC_M_TINT2 = 0x00000200,
+       DMAC_M_TINT1 = 0x00000100, DMAC_M_RINT8 = 0x00000080,
+       DMAC_M_RINT5 = 0x00000010, DMAC_M_RINT4 = 0x00000008,
+       DMAC_M_RINT3 = 0x00000004, DMAC_M_RINT2 = 0x00000002,
+       DMAC_M_RINT1 = 0x00000001,
+};
+
+/* Receive descriptor bit */
+enum RD_STS_BIT {
+       RD_RACT = 0x80000000, RC_RDEL = 0x40000000,
+       RC_RFP1 = 0x20000000, RC_RFP0 = 0x10000000,
+       RD_RFE = 0x08000000, RD_RFS10 = 0x00000200,
+       RD_RFS9 = 0x00000100, RD_RFS8 = 0x00000080,
+       RD_RFS7 = 0x00000040, RD_RFS6 = 0x00000020,
+       RD_RFS5 = 0x00000010, RD_RFS4 = 0x00000008,
+       RD_RFS3 = 0x00000004, RD_RFS2 = 0x00000002,
+       RD_RFS1 = 0x00000001,
+};
+#define RDF1ST RC_RFP1
+#define RDFEND RC_RFP0
+#define RD_RFP (RC_RFP1|RC_RFP0)
+
+/* FCFTR */
+enum FCFTR_BIT {
+       FCFTR_RFF2 = 0x00040000, FCFTR_RFF1 = 0x00020000,
+       FCFTR_RFF0 = 0x00010000, FCFTR_RFD2 = 0x00000004,
+       FCFTR_RFD1 = 0x00000002, FCFTR_RFD0 = 0x00000001,
+};
+#define FIFO_F_D_RFF   (FCFTR_RFF2|FCFTR_RFF1|FCFTR_RFF0)
+#define FIFO_F_D_RFD   (FCFTR_RFD2|FCFTR_RFD1|FCFTR_RFD0)
+
+/* Transfer descriptor bit */
+enum TD_STS_BIT {
+       TD_TACT = 0x80000000, TD_TDLE = 0x40000000, TD_TFP1 = 0x20000000,
+       TD_TFP0 = 0x10000000,
+};
+#define TDF1ST TD_TFP1
+#define TDFEND TD_TFP0
+#define TD_TFP (TD_TFP1|TD_TFP0)
+
+/* RMCR */
+enum RECV_RST_BIT { RMCR_RST = 0x01, };
+/* ECMR */
+enum FELIC_MODE_BIT {
+       ECMR_ZPF = 0x00080000, ECMR_PFR = 0x00040000, ECMR_RXF = 0x00020000,
+       ECMR_TXF = 0x00010000, ECMR_MCT = 0x00002000, ECMR_PRCEF = 0x00001000,
+       ECMR_PMDE = 0x00000200, ECMR_RE = 0x00000040, ECMR_TE = 0x00000020,
+       ECMR_ILB = 0x00000008, ECMR_ELB = 0x00000004, ECMR_DM = 0x00000002,
+       ECMR_PRM = 0x00000001,
+};
+
+/* ECSR */
+enum ECSR_STATUS_BIT {
+       ECSR_BRCRX = 0x20, ECSR_PSRTO = 0x10, ECSR_LCHNG = 0x04,
+       ECSR_MPD = 0x02, ECSR_ICD = 0x01,
+};
+
+/* ECSIPR */
+enum ECSIPR_STATUS_MASK_BIT {
+       ECSIPR_BRCRXIP = 0x20, ECSIPR_PSRTOIP = 0x10, ECSIPR_LCHNGIP = 0x04,
+       ECSIPR_MPDIP = 0x02, ECSIPR_ICDIP = 0x01,
+};
+
+/* APR */
+enum APR_BIT {
+       APR_AP = 0x00000001,
+};
+
+/* MPR */
+enum MPR_BIT {
+       MPR_MP = 0x00000001,
+};
+
+/* TRSCER */
+enum DESC_I_BIT {
+       DESC_I_TINT4 = 0x0800, DESC_I_TINT3 = 0x0400, DESC_I_TINT2 = 0x0200,
+       DESC_I_TINT1 = 0x0100, DESC_I_RINT8 = 0x0080, DESC_I_RINT5 = 0x0010,
+       DESC_I_RINT4 = 0x0008, DESC_I_RINT3 = 0x0004, DESC_I_RINT2 = 0x0002,
+       DESC_I_RINT1 = 0x0001,
+};
+
+/* RPADIR */
+enum RPADIR_BIT {
+       RPADIR_PADS1 = 0x20000, RPADIR_PADS0 = 0x10000,
+       RPADIR_PADR = 0x0003f,
+};
+
+/* FDR */
+enum FIFO_SIZE_BIT {
+       FIFO_SIZE_T = 0x00000700, FIFO_SIZE_R = 0x00000007,
+};
+enum phy_offsets {
+       PHY_CTRL = 0, PHY_STAT = 1, PHY_IDT1 = 2, PHY_IDT2 = 3,
+       PHY_ANA = 4, PHY_ANL = 5, PHY_ANE = 6,
+       PHY_16 = 16,
+};
+
+/* PHY_CTRL */
+enum PHY_CTRL_BIT {
+       PHY_C_RESET = 0x8000, PHY_C_LOOPBK = 0x4000, PHY_C_SPEEDSL = 0x2000,
+       PHY_C_ANEGEN = 0x1000, PHY_C_PWRDN = 0x0800, PHY_C_ISO = 0x0400,
+       PHY_C_RANEG = 0x0200, PHY_C_DUPLEX = 0x0100, PHY_C_COLT = 0x0080,
+};
+#define DM9161_PHY_C_ANEGEN 0  /* auto nego special */
+
+/* PHY_STAT */
+enum PHY_STAT_BIT {
+       PHY_S_100T4 = 0x8000, PHY_S_100X_F = 0x4000, PHY_S_100X_H = 0x2000,
+       PHY_S_10T_F = 0x1000, PHY_S_10T_H = 0x0800, PHY_S_ANEGC = 0x0020,
+       PHY_S_RFAULT = 0x0010, PHY_S_ANEGA = 0x0008, PHY_S_LINK = 0x0004,
+       PHY_S_JAB = 0x0002, PHY_S_EXTD = 0x0001,
+};
+
+/* PHY_ANA */
+enum PHY_ANA_BIT {
+       PHY_A_NP = 0x8000, PHY_A_ACK = 0x4000, PHY_A_RF = 0x2000,
+       PHY_A_FCS = 0x0400, PHY_A_T4 = 0x0200, PHY_A_FDX = 0x0100,
+       PHY_A_HDX = 0x0080, PHY_A_10FDX = 0x0040, PHY_A_10HDX = 0x0020,
+       PHY_A_SEL = 0x001f,
+};
+/* PHY_ANL */
+enum PHY_ANL_BIT {
+       PHY_L_NP = 0x8000, PHY_L_ACK = 0x4000, PHY_L_RF = 0x2000,
+       PHY_L_FCS = 0x0400, PHY_L_T4 = 0x0200, PHY_L_FDX = 0x0100,
+       PHY_L_HDX = 0x0080, PHY_L_10FDX = 0x0040, PHY_L_10HDX = 0x0020,
+       PHY_L_SEL = 0x001f,
+};
+
+/* PHY_ANE */
+enum PHY_ANE_BIT {
+       PHY_E_PDF = 0x0010, PHY_E_LPNPA = 0x0008, PHY_E_NPA = 0x0004,
+       PHY_E_PRX = 0x0002, PHY_E_LPANEGA = 0x0001,
+};
+
+/* DM9161 */
+enum PHY_16_BIT {
+       PHY_16_BP4B45 = 0x8000, PHY_16_BPSCR = 0x4000, PHY_16_BPALIGN = 0x2000,
+       PHY_16_BP_ADPOK = 0x1000, PHY_16_Repeatmode = 0x0800,
+       PHY_16_TXselect = 0x0400,
+       PHY_16_Rsvd = 0x0200, PHY_16_RMIIEnable = 0x0100,
+       PHY_16_Force100LNK = 0x0080,
+       PHY_16_APDLED_CTL = 0x0040, PHY_16_COLLED_CTL = 0x0020,
+       PHY_16_RPDCTR_EN = 0x0010,
+       PHY_16_ResetStMch = 0x0008, PHY_16_PreamSupr = 0x0004,
+       PHY_16_Sleepmode = 0x0002,
+       PHY_16_RemoteLoopOut = 0x0001,
+};
+
+#define POST_RX                0x08
+#define POST_FW                0x04
+#define POST0_RX       (POST_RX)
+#define POST0_FW       (POST_FW)
+#define POST1_RX       (POST_RX >> 2)
+#define POST1_FW       (POST_FW >> 2)
+#define POST_ALL       (POST0_RX | POST0_FW | POST1_RX | POST1_FW)
+
+/* ARSTR */
+enum ARSTR_BIT { ARSTR_ARSTR = 0x00000001, };
+
+/* TSU_FWEN0 */
+enum TSU_FWEN0_BIT {
+       TSU_FWEN0_0 = 0x00000001,
+};
+
+/* TSU_ADSBSY */
+enum TSU_ADSBSY_BIT {
+       TSU_ADSBSY_0 = 0x00000001,
+};
+
+/* TSU_TEN */
+enum TSU_TEN_BIT {
+       TSU_TEN_0 = 0x80000000,
+};
+
+/* TSU_FWSL0 */
+enum TSU_FWSL0_BIT {
+       TSU_FWSL0_FW50 = 0x1000, TSU_FWSL0_FW40 = 0x0800,
+       TSU_FWSL0_FW30 = 0x0400, TSU_FWSL0_FW20 = 0x0200,
+       TSU_FWSL0_FW10 = 0x0100, TSU_FWSL0_RMSA0 = 0x0010,
+};
+
+/* TSU_FWSLC */
+enum TSU_FWSLC_BIT {
+       TSU_FWSLC_POSTENU = 0x2000, TSU_FWSLC_POSTENL = 0x1000,
+       TSU_FWSLC_CAMSEL03 = 0x0080, TSU_FWSLC_CAMSEL02 = 0x0040,
+       TSU_FWSLC_CAMSEL01 = 0x0020, TSU_FWSLC_CAMSEL00 = 0x0010,
+       TSU_FWSLC_CAMSEL13 = 0x0008, TSU_FWSLC_CAMSEL12 = 0x0004,
+       TSU_FWSLC_CAMSEL11 = 0x0002, TSU_FWSLC_CAMSEL10 = 0x0001,
+};
+
+/*
+ * The sh ether Tx buffer descriptors.
+ * This structure should be 20 bytes.
+ */
+struct sh_eth_txdesc {
+       u32 status;             /* TD0 */
+#if defined(CONFIG_CPU_LITTLE_ENDIAN)
+       u16 pad0;               /* TD1 */
+       u16 buffer_length;      /* TD1 */
+#else
+       u16 buffer_length;      /* TD1 */
+       u16 pad0;               /* TD1 */
+#endif
+       u32 addr;               /* TD2 */
+       u32 pad1;               /* padding data */
+};
+
+/*
+ * The sh ether Rx buffer descriptors.
+ * This structure should be 20 bytes.
+ */
+struct sh_eth_rxdesc {
+       u32 status;             /* RD0 */
+#if defined(CONFIG_CPU_LITTLE_ENDIAN)
+       u16 frame_length;       /* RD1 */
+       u16 buffer_length;      /* RD1 */
+#else
+       u16 buffer_length;      /* RD1 */
+       u16 frame_length;       /* RD1 */
+#endif
+       u32 addr;               /* RD2 */
+       u32 pad0;               /* padding data */
+};
+
+struct sh_eth_private {
+       dma_addr_t rx_desc_dma;
+       dma_addr_t tx_desc_dma;
+       struct sh_eth_rxdesc *rx_ring;
+       struct sh_eth_txdesc *tx_ring;
+       struct sk_buff **rx_skbuff;
+       struct sk_buff **tx_skbuff;
+       struct net_device_stats stats;
+       struct timer_list timer;
+       spinlock_t lock;
+       u32 cur_rx, dirty_rx;   /* Producer/consumer ring indices */
+       u32 cur_tx, dirty_tx;
+       u32 rx_buf_sz;          /* Based on MTU+slack. */
+       /* MII transceiver section. */
+       u32 phy_id;                                     /* PHY ID */
+       struct mii_bus *mii_bus;        /* MDIO bus control */
+       struct phy_device *phydev;      /* PHY device control */
+       enum phy_state link;
+       int msg_enable;
+       int speed;
+       int duplex;
+       u32 rx_int_var, tx_int_var;     /* interrupt control variables */
+       char post_rx;           /* POST receive */
+       char post_fw;           /* POST forward */
+       struct net_device_stats tsu_stats;      /* TSU forward status */
+};
+
+static void swaps(char *src, int len)
+{
+#ifdef __LITTLE_ENDIAN__
+       u32 *p = (u32 *)src;
+       u32 *maxp;
+       maxp = p + ((len + sizeof(u32) - 1) / sizeof(u32));
+
+       for (; p < maxp; p++)
+               *p = swab32(*p);
+#endif
+}