diff options
Diffstat (limited to 'target/linux/lantiq/patches/230-xway_etop.patch')
-rw-r--r-- | target/linux/lantiq/patches/230-xway_etop.patch | 580 |
1 files changed, 580 insertions, 0 deletions
diff --git a/target/linux/lantiq/patches/230-xway_etop.patch b/target/linux/lantiq/patches/230-xway_etop.patch new file mode 100644 index 000000000..28955cf9a --- /dev/null +++ b/target/linux/lantiq/patches/230-xway_etop.patch @@ -0,0 +1,580 @@ +--- a/drivers/net/Kconfig ++++ b/drivers/net/Kconfig +@@ -343,6 +343,12 @@ config MACB + + source "drivers/net/arm/Kconfig" + ++config LANTIQ_ETOP ++ tristate "Lantiq SoC ETOP driver" ++ depends on SOC_LANTIQ_XWAY ++ help ++ Support for the MII0 inside the Lantiq SoC ++ + config AX88796 + tristate "ASIX AX88796 NE2000 clone support" + depends on ARM || MIPS || SUPERH +--- a/drivers/net/Makefile ++++ b/drivers/net/Makefile +@@ -204,6 +204,7 @@ obj-$(CONFIG_SNI_82596) += sni_82596.o + obj-$(CONFIG_MVME16x_NET) += 82596.o + obj-$(CONFIG_BVME6000_NET) += 82596.o + obj-$(CONFIG_SC92031) += sc92031.o ++obj-$(CONFIG_LANTIQ_ETOP) += lantiq_etop.o + + # This is also a 82596 and should probably be merged + obj-$(CONFIG_LP486E) += lp486e.o +--- /dev/null ++++ b/drivers/net/lantiq_etop.c +@@ -0,0 +1,552 @@ ++/* ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms of the GNU General Public License version 2 as published ++ * by the Free Software Foundation. ++ * ++ * 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. ++ * ++ * Copyright (C) 2005 Wu Qi Ming <Qi-Ming.Wu@infineon.com> ++ * Copyright (C) 2008 John Crispin <blogic@openwrt.org> ++ */ ++ ++#include <linux/kernel.h> ++#include <linux/slab.h> ++#include <linux/errno.h> ++#include <linux/types.h> ++#include <linux/interrupt.h> ++#include <linux/uaccess.h> ++#include <linux/in.h> ++#include <linux/netdevice.h> ++#include <linux/etherdevice.h> ++#include <linux/phy.h> ++#include <linux/ip.h> ++#include <linux/tcp.h> ++#include <linux/skbuff.h> ++#include <linux/mm.h> ++#include <linux/platform_device.h> ++#include <linux/ethtool.h> ++#include <linux/init.h> ++#include <linux/delay.h> ++ ++#include <asm/checksum.h> ++ ++#include <xway.h> ++#include <xway_dma.h> ++#include <lantiq_platform.h> ++ ++#define ETHERNET_PACKET_DMA_BUFFER_SIZE 0x600 ++#define LQ_PPE32_MEM_MAP ((u32 *)(LQ_PPE32_BASE_ADDR + 0x10000)) ++#define LQ_PPE32_SRST ((u32 *)(LQ_PPE32_BASE_ADDR + 0x10080)) ++ ++/* mdio access */ ++#define LQ_PPE32_MDIO_CFG ((u32 *)(LQ_PPE32_BASE_ADDR + 0x11800)) ++#define LQ_PPE32_MDIO_ACC ((u32 *)(LQ_PPE32_BASE_ADDR + 0x11804)) ++ ++#define MDIO_ACC_REQUEST 0x80000000 ++#define MDIO_ACC_READ 0x40000000 ++#define MDIO_ACC_ADDR_MASK 0x1f ++#define MDIO_ACC_ADDR_OFFSET 0x15 ++#define MDIO_ACC_REG_MASK 0x1f ++#define MDIO_ACC_REG_OFFSET 0x10 ++#define MDIO_ACC_VAL_MASK 0xffff ++ ++/* configuration */ ++#define LQ_PPE32_CFG ((u32 *)(LQ_PPE32_MEM_MAP + 0x1808)) ++ ++#define PPE32_MII_MASK 0xfffffffc ++#define PPE32_MII_NORMAL 0x8 ++#define PPE32_MII_REVERSE 0xe ++ ++/* packet length */ ++#define LQ_PPE32_IG_PLEN_CTRL ((u32 *)(LQ_PPE32_MEM_MAP + 0x1820)) ++ ++#define PPE32_PLEN_OVER 0x5ee ++#define PPE32_PLEN_UNDER 0x400000 ++ ++/* enet */ ++#define LQ_PPE32_ENET_MAC_CFG ((u32 *)(LQ_PPE32_MEM_MAP + 0x1840)) ++ ++#define PPE32_CGEN 0x800 ++ ++struct lq_mii_priv { ++ struct net_device_stats stats; ++ struct dma_device_info *dma_device; ++ struct sk_buff *skb; ++ ++ struct mii_bus *mii_bus; ++ struct phy_device *phydev; ++ int oldlink, oldspeed, oldduplex; ++}; ++ ++static struct net_device *lq_etop_dev; ++static unsigned char mac_addr[MAX_ADDR_LEN]; ++ ++static int lq_mdiobus_write(struct mii_bus *bus, int phy_addr, ++ int phy_reg, u16 phy_data) ++{ ++ u32 val = MDIO_ACC_REQUEST | ++ ((phy_addr & MDIO_ACC_ADDR_MASK) << MDIO_ACC_ADDR_OFFSET) | ++ ((phy_reg & MDIO_ACC_REG_MASK) << MDIO_ACC_REG_OFFSET) | ++ phy_data; ++ ++ while (lq_r32(LQ_PPE32_MDIO_ACC) & MDIO_ACC_REQUEST) ++ ; ++ lq_w32(val, LQ_PPE32_MDIO_ACC); ++ ++ return 0; ++} ++ ++static int lq_mdiobus_read(struct mii_bus *bus, int phy_addr, int phy_reg) ++{ ++ u32 val = MDIO_ACC_REQUEST | MDIO_ACC_READ | ++ ((phy_addr & MDIO_ACC_ADDR_MASK) << MDIO_ACC_ADDR_OFFSET) | ++ ((phy_reg & MDIO_ACC_REG_MASK) << MDIO_ACC_REG_OFFSET); ++ ++ while (lq_r32(LQ_PPE32_MDIO_ACC) & MDIO_ACC_REQUEST) ++ ; ++ lq_w32(val, LQ_PPE32_MDIO_ACC); ++ while (lq_r32(LQ_PPE32_MDIO_ACC) & MDIO_ACC_REQUEST) ++ ; ++ val = lq_r32(LQ_PPE32_MDIO_ACC) & MDIO_ACC_VAL_MASK; ++ return val; ++} ++ ++int lq_mii_open(struct net_device *dev) ++{ ++ struct lq_mii_priv *priv = (struct lq_mii_priv *)netdev_priv(dev); ++ struct dma_device_info *dma_dev = priv->dma_device; ++ int i; ++ ++ for (i = 0; i < dma_dev->max_rx_chan_num; i++) { ++ if ((dma_dev->rx_chan[i])->control == LQ_DMA_CH_ON) ++ (dma_dev->rx_chan[i])->open(dma_dev->rx_chan[i]); ++ } ++ netif_start_queue(dev); ++ return 0; ++} ++ ++int lq_mii_release(struct net_device *dev) ++{ ++ struct lq_mii_priv *priv = (struct lq_mii_priv *)netdev_priv(dev); ++ struct dma_device_info *dma_dev = priv->dma_device; ++ int i; ++ ++ for (i = 0; i < dma_dev->max_rx_chan_num; i++) ++ dma_dev->rx_chan[i]->close(dma_dev->rx_chan[i]); ++ netif_stop_queue(dev); ++ return 0; ++} ++ ++int lq_mii_hw_receive(struct net_device *dev, struct dma_device_info *dma_dev) ++{ ++ struct lq_mii_priv *priv = (struct lq_mii_priv *)netdev_priv(dev); ++ unsigned char *buf = NULL; ++ struct sk_buff *skb = NULL; ++ int len = 0; ++ ++ len = dma_device_read(dma_dev, &buf, (void **)&skb); ++ ++ if (len >= ETHERNET_PACKET_DMA_BUFFER_SIZE) { ++ printk(KERN_INFO "lq_etop: packet too large %d\n", len); ++ goto lq_mii_hw_receive_err_exit; ++ } ++ ++ /* remove CRC */ ++ len -= 4; ++ if (skb == NULL) { ++ printk(KERN_INFO "lq_etop: cannot restore pointer\n"); ++ goto lq_mii_hw_receive_err_exit; ++ } ++ ++ if (len > (skb->end - skb->tail)) { ++ printk(KERN_INFO "lq_etop: BUG, len:%d end:%p tail:%p\n", ++ (len+4), skb->end, skb->tail); ++ goto lq_mii_hw_receive_err_exit; ++ } ++ ++ skb_put(skb, len); ++ skb->dev = dev; ++ skb->protocol = eth_type_trans(skb, dev); ++ netif_rx(skb); ++ ++ priv->stats.rx_packets++; ++ priv->stats.rx_bytes += len; ++ return 0; ++ ++lq_mii_hw_receive_err_exit: ++ if (len == 0) { ++ if (skb) ++ dev_kfree_skb_any(skb); ++ priv->stats.rx_errors++; ++ priv->stats.rx_dropped++; ++ return -EIO; ++ } else { ++ return len; ++ } ++} ++ ++int lq_mii_hw_tx(char *buf, int len, struct net_device *dev) ++{ ++ int ret = 0; ++ struct lq_mii_priv *priv = netdev_priv(dev); ++ struct dma_device_info *dma_dev = priv->dma_device; ++ ret = dma_device_write(dma_dev, buf, len, priv->skb); ++ return ret; ++} ++ ++int lq_mii_tx(struct sk_buff *skb, struct net_device *dev) ++{ ++ int len; ++ char *data; ++ struct lq_mii_priv *priv = netdev_priv(dev); ++ struct dma_device_info *dma_dev = priv->dma_device; ++ ++ len = skb->len < ETH_ZLEN ? ETH_ZLEN : skb->len; ++ data = skb->data; ++ priv->skb = skb; ++ dev->trans_start = jiffies; ++ /* TODO: we got more than 1 dma channel, ++ so we should do something intelligent here to select one */ ++ dma_dev->current_tx_chan = 0; ++ ++ wmb(); ++ ++ if (lq_mii_hw_tx(data, len, dev) != len) { ++ dev_kfree_skb_any(skb); ++ priv->stats.tx_errors++; ++ priv->stats.tx_dropped++; ++ } else { ++ priv->stats.tx_packets++; ++ priv->stats.tx_bytes += len; ++ } ++ ++ return 0; ++} ++ ++void lq_mii_tx_timeout(struct net_device *dev) ++{ ++ int i; ++ struct lq_mii_priv *priv = (struct lq_mii_priv *)netdev_priv(dev); ++ ++ priv->stats.tx_errors++; ++ for (i = 0; i < priv->dma_device->max_tx_chan_num; i++) ++ priv->dma_device->tx_chan[i]->disable_irq(priv->dma_device->tx_chan[i]); ++ netif_wake_queue(dev); ++ return; ++} ++ ++int dma_intr_handler(struct dma_device_info *dma_dev, int status) ++{ ++ int i; ++ ++ switch (status) { ++ case RCV_INT: ++ lq_mii_hw_receive(lq_etop_dev, dma_dev); ++ break; ++ ++ case TX_BUF_FULL_INT: ++ printk(KERN_INFO "lq_etop: tx buffer full\n"); ++ netif_stop_queue(lq_etop_dev); ++ for (i = 0; i < dma_dev->max_tx_chan_num; i++) { ++ if ((dma_dev->tx_chan[i])->control == LQ_DMA_CH_ON) ++ dma_dev->tx_chan[i]->enable_irq(dma_dev->tx_chan[i]); ++ } ++ break; ++ ++ case TRANSMIT_CPT_INT: ++ for (i = 0; i < dma_dev->max_tx_chan_num; i++) ++ dma_dev->tx_chan[i]->disable_irq(dma_dev->tx_chan[i]); ++ ++ netif_wake_queue(lq_etop_dev); ++ break; ++ } ++ ++ return 0; ++} ++ ++unsigned char *lq_etop_dma_buffer_alloc(int len, int *byte_offset, void **opt) ++{ ++ unsigned char *buffer = NULL; ++ struct sk_buff *skb = NULL; ++ ++ skb = dev_alloc_skb(ETHERNET_PACKET_DMA_BUFFER_SIZE); ++ if (skb == NULL) ++ return NULL; ++ ++ buffer = (unsigned char *)(skb->data); ++ skb_reserve(skb, 2); ++ *(int *)opt = (int)skb; ++ *byte_offset = 2; ++ ++ return buffer; ++} ++ ++void lq_etop_dma_buffer_free(unsigned char *dataptr, void *opt) ++{ ++ struct sk_buff *skb = NULL; ++ ++ if (opt == NULL) { ++ kfree(dataptr); ++ } else { ++ skb = (struct sk_buff *)opt; ++ dev_kfree_skb_any(skb); ++ } ++} ++ ++static void ++lq_adjust_link(struct net_device *dev) ++{ ++ struct lq_mii_priv *priv = netdev_priv(dev); ++ struct phy_device *phydev = priv->phydev; ++ int new_state = 0; ++ ++ /* Did anything change? */ ++ if (priv->oldlink != phydev->link || ++ priv->oldduplex != phydev->duplex || ++ priv->oldspeed != phydev->speed) { ++ /* Yes, so update status and mark as changed */ ++ new_state = 1; ++ priv->oldduplex = phydev->duplex; ++ priv->oldspeed = phydev->speed; ++ priv->oldlink = phydev->link; ++ } ++ ++ /* If link status changed, show new status */ ++ if (new_state) ++ phy_print_status(phydev); ++} ++ ++static int mii_probe(struct net_device *dev) ++{ ++ struct lq_mii_priv *priv = netdev_priv(dev); ++ struct phy_device *phydev = NULL; ++ int phy_addr; ++ ++ priv->oldlink = 0; ++ priv->oldspeed = 0; ++ priv->oldduplex = -1; ++ ++ /* find the first (lowest address) PHY on the current MAC's MII bus */ ++ for (phy_addr = 0; phy_addr < PHY_MAX_ADDR; phy_addr++) { ++ if (priv->mii_bus->phy_map[phy_addr]) { ++ phydev = priv->mii_bus->phy_map[phy_addr]; ++ break; /* break out with first one found */ ++ } ++ } ++ ++ if (!phydev) { ++ printk (KERN_ERR "%s: no PHY found\n", dev->name); ++ return -ENODEV; ++ } ++ ++ /* now we are supposed to have a proper phydev, to attach to... */ ++ BUG_ON(!phydev); ++ BUG_ON(phydev->attached_dev); ++ ++ phydev = phy_connect(dev, dev_name(&phydev->dev), &lq_adjust_link, ++ 0, PHY_INTERFACE_MODE_MII); ++ ++ if (IS_ERR(phydev)) { ++ printk(KERN_ERR "%s: Could not attach to PHY\n", dev->name); ++ return PTR_ERR(phydev); ++ } ++ ++ /* mask with MAC supported features */ ++ phydev->supported &= (SUPPORTED_10baseT_Half ++ | SUPPORTED_10baseT_Full ++ | SUPPORTED_100baseT_Half ++ | SUPPORTED_100baseT_Full ++ | SUPPORTED_Autoneg ++ /* | SUPPORTED_Pause | SUPPORTED_Asym_Pause */ ++ | SUPPORTED_MII ++ | SUPPORTED_TP); ++ ++ phydev->advertising = phydev->supported; ++ ++ priv->phydev = phydev; ++ ++ printk(KERN_INFO "%s: attached PHY driver [%s] " ++ "(mii_bus:phy_addr=%s, irq=%d)\n", ++ dev->name, phydev->drv->name, dev_name(&phydev->dev), phydev->irq); ++ ++ return 0; ++} ++ ++ ++static int lq_mii_dev_init(struct net_device *dev) ++{ ++ int i; ++ struct lq_mii_priv *priv = (struct lq_mii_priv *)netdev_priv(dev); ++ ether_setup(dev); ++ dev->watchdog_timeo = 10 * HZ; ++ dev->mtu = 1500; ++ memset(priv, 0, sizeof(struct lq_mii_priv)); ++ priv->dma_device = dma_device_reserve("PPE"); ++ if (!priv->dma_device) { ++ BUG(); ++ return -ENODEV; ++ } ++ priv->dma_device->buffer_alloc = &lq_etop_dma_buffer_alloc; ++ priv->dma_device->buffer_free = &lq_etop_dma_buffer_free; ++ priv->dma_device->intr_handler = &dma_intr_handler; ++ priv->dma_device->max_rx_chan_num = 4; ++ ++ for (i = 0; i < priv->dma_device->max_rx_chan_num; i++) { ++ priv->dma_device->rx_chan[i]->packet_size = ETHERNET_PACKET_DMA_BUFFER_SIZE; ++ priv->dma_device->rx_chan[i]->control = LQ_DMA_CH_ON; ++ } ++ ++ for (i = 0; i < priv->dma_device->max_tx_chan_num; i++) ++ if (i == 0) ++ priv->dma_device->tx_chan[i]->control = LQ_DMA_CH_ON; ++ else ++ priv->dma_device->tx_chan[i]->control = LQ_DMA_CH_OFF; ++ ++ dma_device_register(priv->dma_device); ++ ++ printk(KERN_INFO "%s: using mac=", dev->name); ++ for (i = 0; i < 6; i++) { ++ dev->dev_addr[i] = mac_addr[i]; ++ printk("%02X%c", dev->dev_addr[i], (i == 5) ? ('\n') : (':')); ++ } ++ ++ priv->mii_bus = mdiobus_alloc(); ++ if (priv->mii_bus == NULL) ++ return -ENOMEM; ++ ++ priv->mii_bus->priv = dev; ++ priv->mii_bus->read = lq_mdiobus_read; ++ priv->mii_bus->write = lq_mdiobus_write; ++ priv->mii_bus->name = "lq_mii"; ++ snprintf(priv->mii_bus->id, MII_BUS_ID_SIZE, "%x", 0); ++ priv->mii_bus->irq = kmalloc(sizeof(int) * PHY_MAX_ADDR, GFP_KERNEL); ++ for(i = 0; i < PHY_MAX_ADDR; ++i) ++ priv->mii_bus->irq[i] = PHY_POLL; ++ ++ mdiobus_register(priv->mii_bus); ++ ++ return mii_probe(dev); ++} ++ ++static void lq_mii_chip_init(int mode) ++{ ++ lq_pmu_enable(PMU_DMA); ++ lq_pmu_enable(PMU_PPE); ++ ++ if (mode == REV_MII_MODE) ++ lq_w32_mask(PPE32_MII_MASK, PPE32_MII_REVERSE, LQ_PPE32_CFG); ++ else if (mode == MII_MODE) ++ lq_w32_mask(PPE32_MII_MASK, PPE32_MII_NORMAL, LQ_PPE32_CFG); ++ lq_w32(PPE32_PLEN_UNDER | PPE32_PLEN_OVER, LQ_PPE32_IG_PLEN_CTRL); ++ lq_w32(PPE32_CGEN, LQ_PPE32_ENET_MAC_CFG); ++ wmb(); ++} ++ ++static int lq_mii_eth_mac_addr(struct net_device *dev, void *p) ++{ ++ int retcode; ++ ++ retcode = eth_mac_addr(dev, p); ++ ++ if (retcode) ++ return retcode; ++ ++ // set rx_addr for unicast filter ++ lq_w32(((dev->dev_addr[0]<<24)|(dev->dev_addr[1]<<16)|(dev->dev_addr[2]<< 8)|dev->dev_addr[3]), (u32*)(LQ_PPE32_BASE_ADDR|(0x461b<<2))); ++ lq_w32(((dev->dev_addr[4]<<24)|(dev->dev_addr[5]<<16)), (u32*)(LQ_PPE32_BASE_ADDR|(0x461c<<2))); ++ ++ return 0; ++} ++ ++static void lq_mii_set_rx_mode (struct net_device *dev) ++{ ++ // rx_mode promisc: unset unicast filter ++ if ((dev->flags & IFF_PROMISC) || (dev->flags & IFF_ALLMULTI)) ++ lq_w32(lq_r32((u32*)(LQ_PPE32_BASE_ADDR|(0x4614<<2))) & ~(1<<28), (u32*)(LQ_PPE32_BASE_ADDR|(0x4614<<2))); ++ // enable unicast filter ++ else ++ lq_w32(lq_r32((u32*)(LQ_PPE32_BASE_ADDR|(0x4614<<2))) | (1<<28), (u32*)(LQ_PPE32_BASE_ADDR|(0x4614<<2))); ++} ++ ++static const struct net_device_ops lq_eth_netdev_ops = { ++ .ndo_init = lq_mii_dev_init, ++ .ndo_open = lq_mii_open, ++ .ndo_stop = lq_mii_release, ++ .ndo_start_xmit = lq_mii_tx, ++ .ndo_tx_timeout = lq_mii_tx_timeout, ++ .ndo_change_mtu = eth_change_mtu, ++ .ndo_set_mac_address = lq_mii_eth_mac_addr, ++ .ndo_validate_addr = eth_validate_addr, ++ .ndo_set_multicast_list = lq_mii_set_rx_mode, ++}; ++ ++static int ++lq_mii_probe(struct platform_device *dev) ++{ ++ int result = 0; ++ struct lq_eth_data *eth = (struct lq_eth_data*)dev->dev.platform_data; ++ lq_etop_dev = alloc_etherdev(sizeof(struct lq_mii_priv)); ++ lq_etop_dev->netdev_ops = &lq_eth_netdev_ops; ++ memcpy(mac_addr, eth->mac, 6); ++ strcpy(lq_etop_dev->name, "eth%d"); ++ lq_mii_chip_init(eth->mii_mode); ++ result = register_netdev(lq_etop_dev); ++ if (result) { ++ printk(KERN_INFO "lq_etop: error %i registering device \"%s\"\n", result, lq_etop_dev->name); ++ goto out; ++ } ++ ++ printk(KERN_INFO "lq_etop: driver loaded!\n"); ++ ++out: ++ return result; ++} ++ ++static int lq_mii_remove(struct platform_device *dev) ++{ ++ struct lq_mii_priv *priv = (struct lq_mii_priv *)netdev_priv(lq_etop_dev); ++ ++ printk(KERN_INFO "lq_etop: lq_etop cleanup\n"); ++ ++ dma_device_unregister(priv->dma_device); ++ dma_device_release(priv->dma_device); ++ kfree(priv->dma_device); ++ unregister_netdev(lq_etop_dev); ++ return 0; ++} ++ ++static struct platform_driver lq_mii_driver = { ++ .probe = lq_mii_probe, ++ .remove = lq_mii_remove, ++ .driver = { ++ .name = "lq_etop", ++ .owner = THIS_MODULE, ++ }, ++}; ++ ++int __init lq_mii_init(void) ++{ ++ int ret = platform_driver_register(&lq_mii_driver); ++ if (ret) ++ printk(KERN_INFO "lq_etop: Error registering platfom driver!"); ++ return ret; ++} ++ ++static void __exit lq_mii_cleanup(void) ++{ ++ platform_driver_unregister(&lq_mii_driver); ++} ++ ++module_init(lq_mii_init); ++module_exit(lq_mii_cleanup); ++ ++MODULE_LICENSE("GPL"); ++MODULE_AUTHOR("John Crispin <blogic@openwrt.org>"); ++MODULE_DESCRIPTION("ethernet driver for IFXMIPS boards"); |