diff options
author | hauke <hauke@3c298f89-4303-0410-b956-a3cf2f4a3e73> | 2012-05-13 15:10:40 +0000 |
---|---|---|
committer | hauke <hauke@3c298f89-4303-0410-b956-a3cf2f4a3e73> | 2012-05-13 15:10:40 +0000 |
commit | c8c0045b91db29cdc5229b0f0272a8565b430f76 (patch) | |
tree | b6672dea93059de3c1a3449f25e6b32786b3f03f /target/linux/amazon/files/drivers/net/ethernet/amazon_sw.c | |
parent | 4e0a145caec7cc6c34e979f02a52ae742f0fb9ce (diff) |
amazon: update amazon target to kernel 3.3
This is just compile tested, my device is currently not working.
git-svn-id: svn://svn.openwrt.org/openwrt/trunk@31706 3c298f89-4303-0410-b956-a3cf2f4a3e73
Diffstat (limited to 'target/linux/amazon/files/drivers/net/ethernet/amazon_sw.c')
-rw-r--r-- | target/linux/amazon/files/drivers/net/ethernet/amazon_sw.c | 899 |
1 files changed, 899 insertions, 0 deletions
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 index 0000000000..d18b439cea --- /dev/null +++ b/target/linux/amazon/files/drivers/net/ethernet/amazon_sw.c @@ -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"); + |