Skip to content
Snippets Groups Projects
fec.c 55.4 KiB
Newer Older
  • Learn to ignore specific revisions
  • Linus Torvalds's avatar
    Linus Torvalds committed
    		{ mk_mii_read(MII_QS6612_ISR), NULL },
    		{ mk_mii_read(MII_REG_SR), mii_parse_sr },
    		{ mk_mii_read(MII_REG_ANER), NULL },
    
    		/* read pcr to get info */
    		{ mk_mii_read(MII_QS6612_PCR), mii_parse_qs6612_pcr },
    		{ mk_mii_end, }
    
    	};
    static phy_cmd_t const phy_cmd_qs6612_shutdown[] = { /* disable interrupts */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		{ mk_mii_write(MII_QS6612_IMR, 0x0000), NULL },
    		{ mk_mii_end, }
    
    	};
    static phy_info_t const phy_info_qs6612 = {
    
    	.id = 0x00181440,
    
    	.name = "QS6612",
    	.config = phy_cmd_qs6612_config,
    	.startup = phy_cmd_qs6612_startup,
    	.ack_int = phy_cmd_qs6612_ack_int,
    	.shutdown = phy_cmd_qs6612_shutdown
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    };
    
    /* ------------------------------------------------------------------------- */
    /* AMD AM79C874 phy                                                          */
    
    /* register definitions for the 874 */
    
    #define MII_AM79C874_MFR       16  /* Miscellaneous Feature Register */
    #define MII_AM79C874_ICSR      17  /* Interrupt/Status Register      */
    #define MII_AM79C874_DR        18  /* Diagnostic Register            */
    #define MII_AM79C874_PMLR      19  /* Power and Loopback Register    */
    #define MII_AM79C874_MCR       21  /* ModeControl Register           */
    #define MII_AM79C874_DC        23  /* Disconnect Counter             */
    #define MII_AM79C874_REC       24  /* Recieve Error Counter          */
    
    static void mii_parse_am79c874_dr(uint mii_reg, struct net_device *dev)
    {
    	struct fec_enet_private *fep = netdev_priv(dev);
    	volatile uint *s = &(fep->phy_status);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	status = *s & ~(PHY_STAT_SPMASK | PHY_STAT_ANC);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	if (mii_reg & 0x0080)
    
    		status |= PHY_STAT_ANC;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (mii_reg & 0x0400)
    
    		status |= ((mii_reg & 0x0800) ? PHY_STAT_100FDX : PHY_STAT_100HDX);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	else
    
    		status |= ((mii_reg & 0x0800) ? PHY_STAT_10FDX : PHY_STAT_10HDX);
    
    	*s = status;
    
    static phy_cmd_t const phy_cmd_am79c874_config[] = {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		{ mk_mii_read(MII_REG_CR), mii_parse_cr },
    		{ mk_mii_read(MII_REG_ANAR), mii_parse_anar },
    		{ mk_mii_read(MII_AM79C874_DR), mii_parse_am79c874_dr },
    		{ mk_mii_end, }
    
    	};
    static phy_cmd_t const phy_cmd_am79c874_startup[] = {  /* enable interrupts */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		{ mk_mii_write(MII_AM79C874_ICSR, 0xff00), NULL },
    		{ mk_mii_write(MII_REG_CR, 0x1200), NULL }, /* autonegotiate */
    
    		{ mk_mii_read(MII_REG_SR), mii_parse_sr },
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		{ mk_mii_end, }
    
    	};
    static phy_cmd_t const phy_cmd_am79c874_ack_int[] = {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		/* find out the current status */
    		{ mk_mii_read(MII_REG_SR), mii_parse_sr },
    		{ mk_mii_read(MII_AM79C874_DR), mii_parse_am79c874_dr },
    		/* we only need to read ISR to acknowledge */
    		{ mk_mii_read(MII_AM79C874_ICSR), NULL },
    		{ mk_mii_end, }
    
    	};
    static phy_cmd_t const phy_cmd_am79c874_shutdown[] = { /* disable interrupts */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		{ mk_mii_write(MII_AM79C874_ICSR, 0x0000), NULL },
    		{ mk_mii_end, }
    
    	};
    static phy_info_t const phy_info_am79c874 = {
    	.id = 0x00022561,
    	.name = "AM79C874",
    	.config = phy_cmd_am79c874_config,
    	.startup = phy_cmd_am79c874_startup,
    	.ack_int = phy_cmd_am79c874_ack_int,
    	.shutdown = phy_cmd_am79c874_shutdown
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    /* ------------------------------------------------------------------------- */
    /* Kendin KS8721BL phy                                                       */
    
    /* register definitions for the 8721 */
    
    #define MII_KS8721BL_RXERCR	21
    
    #define MII_KS8721BL_ICSR	27
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #define	MII_KS8721BL_PHYCR	31
    
    
    static phy_cmd_t const phy_cmd_ks8721bl_config[] = {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		{ mk_mii_read(MII_REG_CR), mii_parse_cr },
    		{ mk_mii_read(MII_REG_ANAR), mii_parse_anar },
    		{ mk_mii_end, }
    
    	};
    static phy_cmd_t const phy_cmd_ks8721bl_startup[] = {  /* enable interrupts */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		{ mk_mii_write(MII_KS8721BL_ICSR, 0xff00), NULL },
    		{ mk_mii_write(MII_REG_CR, 0x1200), NULL }, /* autonegotiate */
    
    		{ mk_mii_read(MII_REG_SR), mii_parse_sr },
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		{ mk_mii_end, }
    
    	};
    static phy_cmd_t const phy_cmd_ks8721bl_ack_int[] = {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		/* find out the current status */
    		{ mk_mii_read(MII_REG_SR), mii_parse_sr },
    		/* we only need to read ISR to acknowledge */
    		{ mk_mii_read(MII_KS8721BL_ICSR), NULL },
    		{ mk_mii_end, }
    
    	};
    static phy_cmd_t const phy_cmd_ks8721bl_shutdown[] = { /* disable interrupts */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		{ mk_mii_write(MII_KS8721BL_ICSR, 0x0000), NULL },
    		{ mk_mii_end, }
    
    	};
    static phy_info_t const phy_info_ks8721bl = {
    
    	.id = 0x00022161,
    
    	.name = "KS8721BL",
    	.config = phy_cmd_ks8721bl_config,
    	.startup = phy_cmd_ks8721bl_startup,
    	.ack_int = phy_cmd_ks8721bl_ack_int,
    	.shutdown = phy_cmd_ks8721bl_shutdown
    
    /* ------------------------------------------------------------------------- */
    /* register definitions for the DP83848 */
    
    #define MII_DP8384X_PHYSTST    16  /* PHY Status Register */
    
    static void mii_parse_dp8384x_sr2(uint mii_reg, struct net_device *dev)
    {
    
    	struct fec_enet_private *fep = netdev_priv(dev);
    
    	volatile uint *s = &(fep->phy_status);
    
    	*s &= ~(PHY_STAT_SPMASK | PHY_STAT_LINK | PHY_STAT_ANC);
    
    	/* Link up */
    	if (mii_reg & 0x0001) {
    		fep->link = 1;
    		*s |= PHY_STAT_LINK;
    	} else
    		fep->link = 0;
    	/* Status of link */
    	if (mii_reg & 0x0010)   /* Autonegotioation complete */
    		*s |= PHY_STAT_ANC;
    	if (mii_reg & 0x0002) {   /* 10MBps? */
    		if (mii_reg & 0x0004)   /* Full Duplex? */
    			*s |= PHY_STAT_10FDX;
    		else
    			*s |= PHY_STAT_10HDX;
    	} else {                  /* 100 Mbps? */
    		if (mii_reg & 0x0004)   /* Full Duplex? */
    			*s |= PHY_STAT_100FDX;
    		else
    			*s |= PHY_STAT_100HDX;
    	}
    	if (mii_reg & 0x0008)
    		*s |= PHY_STAT_FAULT;
    }
    
    static phy_info_t phy_info_dp83848= {
    	0x020005c9,
    	"DP83848",
    
    	(const phy_cmd_t []) {  /* config */
    		{ mk_mii_read(MII_REG_CR), mii_parse_cr },
    		{ mk_mii_read(MII_REG_ANAR), mii_parse_anar },
    		{ mk_mii_read(MII_DP8384X_PHYSTST), mii_parse_dp8384x_sr2 },
    		{ mk_mii_end, }
    	},
    	(const phy_cmd_t []) {  /* startup - enable interrupts */
    		{ mk_mii_write(MII_REG_CR, 0x1200), NULL }, /* autonegotiate */
    		{ mk_mii_read(MII_REG_SR), mii_parse_sr },
    		{ mk_mii_end, }
    	},
    	(const phy_cmd_t []) { /* ack_int - never happens, no interrupt */
    		{ mk_mii_end, }
    	},
    	(const phy_cmd_t []) {  /* shutdown */
    		{ mk_mii_end, }
    	},
    };
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    /* ------------------------------------------------------------------------- */
    
    
    static phy_info_t const * const phy_info[] = {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	&phy_info_lxt970,
    	&phy_info_lxt971,
    	&phy_info_qs6612,
    	&phy_info_am79c874,
    	&phy_info_ks8721bl,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	NULL
    };
    
    /* ------------------------------------------------------------------------- */
    
    #ifdef HAVE_mii_link_interrupt
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    static irqreturn_t
    
    mii_link_interrupt(int irq, void * dev_id);
    
     *	This is specific to the MII interrupt setup of the M5272EVB.
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     */
    
    static void __inline__ fec_request_mii_intr(struct net_device *dev)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	if (request_irq(66, mii_link_interrupt, IRQF_DISABLED, "fec(MII)", dev) != 0)
    		printk("FEC: Could not allocate fec(MII) IRQ(66)!\n");
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    }
    
    static void __inline__ fec_disable_phy_intr(void)
    {
    	volatile unsigned long *icrp;
    	icrp = (volatile unsigned long *) (MCF_MBAR + MCFSIM_ICR1);
    
    	*icrp = 0x08000000;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    }
    
    static void __inline__ fec_phy_ack_intr(void)
    {
    	volatile unsigned long *icrp;
    	/* Acknowledge the interrupt */
    	icrp = (volatile unsigned long *) (MCF_MBAR + MCFSIM_ICR1);
    
    	*icrp = 0x0d000000;
    
    static void __inline__ fec_get_mac(struct net_device *dev)
    {
    	struct fec_enet_private *fep = netdev_priv(dev);
    	unsigned char *iap, tmpaddr[ETH_ALEN];
    
    	if (FEC_FLASHMAC) {
    		/*
    		 * Get MAC address from FLASH.
    		 * If it is all 1's or 0's, use the default.
    		 */
    
    		iap = (unsigned char *)FEC_FLASHMAC;
    
    		if ((iap[0] == 0) && (iap[1] == 0) && (iap[2] == 0) &&
    		    (iap[3] == 0) && (iap[4] == 0) && (iap[5] == 0))
    			iap = fec_mac_default;
    		if ((iap[0] == 0xff) && (iap[1] == 0xff) && (iap[2] == 0xff) &&
    		    (iap[3] == 0xff) && (iap[4] == 0xff) && (iap[5] == 0xff))
    			iap = fec_mac_default;
    	} else {
    
    		*((unsigned long *) &tmpaddr[0]) = readl(fep->hwp + FEC_ADDR_LOW);
    		*((unsigned short *) &tmpaddr[4]) = (readl(fep->hwp + FEC_ADDR_HIGH) >> 16);
    
    		iap = &tmpaddr[0];
    	}
    
    	memcpy(dev->dev_addr, iap, ETH_ALEN);
    
    	/* Adjust MAC if using default MAC address */
    	if (iap == fec_mac_default)
    
    		 dev->dev_addr[ETH_ALEN-1] = fec_mac_default[ETH_ALEN-1] + fep->index;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #endif
    
    /* ------------------------------------------------------------------------- */
    
    static void mii_display_status(struct net_device *dev)
    {
    	struct fec_enet_private *fep = netdev_priv(dev);
    	volatile uint *s = &(fep->phy_status);
    
    	if (!fep->link && !fep->old_link) {
    		/* Link is still down - don't print anything */
    		return;
    	}
    
    	printk("%s: status: ", dev->name);
    
    	if (!fep->link) {
    		printk("link down");
    	} else {
    		printk("link up");
    
    		switch(*s & PHY_STAT_SPMASK) {
    		case PHY_STAT_100FDX: printk(", 100MBit Full Duplex"); break;
    		case PHY_STAT_100HDX: printk(", 100MBit Half Duplex"); break;
    		case PHY_STAT_10FDX: printk(", 10MBit Full Duplex"); break;
    		case PHY_STAT_10HDX: printk(", 10MBit Half Duplex"); break;
    		default:
    			printk(", Unknown speed/duplex");
    		}
    
    		if (*s & PHY_STAT_ANC)
    			printk(", auto-negotiation complete");
    	}
    
    	if (*s & PHY_STAT_FAULT)
    		printk(", remote fault");
    
    	printk(".\n");
    }
    
    
    static void mii_display_config(struct work_struct *work)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct fec_enet_private *fep = container_of(work, struct fec_enet_private, phy_task);
    	struct net_device *dev = fep->netdev;
    
    	uint status = fep->phy_status;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	/*
    	** When we get here, phy_task is already removed from
    	** the workqueue.  It is thus safe to allow to reuse it.
    	*/
    	fep->mii_phy_task_queued = 0;
    	printk("%s: config: auto-negotiation ", dev->name);
    
    
    	if (status & PHY_CONF_ANE)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		printk("on");
    	else
    		printk("off");
    
    
    	if (status & PHY_CONF_100FDX)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		printk(", 100FDX");
    
    	if (status & PHY_CONF_100HDX)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		printk(", 100HDX");
    
    	if (status & PHY_CONF_10FDX)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		printk(", 10FDX");
    
    	if (status & PHY_CONF_10HDX)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		printk(", 10HDX");
    
    	if (!(status & PHY_CONF_SPMASK))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		printk(", No speed/duplex selected?");
    
    
    	if (status & PHY_CONF_LOOP)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		printk(", loopback enabled");
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	printk(".\n");
    
    	fep->sequence_done = 1;
    }
    
    
    static void mii_relink(struct work_struct *work)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct fec_enet_private *fep = container_of(work, struct fec_enet_private, phy_task);
    	struct net_device *dev = fep->netdev;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	int duplex;
    
    	/*
    	** When we get here, phy_task is already removed from
    	** the workqueue.  It is thus safe to allow to reuse it.
    	*/
    	fep->mii_phy_task_queued = 0;
    	fep->link = (fep->phy_status & PHY_STAT_LINK) ? 1 : 0;
    	mii_display_status(dev);
    	fep->old_link = fep->link;
    
    	if (fep->link) {
    		duplex = 0;
    
    		if (fep->phy_status
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		    & (PHY_STAT_100FDX | PHY_STAT_10FDX))
    			duplex = 1;
    		fec_restart(dev, duplex);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		fec_stop(dev);
    
    #if 0
    	enable_irq(fep->mii_irq);
    #endif
    
    }
    
    /* mii_queue_relink is called in interrupt context from mii_link_interrupt */
    static void mii_queue_relink(uint mii_reg, struct net_device *dev)
    {
    	struct fec_enet_private *fep = netdev_priv(dev);
    
    	/*
    	** We cannot queue phy_task twice in the workqueue.  It
    	** would cause an endless loop in the workqueue.
    	** Fortunately, if the last mii_relink entry has not yet been
    	** executed now, it will do the job for the current interrupt,
    	** which is just what we want.
    	*/
    	if (fep->mii_phy_task_queued)
    		return;
    
    	fep->mii_phy_task_queued = 1;
    
    	INIT_WORK(&fep->phy_task, mii_relink);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	schedule_work(&fep->phy_task);
    }
    
    
    /* mii_queue_config is called in interrupt context from fec_enet_mii */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    static void mii_queue_config(uint mii_reg, struct net_device *dev)
    {
    	struct fec_enet_private *fep = netdev_priv(dev);
    
    	if (fep->mii_phy_task_queued)
    		return;
    
    	fep->mii_phy_task_queued = 1;
    
    	INIT_WORK(&fep->phy_task, mii_display_config);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	schedule_work(&fep->phy_task);
    }
    
    
    phy_cmd_t const phy_cmd_relink[] = {
    	{ mk_mii_read(MII_REG_CR), mii_queue_relink },
    	{ mk_mii_end, }
    	};
    phy_cmd_t const phy_cmd_config[] = {
    	{ mk_mii_read(MII_REG_CR), mii_queue_config },
    	{ mk_mii_end, }
    	};
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    /* Read remainder of PHY ID.
    */
    static void
    mii_discover_phy3(uint mii_reg, struct net_device *dev)
    {
    	struct fec_enet_private *fep;
    	int i;
    
    	fep = netdev_priv(dev);
    	fep->phy_id |= (mii_reg & 0xffff);
    	printk("fec: PHY @ 0x%x, ID 0x%08x", fep->phy_addr, fep->phy_id);
    
    	for(i = 0; phy_info[i]; i++) {
    		if(phy_info[i]->id == (fep->phy_id >> 4))
    			break;
    	}
    
    	if (phy_info[i])
    		printk(" -- %s\n", phy_info[i]->name);
    	else
    		printk(" -- unknown PHY!\n");
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	fep->phy = phy_info[i];
    	fep->phy_id_done = 1;
    }
    
    /* Scan all of the MII PHY addresses looking for someone to respond
     * with a valid ID.  This usually happens quickly.
     */
    static void
    mii_discover_phy(uint mii_reg, struct net_device *dev)
    {
    	struct fec_enet_private *fep;
    	uint phytype;
    
    	fep = netdev_priv(dev);
    
    	if (fep->phy_addr < 32) {
    		if ((phytype = (mii_reg & 0xffff)) != 0xffff && phytype != 0) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			/* Got first part of ID, now get remainder.
    			*/
    			fep->phy_id = phytype << 16;
    			mii_queue(dev, mk_mii_read(MII_REG_PHYIR2),
    							mii_discover_phy3);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			fep->phy_addr++;
    			mii_queue(dev, mk_mii_read(MII_REG_PHYIR1),
    							mii_discover_phy);
    		}
    	} else {
    		printk("FEC: No PHY device found.\n");
    		/* Disable external MII interface */
    
    		writel(0, fep->hwp + FEC_MII_SPEED);
    		fep->phy_speed = 0;
    
    #ifdef HAVE_mii_link_interrupt
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		fec_disable_phy_intr();
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    }
    
    /* This interrupt occurs when the PHY detects a link change.
    */
    
    #ifdef HAVE_mii_link_interrupt
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    static irqreturn_t
    
    mii_link_interrupt(int irq, void * dev_id)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	struct	net_device *dev = dev_id;
    	struct fec_enet_private *fep = netdev_priv(dev);
    
    	fec_phy_ack_intr();
    
    #if 0
    	disable_irq(fep->mii_irq);  /* disable now, enable later */
    #endif
    
    	mii_do_cmd(dev, fep->phy->ack_int);
    	mii_do_cmd(dev, phy_cmd_relink);  /* restart and display status */
    
    	return IRQ_HANDLED;
    }
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    static int
    fec_enet_open(struct net_device *dev)
    {
    	struct fec_enet_private *fep = netdev_priv(dev);
    
    	/* I should reset the ring buffers here, but I don't yet know
    	 * a simple way to do that.
    	 */
    	fec_set_mac_address(dev);
    
    	fep->sequence_done = 0;
    	fep->link = 0;
    
    	if (fep->phy) {
    		mii_do_cmd(dev, fep->phy->ack_int);
    		mii_do_cmd(dev, fep->phy->config);
    		mii_do_cmd(dev, phy_cmd_config);  /* display configuration */
    
    
    		/* Poll until the PHY tells us its configuration
    		 * (not link state).
    		 * Request is initiated by mii_do_cmd above, but answer
    		 * comes by interrupt.
    		 * This should take about 25 usec per register at 2.5 MHz,
    		 * and we read approximately 5 registers.
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		 */
    		while(!fep->sequence_done)
    			schedule();
    
    		mii_do_cmd(dev, fep->phy->startup);
    
    		/* Set the initial link state to true. A lot of hardware
    		 * based on this device does not implement a PHY interrupt,
    		 * so we are never notified of link change.
    		 */
    		fep->link = 1;
    	} else {
    		fep->link = 1; /* lets just try it and see */
    		/* no phy,  go full duplex,  it's most likely a hub chip */
    		fec_restart(dev, 1);
    	}
    
    	netif_start_queue(dev);
    	fep->opened = 1;
    	return 0;		/* Success */
    }
    
    static int
    fec_enet_close(struct net_device *dev)
    {
    	struct fec_enet_private *fep = netdev_priv(dev);
    
    	/* Don't know what to do yet.
    	*/
    	fep->opened = 0;
    	netif_stop_queue(dev);
    	fec_stop(dev);
    
    	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 *dev)
    {
    
    	struct fec_enet_private *fep = netdev_priv(dev);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct dev_mc_list *dmi;
    
    	unsigned int i, j, bit, data, crc, tmp;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	unsigned char hash;
    
    	if (dev->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
    	} else {
    
    		tmp = readl(fep->hwp + FEC_R_CNTRL);
    		tmp &= ~0x8;
    		writel(tmp, fep->hwp + FEC_R_CNTRL);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    		if (dev->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);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		} else {
    			/* 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);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			dmi = dev->mc_list;
    
    			for (j = 0; j < dev->mc_count; j++, dmi = dmi->next)
    			{
    				/* Only support group multicast for now.
    				*/
    				if (!(dmi->dmi_addr[0] & 1))
    					continue;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				/* calculate crc32 value of mac address
    				*/
    				crc = 0xffffffff;
    
    				for (i = 0; i < dmi->dmi_addrlen; i++)
    				{
    					data = dmi->dmi_addr[i];
    					for (bit = 0; bit < 8; bit++, data >>= 1)
    					{
    						crc = (crc >> 1) ^
    						(((crc ^ data) & 1) ? CRC32_POLY : 0);
    					}
    				}
    
    				/* 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);
    				}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			}
    		}
    	}
    }
    
    /* Set a MAC change in hardware.
     */
    static void
    fec_set_mac_address(struct net_device *dev)
    {
    
    	struct fec_enet_private *fep = netdev_priv(dev);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	/* Set station address. */
    
    	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 + FEC_ADDR_HIGH);
    
    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
      */
    
    int __init fec_enet_init(struct net_device *dev, int index)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	struct fec_enet_private *fep = netdev_priv(dev);
    	unsigned long	mem_addr;
    	volatile cbd_t	*bdp;
    	cbd_t		*cbd_base;
    	int 		i, j;
    
    
    	/* Allocate memory for buffer descriptors.
    	*/
    
    	mem_addr = (unsigned long)dma_alloc_coherent(NULL, PAGE_SIZE,
    			&fep->bd_dma, GFP_KERNEL);
    
    	if (mem_addr == 0) {
    		printk("FEC: allocate descriptor memory failed?\n");
    		return -ENOMEM;
    	}
    
    
    	spin_lock_init(&fep->hw_lock);
    	spin_lock_init(&fep->mii_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
    
    	/* Whack a reset.  We should wait for this.
    	*/
    
    	writel(1, fep->hwp + FEC_ECNTRL);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	udelay(10);
    
    
    	/* 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
    
    	cbd_base = (cbd_t *)mem_addr;
    
    	/* Set receive and transmit descriptor base.
    	*/
    	fep->rx_bd_base = cbd_base;
    	fep->tx_bd_base = cbd_base + RX_RING_SIZE;
    
    	fep->dirty_tx = fep->cur_tx = fep->tx_bd_base;
    	fep->cur_rx = fep->rx_bd_base;
    
    	fep->skb_cur = fep->skb_dirty = 0;
    
    	/* Initialize the receive buffer descriptors.
    	*/
    	bdp = fep->rx_bd_base;
    	for (i=0; i<FEC_ENET_RX_PAGES; i++) {
    
    		/* Allocate a page.
    		*/
    		mem_addr = __get_free_page(GFP_KERNEL);
    		/* XXX: missing check for allocation failure */
    
    		/* Initialize the BD for every fragment in the page.
    		*/
    		for (j=0; j<FEC_ENET_RX_FRPPG; j++) {
    			bdp->cbd_sc = BD_ENET_RX_EMPTY;
    			bdp->cbd_bufaddr = __pa(mem_addr);
    			mem_addr += FEC_ENET_RX_FRSIZE;
    			bdp++;
    		}
    	}
    
    	/* Set the last buffer to wrap.
    	*/
    	bdp--;
    	bdp->cbd_sc |= BD_SC_WRAP;
    
    	/* ...and the same for transmmit.
    	*/
    	bdp = fep->tx_bd_base;
    	for (i=0, j=FEC_ENET_TX_FRPPG; i<TX_RING_SIZE; i++) {
    		if (j >= FEC_ENET_TX_FRPPG) {
    			mem_addr = __get_free_page(GFP_KERNEL);
    			j = 1;
    		} else {
    			mem_addr += FEC_ENET_TX_FRSIZE;
    			j++;
    		}
    		fep->tx_bounce[i] = (unsigned char *) mem_addr;
    
    		/* 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;
    
    	/* Set receive and transmit descriptor base.
    	*/
    
    	writel(fep->bd_dma, fep->hwp + FEC_R_DES_START);
    	writel((unsigned long)fep->bd_dma + sizeof(cbd_t) * RX_RING_SIZE,
    			fep->hwp + FEC_X_DES_START);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    #ifdef HAVE_mii_link_interrupt
    	fec_request_mii_intr(dev);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	writel(0, fep->hwp + FEC_GRP_HASH_TABLE_HIGH);
    	writel(0, fep->hwp + FEC_GRP_HASH_TABLE_LOW);
    	writel(PKT_MAXBLR_SIZE, fep->hwp + FEC_R_BUFF_SIZE);
    	writel(2, fep->hwp + FEC_ECNTRL);
    	writel(0, fep->hwp + FEC_R_DES_ACTIVE);
    
    	writel(0, fep->hwp + FEC_HASH_TABLE_HIGH);
    	writel(0, fep->hwp + FEC_HASH_TABLE_LOW);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	/* The FEC Ethernet specific entries in the device structure. */
    	dev->open = fec_enet_open;
    	dev->hard_start_xmit = fec_enet_start_xmit;
    	dev->tx_timeout = fec_timeout;
    	dev->watchdog_timeo = TX_TIMEOUT;
    	dev->stop = fec_enet_close;
    	dev->set_multicast_list = set_multicast_list;
    
    	for (i=0; i<NMII-1; i++)
    		mii_cmds[i].mii_next = &mii_cmds[i+1];
    	mii_free = mii_cmds;
    
    	/* setup MII interface */
    
    	writel(OPT_FRAME_SIZE | 0x04, fep->hwp + FEC_R_CNTRL);
    	writel(0, fep->hwp + FEC_X_CNTRL);
    
    
    	/*
    	 * Set MII speed to 2.5 MHz
    	 */
    	fep->phy_speed = ((((clk_get_rate(fep->clk) / 2 + 4999999)
    					/ 2500000) / 2) & 0x3F) << 1;
    
    	writel(fep->phy_speed, fep->hwp + FEC_MII_SPEED);
    
    	fec_restart(dev, 0);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	/* Clear and enable interrupts */
    
    	writel(0xffc00000, fep->hwp + FEC_IEVENT);
    	writel(FEC_ENET_TXF | FEC_ENET_RXF | FEC_ENET_MII,
    			fep->hwp + FEC_IMASK);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	/* Queue up command to detect the PHY and initialize the
    	 * remainder of the interface.
    	 */
    	fep->phy_id_done = 0;
    	fep->phy_addr = 0;
    	mii_queue(dev, mk_mii_read(MII_REG_PHYIR1), mii_discover_phy);
    
    	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
    	volatile cbd_t *bdp;
    	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
    
    
    	/* Set station address.	*/
    
    	fec_set_mac_address(dev);
    
    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);
    
    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(cbd_t) * 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;
    	for (i=0; i<=TX_RING_MOD_MASK; i++) {
    		if (fep->tx_skbuff[i] != NULL) {
    			dev_kfree_skb_any(fep->tx_skbuff[i]);
    			fep->tx_skbuff[i] = NULL;
    		}
    	}
    
    
    	/* Initialize the receive buffer descriptors. */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	bdp = fep->rx_bd_base;
    	for (i=0; i<RX_RING_SIZE; i++) {
    
    
    		/* Initialize the BD for every fragment in the page. */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		bdp->cbd_sc = BD_ENET_RX_EMPTY;
    		bdp++;
    	}
    
    
    	/* Set the last buffer to wrap. */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	bdp--;
    	bdp->cbd_sc |= BD_SC_WRAP;
    
    
    	/* ...and the same for transmmit. */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	bdp = fep->tx_bd_base;
    	for (i=0; i<TX_RING_SIZE; i++) {
    
    
    		/* Initialize the BD for every fragment in the page. */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		bdp->cbd_sc = 0;
    		bdp->cbd_bufaddr = 0;
    		bdp++;
    	}
    
    
    	/* Set the last buffer to wrap. */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	bdp--;
    	bdp->cbd_sc |= BD_SC_WRAP;
    
    
    	/* 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;
    
    
    	/* Set MII speed. */
    	writel(fep->phy_speed, fep->hwp + FEC_MII_SPEED);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	/* And last, enable the transmit and receive processing. */
    	writel(2, fep->hwp + FEC_ECNTRL);
    	writel(0, fep->hwp + FEC_R_DES_ACTIVE);
    
    	/* Enable interrupts we wish to service. */
    	writel(FEC_ENET_TXF | FEC_ENET_RXF | FEC_ENET_MII,
    			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
    
    
    	/*
    	** 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(FEC_ENET_MII, fep->hwp + FEC_IMASK);
    	writel(fep->phy_speed, fep->hwp + FEC_MII_SPEED);
    
    static int __devinit
    fec_probe(struct platform_device *pdev)
    {
    	struct fec_enet_private *fep;
    	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);
    
    	/* 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);