Skip to content
Snippets Groups Projects
fec.c 35.5 KiB
Newer Older
  • Learn to ignore specific revisions
  • Linus Torvalds's avatar
    Linus Torvalds committed
    		}
    
    
    		/* only upper 6 bits (HASH_BITS) are used
    		 * which point to specific bit in he hash registers
    		 */
    		hash = (crc >> (32 - HASH_BITS)) & 0x3f;
    
    		if (hash > 31) {
    			tmp = readl(fep->hwp + FEC_GRP_HASH_TABLE_HIGH);
    			tmp |= 1 << (hash - 32);
    			writel(tmp, fep->hwp + FEC_GRP_HASH_TABLE_HIGH);
    		} else {
    			tmp = readl(fep->hwp + FEC_GRP_HASH_TABLE_LOW);
    			tmp |= 1 << hash;
    			writel(tmp, fep->hwp + FEC_GRP_HASH_TABLE_LOW);
    		}
    
    Sascha Hauer's avatar
    Sascha Hauer committed
    /* Set a MAC change in hardware. */
    
    static int
    fec_set_mac_address(struct net_device *dev, void *p)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct fec_enet_private *fep = netdev_priv(dev);
    
    	struct sockaddr *addr = p;
    
    	if (!is_valid_ether_addr(addr->sa_data))
    		return -EADDRNOTAVAIL;
    
    	memcpy(dev->dev_addr, addr->sa_data, dev->addr_len);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	writel(dev->dev_addr[3] | (dev->dev_addr[2] << 8) |
    		(dev->dev_addr[1] << 16) | (dev->dev_addr[0] << 24),
    		fep->hwp + FEC_ADDR_LOW);
    	writel((dev->dev_addr[5] << 16) | (dev->dev_addr[4] << 24),
    
    		fep->hwp + FEC_ADDR_HIGH);
    
    	return 0;
    
    static const struct net_device_ops fec_netdev_ops = {
    	.ndo_open		= fec_enet_open,
    	.ndo_stop		= fec_enet_close,
    	.ndo_start_xmit		= fec_enet_start_xmit,
    	.ndo_set_multicast_list = set_multicast_list,
    
    	.ndo_change_mtu		= eth_change_mtu,
    
    	.ndo_validate_addr	= eth_validate_addr,
    	.ndo_tx_timeout		= fec_timeout,
    	.ndo_set_mac_address	= fec_set_mac_address,
    
    	.ndo_do_ioctl           = fec_enet_ioctl,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     /*
      * XXX:  We need to clean up on failure exits here.
    
      *
      * index is only used in legacy code
    
    Linus Torvalds's avatar
    Linus Torvalds committed
      */
    
    static int fec_enet_init(struct net_device *dev, int index)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	struct fec_enet_private *fep = netdev_priv(dev);
    
    Sascha Hauer's avatar
    Sascha Hauer committed
    	struct bufdesc *cbd_base;
    
    	struct bufdesc *bdp;
    
    Sascha Hauer's avatar
    Sascha Hauer committed
    	int i;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	/* Allocate memory for buffer descriptors. */
    	cbd_base = dma_alloc_coherent(NULL, PAGE_SIZE, &fep->bd_dma,
    			GFP_KERNEL);
    	if (!cbd_base) {
    
    		printk("FEC: allocate descriptor memory failed?\n");
    		return -ENOMEM;
    	}
    
    
    	spin_lock_init(&fep->hw_lock);
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	fep->index = index;
    
    	fep->hwp = (void __iomem *)dev->base_addr;
    
    	fep->netdev = dev;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	/* Set the Ethernet address */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	fec_get_mac(dev);
    
    		l = readl(fep->hwp + FEC_ADDR_LOW);
    
    		dev->dev_addr[0] = (unsigned char)((l & 0xFF000000) >> 24);
    		dev->dev_addr[1] = (unsigned char)((l & 0x00FF0000) >> 16);
    		dev->dev_addr[2] = (unsigned char)((l & 0x0000FF00) >> 8);
    		dev->dev_addr[3] = (unsigned char)((l & 0x000000FF) >> 0);
    
    		l = readl(fep->hwp + FEC_ADDR_HIGH);
    
    		dev->dev_addr[4] = (unsigned char)((l & 0xFF000000) >> 24);
    		dev->dev_addr[5] = (unsigned char)((l & 0x00FF0000) >> 16);
    	}
    #endif
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	/* Set receive and transmit descriptor base. */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	fep->rx_bd_base = cbd_base;
    	fep->tx_bd_base = cbd_base + RX_RING_SIZE;
    
    
    Sascha Hauer's avatar
    Sascha Hauer committed
    	/* The FEC Ethernet specific entries in the device structure */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	dev->watchdog_timeo = TX_TIMEOUT;
    
    	dev->netdev_ops = &fec_netdev_ops;
    
    	dev->ethtool_ops = &fec_enet_ethtool_ops;
    
    
    	/* Initialize the receive buffer descriptors. */
    	bdp = fep->rx_bd_base;
    	for (i = 0; i < RX_RING_SIZE; i++) {
    
    		/* Initialize the BD for every fragment in the page. */
    		bdp->cbd_sc = 0;
    		bdp++;
    	}
    
    	/* Set the last buffer to wrap */
    	bdp--;
    	bdp->cbd_sc |= BD_SC_WRAP;
    
    	/* ...and the same for transmit */
    	bdp = fep->tx_bd_base;
    	for (i = 0; i < TX_RING_SIZE; i++) {
    
    		/* Initialize the BD for every fragment in the page. */
    		bdp->cbd_sc = 0;
    		bdp->cbd_bufaddr = 0;
    		bdp++;
    	}
    
    	/* Set the last buffer to wrap */
    	bdp--;
    	bdp->cbd_sc |= BD_SC_WRAP;
    
    
    	fec_restart(dev, 0);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	return 0;
    }
    
    /* This function is called to start or restart the FEC during a link
     * change.  This only happens when switching between half and full
     * duplex.
     */
    static void
    fec_restart(struct net_device *dev, int duplex)
    {
    
    	struct fec_enet_private *fep = netdev_priv(dev);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	int i;
    
    
    	/* Whack a reset.  We should wait for this. */
    	writel(1, fep->hwp + FEC_ECNTRL);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	udelay(10);
    
    
    	/* Clear any outstanding interrupt. */
    	writel(0xffc00000, fep->hwp + FEC_IEVENT);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	/* Reset all multicast.	*/
    	writel(0, fep->hwp + FEC_GRP_HASH_TABLE_HIGH);
    	writel(0, fep->hwp + FEC_GRP_HASH_TABLE_LOW);
    
    #ifndef CONFIG_M5272
    	writel(0, fep->hwp + FEC_HASH_TABLE_HIGH);
    	writel(0, fep->hwp + FEC_HASH_TABLE_LOW);
    #endif
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	/* Set maximum receive buffer size. */
    	writel(PKT_MAXBLR_SIZE, fep->hwp + FEC_R_BUFF_SIZE);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	/* Set receive and transmit descriptor base. */
    	writel(fep->bd_dma, fep->hwp + FEC_R_DES_START);
    
    	writel((unsigned long)fep->bd_dma + sizeof(struct bufdesc) * RX_RING_SIZE,
    
    			fep->hwp + FEC_X_DES_START);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	fep->dirty_tx = fep->cur_tx = fep->tx_bd_base;
    	fep->cur_rx = fep->rx_bd_base;
    
    
    	/* Reset SKB transmit buffers. */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	fep->skb_cur = fep->skb_dirty = 0;
    
    Sascha Hauer's avatar
    Sascha Hauer committed
    	for (i = 0; i <= TX_RING_MOD_MASK; i++) {
    		if (fep->tx_skbuff[i]) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			dev_kfree_skb_any(fep->tx_skbuff[i]);
    			fep->tx_skbuff[i] = NULL;
    		}
    	}
    
    
    Sascha Hauer's avatar
    Sascha Hauer committed
    	/* Enable MII mode */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (duplex) {
    
    		/* MII enable / FD enable */
    		writel(OPT_FRAME_SIZE | 0x04, fep->hwp + FEC_R_CNTRL);
    		writel(0x04, fep->hwp + FEC_X_CNTRL);
    
    		/* MII enable / No Rcv on Xmit */
    		writel(OPT_FRAME_SIZE | 0x06, fep->hwp + FEC_R_CNTRL);
    		writel(0x0, fep->hwp + FEC_X_CNTRL);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    	fep->full_duplex = duplex;
    
    
    Sascha Hauer's avatar
    Sascha Hauer committed
    	/* Set MII speed */
    
    	writel(fep->phy_speed, fep->hwp + FEC_MII_SPEED);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    #ifdef FEC_MIIGSK_ENR
    	if (fep->phy_interface == PHY_INTERFACE_MODE_RMII) {
    		/* disable the gasket and wait */
    		writel(0, fep->hwp + FEC_MIIGSK_ENR);
    		while (readl(fep->hwp + FEC_MIIGSK_ENR) & 4)
    			udelay(1);
    
    		/* configure the gasket: RMII, 50 MHz, no loopback, no echo */
    		writel(1, fep->hwp + FEC_MIIGSK_CFGR);
    
    		/* re-enable the gasket */
    		writel(2, fep->hwp + FEC_MIIGSK_ENR);
    	}
    #endif
    
    
    Sascha Hauer's avatar
    Sascha Hauer committed
    	/* And last, enable the transmit and receive processing */
    
    	writel(2, fep->hwp + FEC_ECNTRL);
    	writel(0, fep->hwp + FEC_R_DES_ACTIVE);
    
    Sascha Hauer's avatar
    Sascha Hauer committed
    	/* Enable interrupts we wish to service */
    
    	writel(FEC_ENET_TXF | FEC_ENET_RXF, fep->hwp + FEC_IMASK);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    }
    
    static void
    fec_stop(struct net_device *dev)
    {
    
    	struct fec_enet_private *fep = netdev_priv(dev);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    Sascha Hauer's avatar
    Sascha Hauer committed
    	/* We cannot expect a graceful transmit stop without link !!! */
    
    	if (fep->link) {
    		writel(1, fep->hwp + FEC_X_CNTRL); /* Graceful transmit stop */
    
    		if (!(readl(fep->hwp + FEC_IEVENT) & FEC_ENET_GRA))
    
    			printk("fec_stop : Graceful transmit stop did not complete !\n");
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	/* Whack a reset.  We should wait for this. */
    	writel(1, fep->hwp + FEC_ECNTRL);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	udelay(10);
    
    
    	/* Clear outstanding MII command interrupts. */
    	writel(FEC_ENET_MII, fep->hwp + FEC_IEVENT);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	writel(fep->phy_speed, fep->hwp + FEC_MII_SPEED);
    
    static int __devinit
    fec_probe(struct platform_device *pdev)
    {
    	struct fec_enet_private *fep;
    
    	struct fec_platform_data *pdata;
    
    	struct net_device *ndev;
    	int i, irq, ret = 0;
    	struct resource *r;
    
    	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    	if (!r)
    		return -ENXIO;
    
    	r = request_mem_region(r->start, resource_size(r), pdev->name);
    	if (!r)
    		return -EBUSY;
    
    	/* Init network device */
    	ndev = alloc_etherdev(sizeof(struct fec_enet_private));
    	if (!ndev)
    		return -ENOMEM;
    
    	SET_NETDEV_DEV(ndev, &pdev->dev);
    
    	/* setup board info structure */
    	fep = netdev_priv(ndev);
    	memset(fep, 0, sizeof(*fep));
    
    	ndev->base_addr = (unsigned long)ioremap(r->start, resource_size(r));
    
    
    	if (!ndev->base_addr) {
    		ret = -ENOMEM;
    		goto failed_ioremap;
    	}
    
    	platform_set_drvdata(pdev, ndev);
    
    
    	pdata = pdev->dev.platform_data;
    	if (pdata)
    		fep->phy_interface = pdata->phy;
    
    
    	/* This device has up to three irqs on some platforms */
    	for (i = 0; i < 3; i++) {
    		irq = platform_get_irq(pdev, i);
    		if (i && irq < 0)
    			break;
    		ret = request_irq(irq, fec_enet_interrupt, IRQF_DISABLED, pdev->name, ndev);
    		if (ret) {
    			while (i >= 0) {
    				irq = platform_get_irq(pdev, i);
    				free_irq(irq, ndev);
    				i--;
    			}
    			goto failed_irq;
    		}
    	}
    
    	fep->clk = clk_get(&pdev->dev, "fec_clk");
    	if (IS_ERR(fep->clk)) {
    		ret = PTR_ERR(fep->clk);
    		goto failed_clk;
    	}
    	clk_enable(fep->clk);
    
    	ret = fec_enet_init(ndev, 0);
    	if (ret)
    		goto failed_init;
    
    
    	ret = fec_enet_mii_init(pdev);
    	if (ret)
    		goto failed_mii_init;
    
    
    	ret = register_netdev(ndev);
    	if (ret)
    		goto failed_register;
    
    
    	printk(KERN_INFO "%s: Freescale FEC PHY driver [%s] "
    		"(mii_bus:phy_addr=%s, irq=%d)\n", ndev->name,
    		fep->phy_dev->drv->name, dev_name(&fep->phy_dev->dev),
    		fep->phy_dev->irq);
    
    
    	fec_enet_mii_remove(fep);
    failed_mii_init:
    
    failed_init:
    	clk_disable(fep->clk);
    	clk_put(fep->clk);
    failed_clk:
    	for (i = 0; i < 3; i++) {
    		irq = platform_get_irq(pdev, i);
    		if (irq > 0)
    			free_irq(irq, ndev);
    	}
    failed_irq:
    	iounmap((void __iomem *)ndev->base_addr);
    failed_ioremap:
    	free_netdev(ndev);
    
    	return ret;
    }
    
    static int __devexit
    fec_drv_remove(struct platform_device *pdev)
    {
    	struct net_device *ndev = platform_get_drvdata(pdev);
    	struct fec_enet_private *fep = netdev_priv(ndev);
    
    	platform_set_drvdata(pdev, NULL);
    
    	fec_stop(ndev);
    
    	clk_disable(fep->clk);
    	clk_put(fep->clk);
    	iounmap((void __iomem *)ndev->base_addr);
    	unregister_netdev(ndev);
    	free_netdev(ndev);
    	return 0;
    }
    
    static int
    fec_suspend(struct platform_device *dev, pm_message_t state)
    {
    	struct net_device *ndev = platform_get_drvdata(dev);
    	struct fec_enet_private *fep;
    
    	if (ndev) {
    		fep = netdev_priv(ndev);
    		if (netif_running(ndev)) {
    			netif_device_detach(ndev);
    			fec_stop(ndev);
    		}
    	}
    	return 0;
    }
    
    static int
    fec_resume(struct platform_device *dev)
    {
    	struct net_device *ndev = platform_get_drvdata(dev);
    
    	if (ndev) {
    		if (netif_running(ndev)) {
    			fec_enet_init(ndev, 0);
    			netif_device_attach(ndev);
    		}
    	}
    	return 0;
    }
    
    static struct platform_driver fec_driver = {
    	.driver	= {
    		.name    = "fec",
    		.owner	 = THIS_MODULE,
    	},
    	.probe   = fec_probe,
    	.remove  = __devexit_p(fec_drv_remove),
    	.suspend = fec_suspend,
    	.resume  = fec_resume,
    };
    
    static int __init
    fec_enet_module_init(void)
    {
    	printk(KERN_INFO "FEC Ethernet Driver\n");
    
    	return platform_driver_register(&fec_driver);
    }
    
    static void __exit
    fec_enet_cleanup(void)
    {
    	platform_driver_unregister(&fec_driver);
    }
    
    module_exit(fec_enet_cleanup);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    module_init(fec_enet_module_init);
    
    MODULE_LICENSE("GPL");