amazon: update amazon target to kernel 3.3
[openwrt/svn-archive/archive.git] / target / linux / amazon / files / drivers / net / ethernet / amazon_sw.c
diff --git a/target/linux/amazon/files/drivers/net/ethernet/amazon_sw.c b/target/linux/amazon/files/drivers/net/ethernet/amazon_sw.c
new file mode 100644 (file)
index 0000000..d18b439
--- /dev/null
@@ -0,0 +1,899 @@
+/*
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
+ */
+//-----------------------------------------------------------------------
+/*
+ * Description:
+ *     Driver for Infineon Amazon 3 port switch
+ */
+//-----------------------------------------------------------------------
+/* Author:     Wu Qi Ming[Qi-Ming.Wu@infineon.com]
+ * Created:    7-April-2004
+ */
+//-----------------------------------------------------------------------
+/* History
+ * Changed on: Jun 28, 2004
+ * Changed by: peng.liu@infineon.com
+ * Reason:     add hardware flow control (HFC) (CONFIG_NET_HW_FLOWCONTROL)
+ *
+ * Changed on: Apr 6, 2005
+ * Changed by: mars.lin@infineon.com
+ * Reason    : supoort port identification
+ */
+
+
+// copyright 2004-2005 infineon.com
+
+// copyright 2007 john crispin <blogic@openwrt.org>
+// copyright 2007 felix fietkau <nbd@openwrt.org>
+// copyright 2009 hauke mehrtens <hauke@hauke-m.de>
+
+
+// TODO
+//             port vlan code from bcrm target... the tawainese code was scrapped due to crappyness
+//             check all the mmi reg settings and possibly document them better
+//             verify the ethtool code
+//             remove the while(1) stuff
+//             further clean up and rework ... but it works for now
+//             check the mode[]=bridge stuff
+//             verify that the ethaddr can be set from u-boot
+
+
+#ifndef __KERNEL__
+#define __KERNEL__
+#endif
+
+
+#if defined(CONFIG_MODVERSIONS) && !defined(MODVERSIONS)
+#define MODVERSIONS
+#endif
+
+#if defined(MODVERSIONS) && !defined(__GENKSYMS__)
+#include <linux/modversions.h>
+#endif
+
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/sched.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/interrupt.h>
+#include <linux/mii.h>
+#include <asm/uaccess.h>
+#include <linux/in.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/ip.h>
+#include <linux/tcp.h>
+#include <linux/skbuff.h>
+#include <linux/in6.h>
+#include <linux/proc_fs.h>
+#include <linux/mm.h>
+#include <linux/ethtool.h>
+#include <asm/checksum.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+
+#include <asm/amazon/amazon.h>
+#include <asm/amazon/amazon_dma.h>
+#include <asm/amazon/amazon_sw.h>
+
+// how many mii ports are there ?
+#define AMAZON_SW_INT_NO 2
+
+#define ETHERNET_PACKET_DMA_BUFFER_SIZE 1536
+
+/***************************************** Module Parameters *************************************/
+static char mode[] = "bridge";
+module_param_array(mode, charp, NULL, 0);
+
+static int timeout = 1 * HZ;
+module_param(timeout, int, 0);
+
+int switch_init(struct net_device *dev);
+void switch_tx_timeout(struct net_device *dev);
+
+static struct net_device *switch_devs[2];
+
+int add_mac_table_entry(u64 entry_value)
+{
+       int i;
+       u32 data1, data2;
+
+       AMAZON_SW_REG32(AMAZON_SW_ARL_CTL) = ~7;
+
+       for (i = 0; i < 32; i++) {
+               AMAZON_SW_REG32(AMAZON_SW_CPU_ACTL) = 0x80000000 | 0x20 | i;
+               while (AMAZON_SW_REG32(AMAZON_SW_CPU_ACTL) & (0x80000000)) {};
+               data1 = AMAZON_SW_REG32(AMAZON_SW_DATA1);
+               data2 = AMAZON_SW_REG32(AMAZON_SW_DATA2);
+               if ((data1 & (0x00700000)) != 0x00700000)
+                       continue;
+               AMAZON_SW_REG32(AMAZON_SW_DATA1) = (u32) (entry_value >> 32);
+               AMAZON_SW_REG32(AMAZON_SW_DATA2) = (u32) entry_value & 0xffffffff;
+               AMAZON_SW_REG32(AMAZON_SW_CPU_ACTL) = 0xc0000020 | i;
+               while (AMAZON_SW_REG32(AMAZON_SW_CPU_ACTL) & (0x80000000)) {};
+               break;
+       }
+       AMAZON_SW_REG32(AMAZON_SW_ARL_CTL) |= 7;
+       if (i >= 32)
+               return -1;
+       return OK;
+}
+
+u64 read_mac_table_entry(int index)
+{
+       u32 data1, data2;
+       u64 value;
+       AMAZON_SW_REG32(AMAZON_SW_CPU_ACTL) = 0x80000000 | 0x20 | index;
+       while (AMAZON_SW_REG32(AMAZON_SW_CPU_ACTL) & (0x80000000)) {};
+       data1 = AMAZON_SW_REG32(AMAZON_SW_DATA1) & 0xffffff;
+       data2 = AMAZON_SW_REG32(AMAZON_SW_DATA2);
+       value = (u64) data1 << 32 | (u64) data2;
+       return value;
+}
+
+int write_mac_table_entry(int index, u64 value)
+{
+       u32 data1, data2;
+       data1 = (u32) (value >> 32);
+       data2 = (u32) value & 0xffffffff;
+       AMAZON_SW_REG32(AMAZON_SW_DATA1) = data1;
+       AMAZON_SW_REG32(AMAZON_SW_DATA2) = data2;
+       AMAZON_SW_REG32(AMAZON_SW_CPU_ACTL) = 0xc0000020 | index;
+       while (AMAZON_SW_REG32(AMAZON_SW_CPU_ACTL) & (0x80000000)) {};
+       return OK;
+}
+
+u32 get_mdio_reg(int phy_addr, int reg_num)
+{
+       u32 value;
+       AMAZON_SW_REG32(AMAZON_SW_MDIO_ACC) = (3 << 30) | ((phy_addr & 0x1f) << 21) | ((reg_num & 0x1f) << 16);
+       while (AMAZON_SW_REG32(AMAZON_SW_MDIO_ACC) & (1 << 31)) {};
+       value = AMAZON_SW_REG32(AMAZON_SW_MDIO_ACC) & 0xffff;
+       return value;
+}
+
+int set_mdio_reg(int phy_addr, int reg_num, u32 value)
+{
+       AMAZON_SW_REG32(AMAZON_SW_MDIO_ACC) = (2 << 30) | ((phy_addr & 0x1f) << 21) | ((reg_num & 0x1f) << 16) | (value & 0xffff);
+       while (AMAZON_SW_REG32(AMAZON_SW_MDIO_ACC) & (1 << 31)) {};
+       return OK;
+}
+
+int auto_negotiate(int phy_addr)
+{
+       u32 value = 0;
+       value = get_mdio_reg(phy_addr, MDIO_BASE_CONTROL_REG);
+       set_mdio_reg(phy_addr, MDIO_BASE_CONTROL_REG, (value | RESTART_AUTO_NEGOTIATION | AUTO_NEGOTIATION_ENABLE | PHY_RESET));
+       return OK;
+}
+
+/*
+     In this version of switch driver, we split the dma channels for the switch.
+     2 for port0 and 2 for port1. So that we can do internal bridging if necessary.
+     In switch mode, packets coming in from port0 or port1 is able to do Destination 
+     address lookup. Packets coming from port0 with destination address of port1 should 
+     not go to pmac again. The switch hardware should be able to do the switch in the hard 
+     ware level. Packets coming from the pmac should not do the DA look up in that the
+     desination is already known for the kernel. It only needs to go to the correct NIC to 
+     find its way out.
+  */
+int amazon_sw_chip_init(void)
+{
+       u32 tmp1;
+       int i = 0;
+
+       /* Aging tick select: 5mins */
+       tmp1 = 0xa0;
+       if (strcmp(mode, "bridge") == 0) {
+               // bridge mode, set militarised mode to 1, no learning!
+               tmp1 |= 0xC00;
+       } else {
+               // enable learning for P0 and P1,
+               tmp1 |= 3;
+       }
+
+       /* unknown broadcast/multicast/unicast to all ports */
+       AMAZON_SW_REG32(AMAZON_SW_UN_DEST) = 0x1ff;
+
+       AMAZON_SW_REG32(AMAZON_SW_ARL_CTL) = tmp1;
+
+       /* OCS:1 set OCS bit, split the two NIC in rx direction EDL:1 (enable DA lookup) */
+#if defined(CONFIG_IFX_NFEXT_AMAZON_SWITCH_PHYPORT) || defined(CONFIG_IFX_NFEXT_AMAZON_SWITCH_PHYPORT_MODULE)
+       AMAZON_SW_REG32(AMAZON_SW_P2_PCTL) = 0x700;
+#else
+       AMAZON_SW_REG32(AMAZON_SW_P2_PCTL) = 0x401;
+#endif
+
+       /* EPC: 1 split the two NIC in tx direction CRC is generated */
+       AMAZON_SW_REG32(AMAZON_SW_P2_CTL) = 0x6;
+
+       // for bi-directional 
+       AMAZON_SW_REG32(AMAZON_SW_P0_WM) = 0x14141412;
+       AMAZON_SW_REG32(AMAZON_SW_P1_WM) = 0x14141412;
+       AMAZON_SW_REG32(AMAZON_SW_P2_WM) = 0x28282826;
+       AMAZON_SW_REG32(AMAZON_SW_GBL_WM) = 0x0;
+
+       AMAZON_SW_REG32(AMAZON_CGU_PLL0SR) = (AMAZON_SW_REG32(AMAZON_CGU_PLL0SR)) | 0x58000000;
+       // clock for PHY
+       AMAZON_SW_REG32(AMAZON_CGU_IFCCR) =     (AMAZON_SW_REG32(AMAZON_CGU_IFCCR)) | 0x80000004;
+       // enable power for PHY
+       AMAZON_SW_REG32(AMAZON_PMU_PWDCR) = (AMAZON_SW_REG32(AMAZON_PMU_PWDCR)) | AMAZON_PMU_PWDCR_EPHY;
+       // set reverse MII, enable MDIO statemachine
+       AMAZON_SW_REG32(AMAZON_SW_MDIO_CFG) = 0x800027bf;
+       while (1)
+               if (((AMAZON_SW_REG32(AMAZON_SW_MDIO_CFG)) & 0x80000000) == 0)
+                       break;
+       AMAZON_SW_REG32(AMAZON_SW_EPHY) = 0xff;
+
+       // auto negotiation
+       AMAZON_SW_REG32(AMAZON_SW_MDIO_ACC) = 0x83e08000;
+       auto_negotiate(0x1f);
+
+       /* enable all ports */
+       AMAZON_SW_REG32(AMAZON_SW_PS_CTL) = 0x7;
+       for (i = 0; i < 32; i++)
+               write_mac_table_entry(i, 1 << 50);
+       return 0;
+}
+
+static unsigned char my_ethaddr[MAX_ADDR_LEN];
+/* need to get the ether addr from u-boot */
+static int __init ethaddr_setup(char *line)
+{
+       char *ep;
+       int i;
+
+       memset(my_ethaddr, 0, MAX_ADDR_LEN);
+       for (i = 0; i < 6; i++) {
+               my_ethaddr[i] = line ? simple_strtoul(line, &ep, 16) : 0;
+               if (line)
+                       line = (*ep) ? ep + 1 : ep;
+       }
+       printk(KERN_INFO "amazon_mii0: mac address %2x-%2x-%2x-%2x-%2x-%2x \n", my_ethaddr[0], my_ethaddr[1], my_ethaddr[2], my_ethaddr[3], my_ethaddr[4], my_ethaddr[5]);
+       return 0;
+}
+
+__setup("ethaddr=", ethaddr_setup);
+
+static void open_rx_dma(struct net_device *dev)
+{
+       struct switch_priv *priv = (struct switch_priv *) netdev_priv(dev);
+       struct dma_device_info *dma_dev = priv->dma_device;
+       int i;
+
+       for (i = 0; i < dma_dev->num_rx_chan; i++)
+               dma_dev->rx_chan[i].control = 1;
+       dma_device_update_rx(dma_dev);
+}
+
+#ifdef CONFIG_NET_HW_FLOWCONTROL
+static void close_rx_dma(struct net_device *dev)
+{
+       struct switch_priv *priv = (struct switch_priv *) netdev_priv(dev);
+       struct dma_device_info *dma_dev = priv->dma_device;
+       int i;
+
+       for (i = 0; i < dma_dev->num_rx_chan; i++)
+               dma_dev->rx_chan[i].control = 0;
+       dma_device_update_rx(dma_dev);
+}
+
+void amazon_xon(struct net_device *dev)
+{
+       unsigned long flag;
+       local_irq_save(flag);
+       open_rx_dma(dev);
+       local_irq_restore(flag);
+}
+#endif
+
+int switch_open(struct net_device *dev)
+{
+       struct switch_priv *priv = (struct switch_priv *) netdev_priv(dev);
+       if (!strcmp(dev->name, "eth1")) {
+               priv->mdio_phy_addr = PHY0_ADDR;
+       }
+       open_rx_dma(dev);                       
+
+#ifdef CONFIG_NET_HW_FLOWCONTROL
+       if ((priv->fc_bit = netdev_register_fc(dev, amazon_xon)) == 0) {
+               printk(KERN_WARNING "amazon_mii0: Hardware Flow Control register fails\n");
+       }
+#endif
+
+       netif_start_queue(dev);
+       return OK;
+}
+
+int switch_release(struct net_device *dev)
+{
+       int i;
+       struct switch_priv *priv = (struct switch_priv *) netdev_priv(dev);
+       struct dma_device_info *dma_dev = priv->dma_device;
+
+       for (i = 0; i < dma_dev->num_tx_chan; i++)
+               dma_dev->tx_chan[i].control = 0;
+       for (i = 0; i < dma_dev->num_rx_chan; i++)
+               dma_dev->rx_chan[i].control = 0;
+
+       dma_device_update(dma_dev);
+
+#ifdef CONFIG_NET_HW_FLOWCONTROL
+       if (priv->fc_bit) {
+               netdev_unregister_fc(priv->fc_bit);
+       }
+#endif
+       netif_stop_queue(dev);
+
+       return OK;
+}
+
+
+void switch_rx(struct net_device *dev, int len, struct sk_buff *skb)
+{
+       struct switch_priv *priv = (struct switch_priv *) netdev_priv(dev);
+#ifdef CONFIG_NET_HW_FLOWCONTROL
+       int mit_sel = 0;
+#endif
+       skb->dev = dev;
+       skb->protocol = eth_type_trans(skb, dev);
+
+#ifdef CONFIG_NET_HW_FLOWCONTROL
+       mit_sel = netif_rx(skb);
+       switch (mit_sel) {
+       case NET_RX_SUCCESS:
+       case NET_RX_CN_LOW:
+       case NET_RX_CN_MOD:
+               break;
+       case NET_RX_CN_HIGH:
+               break;
+       case NET_RX_DROP:
+               if ((priv->fc_bit)
+                       && (!test_and_set_bit(priv->fc_bit, &netdev_fc_xoff))) {
+                       close_rx_dma(dev);
+               }
+               break;
+       }
+#else
+       netif_rx(skb);
+#endif
+       priv->stats.rx_packets++;
+       priv->stats.rx_bytes += len;
+       return;
+}
+
+int asmlinkage switch_hw_tx(char *buf, int len, struct net_device *dev)
+{
+       struct switch_priv *priv = netdev_priv(dev);
+       struct dma_device_info *dma_dev = priv->dma_device;
+
+       dma_dev->current_tx_chan = 0;
+       return dma_device_write(dma_dev, buf, len, priv->skb);
+}
+
+int asmlinkage switch_tx(struct sk_buff *skb, struct net_device *dev)
+{
+       int len;
+       char *data;
+       struct switch_priv *priv = (struct switch_priv *) netdev_priv(dev);
+
+       len = skb->len < ETH_ZLEN ? ETH_ZLEN : skb->len;
+       data = skb->data;
+       priv->skb = skb;
+       dev->trans_start = jiffies;
+
+       if (switch_hw_tx(data, len, dev) != len) {
+               dev_kfree_skb_any(skb);
+               return OK;
+       }
+
+       priv->stats.tx_packets++;
+       priv->stats.tx_bytes += len;
+       return OK;
+}
+
+void switch_tx_timeout(struct net_device *dev)
+{
+       struct switch_priv *priv = (struct switch_priv *) netdev_priv(dev);
+       priv->stats.tx_errors++;
+       netif_wake_queue(dev);
+       return;
+}
+
+void negotiate(struct net_device *dev)
+{
+       struct switch_priv *priv = (struct switch_priv *) netdev_priv(dev);
+       unsigned short data = get_mdio_reg(priv->mdio_phy_addr, MDIO_ADVERTISMENT_REG);
+
+       data &= ~(MDIO_ADVERT_100_HD | MDIO_ADVERT_100_FD | MDIO_ADVERT_10_FD | MDIO_ADVERT_10_HD);
+
+       switch (priv->current_speed_selection) {
+       case 10:
+               if (priv->current_duplex == full)
+                       data |= MDIO_ADVERT_10_FD;
+               else if (priv->current_duplex == half)
+                       data |= MDIO_ADVERT_10_HD;
+               else
+                       data |= MDIO_ADVERT_10_HD | MDIO_ADVERT_10_FD;
+               break;
+
+       case 100:
+               if (priv->current_duplex == full)
+                       data |= MDIO_ADVERT_100_FD;
+               else if (priv->current_duplex == half)
+                       data |= MDIO_ADVERT_100_HD;
+               else
+                       data |= MDIO_ADVERT_100_HD | MDIO_ADVERT_100_FD;
+               break;
+
+       case 0:                                 /* Auto */
+               if (priv->current_duplex == full)
+                       data |= MDIO_ADVERT_100_FD | MDIO_ADVERT_10_FD;
+               else if (priv->current_duplex == half)
+                       data |= MDIO_ADVERT_100_HD | MDIO_ADVERT_10_HD;
+               else
+                       data |= MDIO_ADVERT_100_HD | MDIO_ADVERT_100_FD | MDIO_ADVERT_10_FD     | MDIO_ADVERT_10_HD;
+               break;
+
+       default:                                        /* assume autoneg speed and duplex */
+               data |= MDIO_ADVERT_100_HD | MDIO_ADVERT_100_FD | MDIO_ADVERT_10_FD | MDIO_ADVERT_10_HD;
+       }
+
+       set_mdio_reg(priv->mdio_phy_addr, MDIO_ADVERTISMENT_REG, data);
+
+       /* Renegotiate with link partner */
+
+       data = get_mdio_reg(priv->mdio_phy_addr, MDIO_BASE_CONTROL_REG);
+       data |= MDIO_BC_NEGOTIATE;
+
+       set_mdio_reg(priv->mdio_phy_addr, MDIO_BASE_CONTROL_REG, data);
+
+}
+
+
+void set_duplex(struct net_device *dev, enum duplex new_duplex)
+{
+       struct switch_priv *priv = (struct switch_priv *) netdev_priv(dev);
+       if (new_duplex != priv->current_duplex) {
+               priv->current_duplex = new_duplex;
+               negotiate(dev);
+       }
+}
+
+void set_speed(struct net_device *dev, unsigned long speed)
+{
+       struct switch_priv *priv = (struct switch_priv *) netdev_priv(dev);
+       priv->current_speed_selection = speed;
+       negotiate(dev);
+}
+
+static int switch_ethtool_ioctl(struct net_device *dev, struct ifreq *ifr)
+{
+       struct switch_priv *priv = (struct switch_priv *) netdev_priv(dev);
+       struct ethtool_cmd ecmd;
+
+       if (copy_from_user(&ecmd, ifr->ifr_data, sizeof(ecmd)))
+               return -EFAULT;
+
+       switch (ecmd.cmd) {
+       case ETHTOOL_GSET:
+               memset((void *) &ecmd, 0, sizeof(ecmd));
+               ecmd.supported = SUPPORTED_Autoneg | SUPPORTED_TP | SUPPORTED_MII |     SUPPORTED_10baseT_Half | SUPPORTED_10baseT_Full |
+                                               SUPPORTED_100baseT_Half | SUPPORTED_100baseT_Full;
+               ecmd.port = PORT_TP;
+               ecmd.transceiver = XCVR_EXTERNAL;
+               ecmd.phy_address = priv->mdio_phy_addr;
+
+               ecmd.speed = priv->current_speed;
+
+               ecmd.duplex = priv->full_duplex ? DUPLEX_FULL : DUPLEX_HALF;
+
+               ecmd.advertising = ADVERTISED_TP;
+               if (priv->current_duplex == autoneg && priv->current_speed_selection == 0)
+                       ecmd.advertising |= ADVERTISED_Autoneg;
+               else {
+                       ecmd.advertising |=     ADVERTISED_10baseT_Half | ADVERTISED_10baseT_Full |
+                               ADVERTISED_100baseT_Half | ADVERTISED_100baseT_Full;
+                       if (priv->current_speed_selection == 10)
+                               ecmd.advertising &=     ~(ADVERTISED_100baseT_Half | ADVERTISED_100baseT_Full);
+                       else if (priv->current_speed_selection == 100)
+                               ecmd.advertising &=     ~(ADVERTISED_10baseT_Half | ADVERTISED_10baseT_Full);
+                       if (priv->current_duplex == half)
+                               ecmd.advertising &=     ~(ADVERTISED_10baseT_Full | ADVERTISED_100baseT_Full);
+                       else if (priv->current_duplex == full)
+                               ecmd.advertising &=     ~(ADVERTISED_10baseT_Half | ADVERTISED_100baseT_Half);
+               }
+               ecmd.autoneg = AUTONEG_ENABLE;
+               if (copy_to_user(ifr->ifr_data, &ecmd, sizeof(ecmd)))
+                       return -EFAULT;
+               break;
+
+       case ETHTOOL_SSET:
+               if (!capable(CAP_NET_ADMIN)) {
+                       return -EPERM;
+               }
+               if (ecmd.autoneg == AUTONEG_ENABLE) {
+                       set_duplex(dev, autoneg);
+                       set_speed(dev, 0);
+               } else {
+                       set_duplex(dev, ecmd.duplex == DUPLEX_HALF ? half : full);
+                       set_speed(dev, ecmd.speed == SPEED_10 ? 10 : 100);
+               }
+               break;
+
+       case ETHTOOL_GDRVINFO:
+               {
+                       struct ethtool_drvinfo info;
+                       memset((void *) &info, 0, sizeof(info));
+                       strncpy(info.driver, "AMAZONE", sizeof(info.driver) - 1);
+                       strncpy(info.fw_version, "N/A", sizeof(info.fw_version) - 1);
+                       strncpy(info.bus_info, "N/A", sizeof(info.bus_info) - 1);
+                       info.regdump_len = 0;
+                       info.eedump_len = 0;
+                       info.testinfo_len = 0;
+                       if (copy_to_user(ifr->ifr_data, &info, sizeof(info)))
+                               return -EFAULT;
+               }
+               break;
+       case ETHTOOL_NWAY_RST:
+               if (priv->current_duplex == autoneg     && priv->current_speed_selection == 0)
+                       negotiate(dev);
+               break;
+       default:
+               return -EOPNOTSUPP;
+               break;
+       }
+       return 0;
+}
+
+
+
+int mac_table_tools_ioctl(struct net_device *dev, struct mac_table_req *req)
+{
+       int cmd;
+       int i;
+       cmd = req->cmd;
+       switch (cmd) {
+       case RESET_MAC_TABLE:
+               for (i = 0; i < 32; i++) {
+                       write_mac_table_entry(i, 0);
+               }
+               break;
+       case READ_MAC_ENTRY:
+               req->entry_value = read_mac_table_entry(req->index);
+               break;
+       case WRITE_MAC_ENTRY:
+               write_mac_table_entry(req->index, req->entry_value);
+               break;
+       case ADD_MAC_ENTRY:
+               add_mac_table_entry(req->entry_value);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+
+/*
+    the ioctl for the switch driver is developed in the conventional way
+    the control type falls into some basic categories, among them, the 
+    SIOCETHTOOL is the traditional eth interface. VLAN_TOOLS and  
+    MAC_TABLE_TOOLS are designed specifically for amazon chip. User 
+    should be aware of the data structures used in these interfaces. 
+*/
+int switch_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+       struct data_req *switch_data_req = (struct data_req *) ifr->ifr_data;
+       struct mac_table_req *switch_mac_table_req;
+       switch (cmd) {
+       case SIOCETHTOOL:
+               switch_ethtool_ioctl(dev, ifr);
+               break;
+       case SIOCGMIIPHY:                       /* Get PHY address */
+               break;
+       case SIOCGMIIREG:                       /* Read MII register */
+               break;
+       case SIOCSMIIREG:                       /* Write MII register */
+               break;
+       case SET_ETH_SPEED_10:          /* 10 Mbps */
+               break;
+       case SET_ETH_SPEED_100: /* 100 Mbps */
+               break;
+       case SET_ETH_SPEED_AUTO:        /* Auto negotiate speed */
+               break;
+       case SET_ETH_DUPLEX_HALF:       /* Half duplex. */
+               break;
+       case SET_ETH_DUPLEX_FULL:       /* Full duplex. */
+               break;
+       case SET_ETH_DUPLEX_AUTO:       /* Autonegotiate duplex */
+               break;
+       case SET_ETH_REG:
+               AMAZON_SW_REG32(switch_data_req->index) = switch_data_req->value;
+               break;
+       case MAC_TABLE_TOOLS:
+               switch_mac_table_req = (struct mac_table_req *) ifr->ifr_data;
+               mac_table_tools_ioctl(dev, switch_mac_table_req);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+struct net_device_stats *switch_stats(struct net_device *dev)
+{
+       struct switch_priv *priv = (struct switch_priv *) netdev_priv(dev);
+       return &priv->stats;
+}
+
+int switch_change_mtu(struct net_device *dev, int new_mtu)
+{
+       if (new_mtu >= 1516)
+               new_mtu = 1516;
+       dev->mtu = new_mtu;
+       return 0;
+}
+
+int switch_hw_receive(struct net_device *dev, struct dma_device_info *dma_dev)
+{
+       u8 *buf = NULL;
+       int len = 0;
+       struct sk_buff *skb = NULL;
+
+       len = dma_device_read(dma_dev, &buf, (void **) &skb);
+
+       if (len >= 0x600) {
+               printk(KERN_WARNING "amazon_mii0: packet too large %d\n", len);
+               goto switch_hw_receive_err_exit;
+       }
+
+       /* remove CRC */
+       len -= 4;
+       if (skb == NULL) {
+               printk(KERN_WARNING "amazon_mii0: cannot restore pointer\n");
+               goto switch_hw_receive_err_exit;
+       }
+       if (len > (skb->end - skb->tail)) {
+               printk(KERN_WARNING "amazon_mii0: BUG, len:%d end:%p tail:%p\n", (len + 4), skb->end, skb->tail);
+               goto switch_hw_receive_err_exit;
+       }
+       skb_put(skb, len);
+       skb->dev = dev;
+       switch_rx(dev, len, skb);
+       return OK;
+  
+  switch_hw_receive_err_exit:
+       if (skb)
+               dev_kfree_skb_any(skb);
+       return -EIO;
+}
+
+int dma_intr_handler(struct dma_device_info *dma_dev, int status)
+{
+       struct net_device *dev;
+
+       dev = dma_dev->priv;
+       switch (status) {
+       case RCV_INT:
+               switch_hw_receive(dev, dma_dev);
+               break;
+       case TX_BUF_FULL_INT:
+               netif_stop_queue(dev);
+               break;
+       case TRANSMIT_CPT_INT:
+               netif_wake_queue(dev);
+               break;
+       }
+       return OK;
+}
+
+/* reserve 2 bytes in front of data pointer*/
+u8 *dma_buffer_alloc(int len, int *byte_offset, void **opt)
+{
+       u8 *buffer = NULL;
+       struct sk_buff *skb = NULL;
+       skb = dev_alloc_skb(ETHERNET_PACKET_DMA_BUFFER_SIZE);
+       if (skb == NULL) {
+               return NULL;
+       }
+       buffer = (u8 *) (skb->data);
+       skb_reserve(skb, 2);
+       *(int *) opt = (int) skb;
+       *byte_offset = 2;
+       return buffer;
+}
+
+int dma_buffer_free(u8 * dataptr, void *opt)
+{
+       struct sk_buff *skb = NULL;
+       if (opt == NULL) {
+               kfree(dataptr);
+       } else {
+               skb = (struct sk_buff *) opt;
+               dev_kfree_skb_any(skb);
+       }
+       return OK;
+}
+
+int init_dma_device(_dma_device_info * dma_dev, struct net_device *dev)
+{
+       int i;
+       int num_tx_chan, num_rx_chan;
+       if (strcmp(dma_dev->device_name, "switch1") == 0) {
+               num_tx_chan = 1;
+               num_rx_chan = 2;
+       } else {
+               num_tx_chan = 1;
+               num_rx_chan = 2;
+       }
+       dma_dev->priv = dev;
+
+       dma_dev->weight = 1;
+       dma_dev->num_tx_chan = num_tx_chan;
+       dma_dev->num_rx_chan = num_rx_chan;
+       dma_dev->ack = 1;
+       dma_dev->tx_burst_len = 4;
+       dma_dev->rx_burst_len = 4;
+       for (i = 0; i < dma_dev->num_tx_chan; i++) {
+               dma_dev->tx_chan[i].weight = QOS_DEFAULT_WGT;
+               dma_dev->tx_chan[i].desc_num = 10;
+               dma_dev->tx_chan[i].packet_size = 0;
+               dma_dev->tx_chan[i].control = 0;
+       }
+       for (i = 0; i < num_rx_chan; i++) {
+               dma_dev->rx_chan[i].weight = QOS_DEFAULT_WGT;
+               dma_dev->rx_chan[i].desc_num = 10;
+               dma_dev->rx_chan[i].packet_size = ETHERNET_PACKET_DMA_BUFFER_SIZE;
+               dma_dev->rx_chan[i].control = 0;
+       }
+       dma_dev->intr_handler = dma_intr_handler;
+       dma_dev->buffer_alloc = dma_buffer_alloc;
+       dma_dev->buffer_free = dma_buffer_free;
+       return 0;
+}
+
+int switch_set_mac_address(struct net_device *dev, void *p)
+{
+       struct sockaddr *addr = p;
+       memcpy(dev->dev_addr, addr->sa_data, dev->addr_len);
+       return OK;
+}
+
+static const struct net_device_ops amazon_mii_ops = {
+       .ndo_init               = switch_init,
+       .ndo_open               = switch_open,
+       .ndo_stop               = switch_release,
+       .ndo_start_xmit         = switch_tx,
+       .ndo_do_ioctl           = switch_ioctl,
+       .ndo_get_stats          = switch_stats,
+       .ndo_change_mtu         = switch_change_mtu,
+       .ndo_set_mac_address            = switch_set_mac_address,
+       .ndo_tx_timeout         = switch_tx_timeout,
+};
+
+int switch_init(struct net_device *dev)
+{
+       u64 retval = 0;
+       int i;
+       int result;
+       struct switch_priv *priv;
+       ether_setup(dev);                       /* assign some of the fields */
+       printk(KERN_INFO "amazon_mii0: %s up using ", dev->name);
+       dev->watchdog_timeo = timeout;
+
+       priv = netdev_priv(dev);
+       priv->dma_device = (struct dma_device_info *) kmalloc(sizeof(struct dma_device_info), GFP_KERNEL);
+       if (priv->num == 0) {
+               sprintf(priv->dma_device->device_name, "switch1");
+       } else if (priv->num == 1) {
+               sprintf(priv->dma_device->device_name, "switch2");
+       }
+       printk("\"%s\"\n", priv->dma_device->device_name);
+       init_dma_device(priv->dma_device, dev);
+       result = dma_device_register(priv->dma_device);
+
+       /* read the mac address from the mac table and put them into the mac table. */
+       for (i = 0; i < 6; i++) {
+               retval += my_ethaddr[i];
+       }
+       /* ethaddr not set in u-boot ? */
+       if (retval == 0) {
+               dev->dev_addr[0] = 0x00;
+               dev->dev_addr[1] = 0x20;
+               dev->dev_addr[2] = 0xda;
+               dev->dev_addr[3] = 0x86;
+               dev->dev_addr[4] = 0x23;
+               dev->dev_addr[5] = 0x74 + (unsigned char) priv->num;
+       } else {
+               for (i = 0; i < 6; i++) {
+                       dev->dev_addr[i] = my_ethaddr[i];
+               }
+               dev->dev_addr[5] += +(unsigned char) priv->num;
+       }
+       return OK;
+}
+
+static int amazon_mii_probe(struct platform_device *dev)
+{
+       int i = 0, result, device_present = 0;
+       struct switch_priv *priv;
+
+       for (i = 0; i < AMAZON_SW_INT_NO; i++) {
+               switch_devs[i] = alloc_etherdev(sizeof(struct switch_priv));
+               switch_devs[i]->netdev_ops = &amazon_mii_ops;
+               strcpy(switch_devs[i]->name, "eth%d");
+               priv = (struct switch_priv *) netdev_priv(switch_devs[i]);
+               priv->num = i;
+               if ((result = register_netdev(switch_devs[i])))
+                       printk(KERN_WARNING "amazon_mii0: error %i registering device \"%s\"\n", result, switch_devs[i]->name);
+               else
+                       device_present++;
+       }
+       amazon_sw_chip_init();
+       return device_present ? 0 : -ENODEV;
+}
+
+static int amazon_mii_remove(struct platform_device *dev)
+{
+       int i;
+       struct switch_priv *priv;
+       for (i = 0; i < AMAZON_SW_INT_NO; i++) {
+               priv = netdev_priv(switch_devs[i]);
+               if (priv->dma_device) {
+                       dma_device_unregister(priv->dma_device);
+                       kfree(priv->dma_device);
+               }
+               kfree(netdev_priv(switch_devs[i]));
+               unregister_netdev(switch_devs[i]);
+       }
+       return 0;
+}
+
+static struct platform_driver amazon_mii_driver = {
+       .probe = amazon_mii_probe,
+       .remove = amazon_mii_remove,
+       .driver = {
+               .name = "amazon_mii0",
+               .owner = THIS_MODULE,
+       },
+};
+
+static int __init amazon_mii_init(void)
+{
+       int ret = platform_driver_register(&amazon_mii_driver);
+       if (ret)
+               printk(KERN_WARNING "amazon_mii0: Error registering platfom driver!\n");
+       return ret;
+}
+
+static void __exit amazon_mii_cleanup(void)
+{
+       platform_driver_unregister(&amazon_mii_driver);
+}
+
+module_init(amazon_mii_init);
+module_exit(amazon_mii_cleanup);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Wu Qi Ming");
+MODULE_DESCRIPTION("ethernet driver for AMAZON boards");
+