Skip to content
Snippets Groups Projects
fec.c 38.6 KiB
Newer Older
  • Learn to ignore specific revisions
  • static void fec_enet_mii_remove(struct fec_enet_private *fep)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	if (fep->phy_dev)
    		phy_disconnect(fep->phy_dev);
    	mdiobus_unregister(fep->mii_bus);
    	kfree(fep->mii_bus->irq);
    	mdiobus_free(fep->mii_bus);
    
    static int fec_enet_get_settings(struct net_device *ndev,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct fec_enet_private *fep = netdev_priv(ndev);
    
    	struct phy_device *phydev = fep->phy_dev;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	return phy_ethtool_gset(phydev, cmd);
    
    static int fec_enet_set_settings(struct net_device *ndev,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct fec_enet_private *fep = netdev_priv(ndev);
    
    	struct phy_device *phydev = fep->phy_dev;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	return phy_ethtool_sset(phydev, cmd);
    
    static void fec_enet_get_drvinfo(struct net_device *ndev,
    
    				 struct ethtool_drvinfo *info)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct fec_enet_private *fep = netdev_priv(ndev);
    
    	strcpy(info->driver, fep->pdev->dev.driver->name);
    	strcpy(info->version, "Revision: 1.0");
    
    	strcpy(info->bus_info, dev_name(&ndev->dev));
    
    static struct ethtool_ops fec_enet_ethtool_ops = {
    	.get_settings		= fec_enet_get_settings,
    	.set_settings		= fec_enet_set_settings,
    	.get_drvinfo		= fec_enet_get_drvinfo,
    	.get_link		= ethtool_op_get_link,
    };
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    static int fec_enet_ioctl(struct net_device *ndev, struct ifreq *rq, int cmd)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct fec_enet_private *fep = netdev_priv(ndev);
    
    	struct phy_device *phydev = fep->phy_dev;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	return phy_mii_ioctl(phydev, rq, cmd);
    
    static void fec_enet_free_buffers(struct net_device *ndev)
    
    Sascha Hauer's avatar
    Sascha Hauer committed
    {
    
    	struct fec_enet_private *fep = netdev_priv(ndev);
    
    Sascha Hauer's avatar
    Sascha Hauer committed
    	int i;
    	struct sk_buff *skb;
    	struct bufdesc	*bdp;
    
    	bdp = fep->rx_bd_base;
    	for (i = 0; i < RX_RING_SIZE; i++) {
    		skb = fep->rx_skbuff[i];
    
    		if (bdp->cbd_bufaddr)
    
    			dma_unmap_single(&fep->pdev->dev, bdp->cbd_bufaddr,
    
    Sascha Hauer's avatar
    Sascha Hauer committed
    					FEC_ENET_RX_FRSIZE, DMA_FROM_DEVICE);
    		if (skb)
    			dev_kfree_skb(skb);
    		bdp++;
    	}
    
    	bdp = fep->tx_bd_base;
    	for (i = 0; i < TX_RING_SIZE; i++)
    		kfree(fep->tx_bounce[i]);
    }
    
    
    static int fec_enet_alloc_buffers(struct net_device *ndev)
    
    Sascha Hauer's avatar
    Sascha Hauer committed
    {
    
    	struct fec_enet_private *fep = netdev_priv(ndev);
    
    Sascha Hauer's avatar
    Sascha Hauer committed
    	int i;
    	struct sk_buff *skb;
    	struct bufdesc	*bdp;
    
    	bdp = fep->rx_bd_base;
    	for (i = 0; i < RX_RING_SIZE; i++) {
    		skb = dev_alloc_skb(FEC_ENET_RX_FRSIZE);
    		if (!skb) {
    
    Sascha Hauer's avatar
    Sascha Hauer committed
    			return -ENOMEM;
    		}
    		fep->rx_skbuff[i] = skb;
    
    
    		bdp->cbd_bufaddr = dma_map_single(&fep->pdev->dev, skb->data,
    
    Sascha Hauer's avatar
    Sascha Hauer committed
    				FEC_ENET_RX_FRSIZE, DMA_FROM_DEVICE);
    		bdp->cbd_sc = BD_ENET_RX_EMPTY;
    		bdp++;
    	}
    
    	/* Set the last buffer to wrap. */
    	bdp--;
    	bdp->cbd_sc |= BD_SC_WRAP;
    
    	bdp = fep->tx_bd_base;
    	for (i = 0; i < TX_RING_SIZE; i++) {
    		fep->tx_bounce[i] = kmalloc(FEC_ENET_TX_FRSIZE, GFP_KERNEL);
    
    		bdp->cbd_sc = 0;
    		bdp->cbd_bufaddr = 0;
    		bdp++;
    	}
    
    	/* Set the last buffer to wrap. */
    	bdp--;
    	bdp->cbd_sc |= BD_SC_WRAP;
    
    	return 0;
    }
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    static int
    
    fec_enet_open(struct net_device *ndev)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct fec_enet_private *fep = netdev_priv(ndev);
    
    Sascha Hauer's avatar
    Sascha Hauer committed
    	int ret;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	/* I should reset the ring buffers here, but I don't yet know
    	 * a simple way to do that.
    	 */
    
    
    	ret = fec_enet_alloc_buffers(ndev);
    
    Sascha Hauer's avatar
    Sascha Hauer committed
    	if (ret)
    		return ret;
    
    
    	/* Probe and connect to PHY when open the interface */
    
    	ret = fec_enet_mii_probe(ndev);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	fep->opened = 1;
    
    Sascha Hauer's avatar
    Sascha Hauer committed
    	return 0;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    }
    
    static int
    
    fec_enet_close(struct net_device *ndev)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct fec_enet_private *fep = netdev_priv(ndev);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    Sascha Hauer's avatar
    Sascha Hauer committed
    	/* Don't know what to do yet. */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	fep->opened = 0;
    
    	netif_stop_queue(ndev);
    	fec_stop(ndev);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	if (fep->phy_dev) {
    		phy_stop(fep->phy_dev);
    
    		phy_disconnect(fep->phy_dev);
    
    	fec_enet_free_buffers(ndev);
    
    Sascha Hauer's avatar
    Sascha Hauer committed
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return 0;
    }
    
    /* Set or clear the multicast filter for this adaptor.
     * Skeleton taken from sunlance driver.
     * The CPM Ethernet implementation allows Multicast as well as individual
     * MAC address filtering.  Some of the drivers check to make sure it is
     * a group multicast address, and discard those that are not.  I guess I
     * will do the same for now, but just remove the test if you want
     * individual filtering as well (do the upper net layers want or support
     * this kind of feature?).
     */
    
    #define HASH_BITS	6		/* #bits in hash */
    #define CRC32_POLY	0xEDB88320
    
    
    static void set_multicast_list(struct net_device *ndev)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct fec_enet_private *fep = netdev_priv(ndev);
    
    	struct netdev_hw_addr *ha;
    
    	unsigned int i, bit, data, crc, tmp;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	unsigned char hash;
    
    
    	if (ndev->flags & IFF_PROMISC) {
    
    		tmp = readl(fep->hwp + FEC_R_CNTRL);
    		tmp |= 0x8;
    		writel(tmp, fep->hwp + FEC_R_CNTRL);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	tmp = readl(fep->hwp + FEC_R_CNTRL);
    	tmp &= ~0x8;
    	writel(tmp, fep->hwp + FEC_R_CNTRL);
    
    
    	if (ndev->flags & IFF_ALLMULTI) {
    
    		/* Catch all multicast addresses, so set the
    		 * filter to all 1's
    		 */
    		writel(0xffffffff, fep->hwp + FEC_GRP_HASH_TABLE_HIGH);
    		writel(0xffffffff, fep->hwp + FEC_GRP_HASH_TABLE_LOW);
    
    		return;
    	}
    
    	/* Clear filter and add the addresses in hash register
    	 */
    	writel(0, fep->hwp + FEC_GRP_HASH_TABLE_HIGH);
    	writel(0, fep->hwp + FEC_GRP_HASH_TABLE_LOW);
    
    
    	netdev_for_each_mc_addr(ha, ndev) {
    
    		/* Only support group multicast for now */
    
    		if (!(ha->addr[0] & 1))
    
    			continue;
    
    		/* calculate crc32 value of mac address */
    		crc = 0xffffffff;
    
    
    		for (i = 0; i < ndev->addr_len; i++) {
    
    			data = ha->addr[i];
    
    			for (bit = 0; bit < 8; bit++, data >>= 1) {
    				crc = (crc >> 1) ^
    				(((crc ^ data) & 1) ? CRC32_POLY : 0);
    
    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 *ndev, void *p)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct fec_enet_private *fep = netdev_priv(ndev);
    
    	struct sockaddr *addr = p;
    
    	if (!is_valid_ether_addr(addr->sa_data))
    		return -EADDRNOTAVAIL;
    
    
    	memcpy(ndev->dev_addr, addr->sa_data, ndev->addr_len);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	writel(ndev->dev_addr[3] | (ndev->dev_addr[2] << 8) |
    		(ndev->dev_addr[1] << 16) | (ndev->dev_addr[0] << 24),
    
    		fep->hwp + FEC_ADDR_LOW);
    
    	writel((ndev->dev_addr[5] << 16) | (ndev->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.
    
    Linus Torvalds's avatar
    Linus Torvalds committed
      */
    
    static int fec_enet_init(struct net_device *ndev)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct fec_enet_private *fep = netdev_priv(ndev);
    
    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
    
    
    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 */
    
    	ndev->watchdog_timeo = TX_TIMEOUT;
    	ndev->netdev_ops = &fec_netdev_ops;
    	ndev->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;
    
    
    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) {
    		ret = -ENOMEM;
    		goto failed_alloc_etherdev;
    	}
    
    
    	SET_NETDEV_DEV(ndev, &pdev->dev);
    
    	/* setup board info structure */
    	fep = netdev_priv(ndev);
    
    
    	fep->hwp = ioremap(r->start, resource_size(r));
    
    		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) {
    
    				irq = platform_get_irq(pdev, i);
    				free_irq(irq, ndev);
    			}
    			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);
    
    	ret = fec_enet_mii_init(pdev);
    	if (ret)
    		goto failed_mii_init;
    
    
    	/* Carrier starts down, phylib will bring it up */
    	netif_carrier_off(ndev);
    
    
    	ret = register_netdev(ndev);
    	if (ret)
    		goto failed_register;
    
    	return 0;
    
    failed_register:
    
    	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:
    
    failed_ioremap:
    	free_netdev(ndev);
    
    failed_alloc_etherdev:
    	release_mem_region(r->start, resource_size(r));
    
    
    	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);
    
    	clk_disable(fep->clk);
    	clk_put(fep->clk);
    
    	unregister_netdev(ndev);
    	free_netdev(ndev);
    
    
    	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    	BUG_ON(!r);
    	release_mem_region(r->start, resource_size(r));
    
    
    #ifdef CONFIG_PM
    
    fec_suspend(struct device *dev)
    
    	struct net_device *ndev = dev_get_drvdata(dev);
    
    	struct fec_enet_private *fep = netdev_priv(ndev);
    
    	if (netif_running(ndev)) {
    		fec_stop(ndev);
    		netif_device_detach(ndev);
    
    fec_resume(struct device *dev)
    
    	struct net_device *ndev = dev_get_drvdata(dev);
    
    	struct fec_enet_private *fep = netdev_priv(ndev);
    
    	clk_enable(fep->clk);
    	if (netif_running(ndev)) {
    		fec_restart(ndev, fep->full_duplex);
    		netif_device_attach(ndev);
    
    static const struct dev_pm_ops fec_pm_ops = {
    	.suspend	= fec_suspend,
    	.resume		= fec_resume,
    	.freeze		= fec_suspend,
    	.thaw		= fec_resume,
    	.poweroff	= fec_suspend,
    	.restore	= fec_resume,
    };
    
    #endif
    
    static struct platform_driver fec_driver = {
    	.driver	= {
    
    		.name	= DRIVER_NAME,
    
    		.owner	= THIS_MODULE,
    #ifdef CONFIG_PM
    		.pm	= &fec_pm_ops,
    #endif
    
    	.id_table = fec_devtype,
    
    	.probe	= fec_probe,
    	.remove	= __devexit_p(fec_drv_remove),
    
    };
    
    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");