Skip to content
Snippets Groups Projects
tsec.c 10.6 KiB
Newer Older
  • Learn to ignore specific revisions
  • /*
     * tsec.c
     * Motorola Three Speed Ethernet Controller driver
     *
     * This software may be used and distributed according to the
     * terms of the GNU Public License, Version 2, incorporated
     * herein by reference.
     *
     * (C) Copyright 2003, Motorola, Inc.
     * maintained by Xianghua Xiao (x.xiao@motorola.com)
     * author Andy Fleming
     *
     */
    
    #include <config.h>
    #include <mpc85xx.h>
    #include <common.h>
    #include <malloc.h>
    #include <net.h>
    #include <command.h>
    
    #if defined(CONFIG_TSEC_ENET)
    #include "tsec.h"
    
    #define TX_BUF_CNT 2
    
    #undef TSEC_DEBUG
    #ifdef TSEC_DEBUG
    #define DBGPRINT(x) printf(x)
    #else
    #define DBGPRINT(x)
    #endif
    
    static uint rxIdx;	/* index of the current RX buffer */
    static uint txIdx;	/* index of the current TX buffer */
    
    typedef volatile struct rtxbd {
    	txbd8_t txbd[TX_BUF_CNT];
    	rxbd8_t rxbd[PKTBUFSRX];
    }  RTXBD;
    
    #ifdef __GNUC__
    static RTXBD rtx __attribute__ ((aligned(8)));
    #else
    #error "rtx must be 64-bit aligned"
    #endif
    
    static int tsec_send(struct eth_device* dev, volatile void *packet, int length);
    static int tsec_recv(struct eth_device* dev);
    static int tsec_init(struct eth_device* dev, bd_t * bd);
    static void tsec_halt(struct eth_device* dev);
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    static void init_registers(volatile tsec_t *regs);
    static void startup_tsec(volatile tsec_t *regs);
    static void init_phy(volatile tsec_t *regs);
    
    
    /* Initialize device structure.  returns 0 on failure, 1 on
     * success */
    int tsec_initialize(bd_t *bis)
    {
    	struct eth_device* dev;
    	int i;
    
    	dev = (struct eth_device*) malloc(sizeof *dev);
    
    	if(dev == NULL)
    		return 0;
    
    	memset(dev, 0, sizeof *dev);
    
    	sprintf(dev->name, "MOTOROLA ETHERNET");
    	dev->iobase = 0;
    	dev->priv   = 0;
    	dev->init   = tsec_init;
    	dev->halt   = tsec_halt;
    	dev->send   = tsec_send;
    	dev->recv   = tsec_recv;
    
    	/* Tell u-boot to get the addr from the env */
    	for(i=0;i<6;i++)
    		dev->enetaddr[i] = 0;
    
    	eth_register(dev);
    
    	return 1;
    }
    
    
    /* Initializes data structures and registers for the controller,
     * and brings the interface up */
    int tsec_init(struct eth_device* dev, bd_t * bd)
    {
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	volatile tsec_t *regs;
    
    	uint tempval;
    	char tmpbuf[MAC_ADDR_LEN];
    	int i;
    
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	regs = (volatile tsec_t *)(TSEC_BASE_ADDR);
    
    
    	/* Make sure the controller is stopped */
    	tsec_halt(dev);
    
    	/* Reset the MAC */
    	regs->maccfg1 |= MACCFG1_SOFT_RESET;
    
    	/* Clear MACCFG1[Soft_Reset] */
    	regs->maccfg1 &= ~(MACCFG1_SOFT_RESET);
    
    	/* Init MACCFG2.  Defaults to GMII/MII */
    	regs->maccfg2 = MACCFG2_INIT_SETTINGS;
    
    	/* Init ECNTRL */
    	regs->ecntrl = ECNTRL_INIT_SETTINGS;
    
    	/* Copy the station address into the address registers.
    	 * Backwards, because little endian MACS are dumb */
    	for(i=0;i<MAC_ADDR_LEN;i++) {
    		tmpbuf[MAC_ADDR_LEN - 1 - i] = bd->bi_enetaddr[i];
    	}
    	(uint)(regs->macstnaddr1) = *((uint *)(tmpbuf));
    
    	tempval = *((uint *)(tmpbuf +4));
    
    	(uint)(regs->macstnaddr2) = tempval;
    
    	/* Initialize the PHY */
    	init_phy(regs);
    
    	/* reset the indices to zero */
    	rxIdx = 0;
    	txIdx = 0;
    
    	/* Clear out (for the most part) the other registers */
    	init_registers(regs);
    
    	/* Ready the device for tx/rx */
    	startup_tsec(regs);
    
    	return 1;
    
    }
    
    
    /* Reads from the register at offset in the PHY at phyid, */
    /* using the register set defined in regbase.  It waits until the */
    /* bits in the miimstat are valid (miimind notvalid bit cleared), */
    /* and then passes those bits on to the variable specified in */
    /* value */
    /* Before it does the read, it needs to clear the command field */
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    uint read_phy_reg(volatile tsec_t *regbase, uint phyid, uint offset)
    
    {
    	uint value;
    
    	/* Put the address of the phy, and the register number into
    	 * MIIMADD
    	 */
    	regbase->miimadd = (phyid << 8) | offset;
    
    	/* Clear the command register, and wait */
    	regbase->miimcom = 0;
    	asm("msync");
    
    	/* Initiate a read command, and wait */
    	regbase->miimcom = MIIM_READ_COMMAND;
    	asm("msync");
    
    	/* Wait for the the indication that the read is done */
    	while((regbase->miimind & (MIIMIND_NOTVALID | MIIMIND_BUSY)));
    
    	/* Grab the value read from the PHY */
    	value = regbase->miimstat;
    
    	return value;
    }
    
    /* Setup the PHY */
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    static void init_phy(volatile tsec_t *regs)
    
    {
    	uint testval;
    	unsigned int timeout = TSEC_TIMEOUT;
    
    	/* Assign a Physical address to the TBI */
    	regs->tbipa=TBIPA_VALUE;
    
    	/* reset the management interface */
    	regs->miimcfg=MIIMCFG_RESET;
    
    	regs->miimcfg=MIIMCFG_INIT_VALUE;
    
    	/* Wait until the bus is free */
    	while(regs->miimind & MIIMIND_BUSY);
    
    #ifdef CONFIG_PHY_CIS8201
    	/* override PHY config settings */
    	write_phy_reg(regs, 0, MIIM_AUX_CONSTAT, MIIM_AUXCONSTAT_INIT);
    
    	/* Set up interface mode */
    	write_phy_reg(regs, 0, MIIM_EXT_CON1, MIIM_EXTCON1_INIT);
    #endif
    
    	/* Set the PHY to gigabit, full duplex, Auto-negotiate */
    	write_phy_reg(regs, 0, MIIM_CONTROL, MIIM_CONTROL_INIT);
    
    	/* Wait until TBI_STATUS indicates AN is done */
    	DBGPRINT("Waiting for Auto-negotiation to complete\n");
    	testval=read_phy_reg(regs, 0, MIIM_TBI_STATUS);
    
    	while((!(testval & MIIM_TBI_STATUS_AN_DONE))&& timeout--) {
    		testval=read_phy_reg(regs, 0, MIIM_TBI_STATUS);
    	}
    
    	if(testval & MIIM_TBI_STATUS_AN_DONE)
    		DBGPRINT("Auto-negotiation done\n");
    	else
    		DBGPRINT("Auto-negotiation timed-out.\n");
    
    #ifdef CONFIG_PHY_CIS8201
    	/* Find out what duplexity (duplicity?) we have */
    	/* Read it twice to make sure */
    	testval=read_phy_reg(regs, 0, MIIM_AUX_CONSTAT);
    
    	if(testval & MIIM_AUXCONSTAT_DUPLEX) {
    		DBGPRINT("Enet starting in full duplex\n");
    		regs->maccfg2 |= MACCFG2_FULL_DUPLEX;
    	} else {
    		DBGPRINT("Enet starting in half duplex\n");
    		regs->maccfg2 &= ~MACCFG2_FULL_DUPLEX;
    	}
    
    	/* Also, we look to see what speed we are at
    	 * if Gigabit, MACCFG2 goes in GMII, otherwise,
    	 * MII mode.
    	 */
    	if((testval & MIIM_AUXCONSTAT_SPEED) != MIIM_AUXCONSTAT_GBIT) {
    		if((testval & MIIM_AUXCONSTAT_SPEED) == MIIM_AUXCONSTAT_100)
    			DBGPRINT("Enet starting in 100BT\n");
    		else
    			DBGPRINT("Enet starting in 10BT\n");
    
    		/* mark the mode in MACCFG2 */
    		regs->maccfg2 = ((regs->maccfg2&~(MACCFG2_IF)) | MACCFG2_MII);
    	} else {
    		DBGPRINT("Enet starting in 1000BT\n");
    	}
    
    #endif
    
    #ifdef CONFIG_PHY_M88E1011
    	/* Read the PHY to see what speed and duplex we are */
    	testval=read_phy_reg(regs, 0, MIIM_PHY_STATUS);
    
    	timeout = TSEC_TIMEOUT;
    	while((!(testval & MIIM_PHYSTAT_SPDDONE)) && timeout--) {
    		testval = read_phy_reg(regs,0,MIIM_PHY_STATUS);
    	}
    
    	if(!(testval & MIIM_PHYSTAT_SPDDONE))
    		DBGPRINT("Enet: Speed not resolved\n");
    
    	testval=read_phy_reg(regs, 0, MIIM_PHY_STATUS);
    	if(testval & MIIM_PHYSTAT_DUPLEX) {
    		DBGPRINT("Enet starting in Full Duplex\n");
    		regs->maccfg2 |= MACCFG2_FULL_DUPLEX;
    	} else {
    		DBGPRINT("Enet starting in Half Duplex\n");
    		regs->maccfg2 &= ~MACCFG2_FULL_DUPLEX;
    	}
    
    	if(!((testval&MIIM_PHYSTAT_SPEED) == MIIM_PHYSTAT_GBIT)) {
    		if((testval & MIIM_PHYSTAT_SPEED) == MIIM_PHYSTAT_100)
    			DBGPRINT("Enet starting in 100BT\n");
    		else
    			DBGPRINT("Enet starting in 10BT\n");
    
    		regs->maccfg2 = ((regs->maccfg2&~(MACCFG2_IF)) | MACCFG2_MII);
    	} else {
    		DBGPRINT("Enet starting in 1000BT\n");
    	}
    #endif
    
    }
    
    
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    static void init_registers(volatile tsec_t *regs)
    
    {
    	/* Clear IEVENT */
    	regs->ievent = IEVENT_INIT_CLEAR;
    
    	regs->imask = IMASK_INIT_CLEAR;
    
    	regs->hash.iaddr0 = 0;
    	regs->hash.iaddr1 = 0;
    	regs->hash.iaddr2 = 0;
    	regs->hash.iaddr3 = 0;
    	regs->hash.iaddr4 = 0;
    	regs->hash.iaddr5 = 0;
    	regs->hash.iaddr6 = 0;
    	regs->hash.iaddr7 = 0;
    
    	regs->hash.gaddr0 = 0;
    	regs->hash.gaddr1 = 0;
    	regs->hash.gaddr2 = 0;
    	regs->hash.gaddr3 = 0;
    	regs->hash.gaddr4 = 0;
    	regs->hash.gaddr5 = 0;
    	regs->hash.gaddr6 = 0;
    	regs->hash.gaddr7 = 0;
    
    	regs->rctrl = 0x00000000;
    
    	/* Init RMON mib registers */
    	memset((void *)&(regs->rmon), 0, sizeof(rmon_mib_t));
    
    	regs->rmon.cam1 = 0xffffffff;
    	regs->rmon.cam2 = 0xffffffff;
    
    	regs->mrblr = MRBLR_INIT_SETTINGS;
    
    	regs->minflr = MINFLR_INIT_SETTINGS;
    
    	regs->attr = ATTR_INIT_SETTINGS;
    	regs->attreli = ATTRELI_INIT_SETTINGS;
    
    }
    
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    static void startup_tsec(volatile tsec_t *regs)
    
    {
    	int i;
    
    	/* Point to the buffer descriptors */
    	regs->tbase = (unsigned int)(&rtx.txbd[txIdx]);
    	regs->rbase = (unsigned int)(&rtx.rxbd[rxIdx]);
    
    	/* Initialize the Rx Buffer descriptors */
    	for (i = 0; i < PKTBUFSRX; i++) {
    		rtx.rxbd[i].status = RXBD_EMPTY;
    		rtx.rxbd[i].length = 0;
    		rtx.rxbd[i].bufPtr = (uint)NetRxPackets[i];
    	}
    	rtx.rxbd[PKTBUFSRX -1].status |= RXBD_WRAP;
    
    	/* Initialize the TX Buffer Descriptors */
    	for(i=0; i<TX_BUF_CNT; i++) {
    		rtx.txbd[i].status = 0;
    		rtx.txbd[i].length = 0;
    		rtx.txbd[i].bufPtr = 0;
    	}
    	rtx.txbd[TX_BUF_CNT -1].status |= TXBD_WRAP;
    
    	/* Enable Transmit and Receive */
    	regs->maccfg1 |= (MACCFG1_RX_EN | MACCFG1_TX_EN);
    
    	/* Tell the DMA it is clear to go */
    	regs->dmactrl |= DMACTRL_INIT_SETTINGS;
    	regs->tstat = TSTAT_CLEAR_THALT;
    	regs->dmactrl &= ~(DMACTRL_GRS | DMACTRL_GTS);
    }
    
    /* This returns the status bits of the device.  The return value
     * is never checked, and this is what the 8260 driver did, so we
     * do the same.  Presumably, this would be zero if there were no
     * errors */
    static int tsec_send(struct eth_device* dev, volatile void *packet, int length)
    {
    	int i;
    	int result = 0;
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	volatile tsec_t * regs = (volatile tsec_t *)(TSEC_BASE_ADDR);
    
    
    	/* Find an empty buffer descriptor */
    	for(i=0; rtx.txbd[txIdx].status & TXBD_READY; i++) {
    		if (i >= TOUT_LOOP) {
    			DBGPRINT("tsec: tx buffers full\n");
    			return result;
    		}
    	}
    
    	rtx.txbd[txIdx].bufPtr = (uint)packet;
    	rtx.txbd[txIdx].length = length;
    	rtx.txbd[txIdx].status |= (TXBD_READY | TXBD_LAST | TXBD_CRC | TXBD_INTERRUPT);
    
    	/* Tell the DMA to go */
    	regs->tstat = TSTAT_CLEAR_THALT;
    
    	/* Wait for buffer to be transmitted */
    	for(i=0; rtx.txbd[txIdx].status & TXBD_READY; i++) {
    		if (i >= TOUT_LOOP) {
    			DBGPRINT("tsec: tx error\n");
    			return result;
    		}
    	}
    
    	txIdx = (txIdx + 1) % TX_BUF_CNT;
    	result = rtx.txbd[txIdx].status & TXBD_STATS;
    
    	return result;
    }
    
    static int tsec_recv(struct eth_device* dev)
    {
    	int length;
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	volatile tsec_t *regs = (volatile tsec_t *)(TSEC_BASE_ADDR);
    
    
    	while(!(rtx.rxbd[rxIdx].status & RXBD_EMPTY)) {
    
    		length = rtx.rxbd[rxIdx].length;
    
    		/* Send the packet up if there were no errors */
    		if (!(rtx.rxbd[rxIdx].status & RXBD_STATS)) {
    			NetReceive(NetRxPackets[rxIdx], length - 4);
    		}
    
    		rtx.rxbd[rxIdx].length = 0;
    
    		/* Set the wrap bit if this is the last element in the list */
    		rtx.rxbd[rxIdx].status = RXBD_EMPTY | (((rxIdx + 1) == PKTBUFSRX) ? RXBD_WRAP : 0);
    
    		rxIdx = (rxIdx + 1) % PKTBUFSRX;
    	}
    
    	if(regs->ievent&IEVENT_BSY) {
    		regs->ievent = IEVENT_BSY;
    		regs->rstat = RSTAT_CLEAR_RHALT;
    	}
    
    	return -1;
    
    }
    
    
    static void tsec_halt(struct eth_device* dev)
    {
    
    Wolfgang Denk's avatar
    Wolfgang Denk committed
    	volatile tsec_t *regs = (volatile tsec_t *)(TSEC_BASE_ADDR);
    
    
    	regs->dmactrl &= ~(DMACTRL_GRS | DMACTRL_GTS);
    	regs->dmactrl |= (DMACTRL_GRS | DMACTRL_GTS);
    
    	while(!(regs->ievent & (IEVENT_GRSC | IEVENT_GTSC)));
    
    	regs->maccfg1 &= ~(MACCFG1_TX_EN | MACCFG1_RX_EN);
    
    }
    #endif /* CONFIG_TSEC_ENET */