Skip to content
Snippets Groups Projects
fec.c 35.6 KiB
Newer Older
				(((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 *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_DEFAULT_IMASK, 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);
	writel(fep->phy_speed, fep->hwp + FEC_MII_SPEED);
	writel(FEC_DEFAULT_IMASK, fep->hwp + FEC_IMASK);
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;

	/* 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:
	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;
}

#ifdef CONFIG_PM
fec_suspend(struct device *dev)
	struct net_device *ndev = dev_get_drvdata(dev);
	struct fec_enet_private *fep;

	if (ndev) {
		fep = netdev_priv(ndev);
		if (netif_running(ndev))
			fec_enet_close(ndev);
		clk_disable(fep->clk);
fec_resume(struct device *dev)
	struct net_device *ndev = dev_get_drvdata(dev);
	struct fec_enet_private *fep;
		fep = netdev_priv(ndev);
		clk_enable(fep->clk);
		if (netif_running(ndev))
			fec_enet_open(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	= "fec",
		.owner	= THIS_MODULE,
#ifdef CONFIG_PM
		.pm	= &fec_pm_ops,
#endif
	.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");