/* * 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); static void init_registers(tsec_t *regs); static void startup_tsec(tsec_t *regs); static void init_phy(tsec_t *regs); uint read_phy_reg(tsec_t *regbase, uint phyid, uint offset); static int phy_id = -1; /* Initialize device structure. returns 0 on failure, 1 on * success */ int tsec_initialize(bd_t *bis) { struct eth_device* dev; int i; tsec_t *regs = (tsec_t *)(TSEC_BASE_ADDR); dev = (struct eth_device*) malloc(sizeof *dev); if(dev == NULL) return 0; memset(dev, 0, sizeof *dev); sprintf(dev->name, "MOTO 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); /* Reconfigure the PHY to advertise everything here * so that it works with both gigabit and 10/100 */ #ifdef CONFIG_PHY_M88E1011 /* 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); /* Locate PHYs. Skip TBIPA, which we know is 31. */ for (i=0; i<31; i++) { if (read_phy_reg(regs, i, 2) == 0x141) { if (phy_id == -1) phy_id = i; #ifdef TSEC_DEBUG printf("Found Marvell PHY at 0x%02x\n", i); #endif } } #ifdef TSEC_DEBUG printf("Using PHY ID 0x%02x\n", phy_id); #endif write_phy_reg(regs, phy_id, MIIM_CONTROL, MIIM_CONTROL_RESET); RESET_ERRATA(regs, phy_id); /* Configure the PHY to advertise gbit and 10/100 */ write_phy_reg(regs, phy_id, MIIM_GBIT_CONTROL, MIIM_GBIT_CONTROL_INIT); write_phy_reg(regs, phy_id, MIIM_ANAR, MIIM_ANAR_INIT); /* Reset the PHY so the new settings take effect */ write_phy_reg(regs, phy_id, MIIM_CONTROL, MIIM_CONTROL_RESET); #endif 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) { tsec_t *regs; uint tempval; char tmpbuf[MAC_ADDR_LEN]; int i; regs = (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 */ uint read_phy_reg(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 */ static void init_phy(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, phy_id, MIIM_CONTROL, MIIM_CONTROL_INIT); /* Wait until STATUS indicates Auto-Negotiation is done */ DBGPRINT("Waiting for Auto-negotiation to complete\n"); testval=read_phy_reg(regs, phy_id, MIIM_STATUS); while((!(testval & MIIM_STATUS_AN_DONE))&& timeout--) { testval=read_phy_reg(regs, phy_id, MIIM_STATUS); } if(testval & MIIM_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, phy_id, 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, phy_id, MIIM_PHY_STATUS); timeout = TSEC_TIMEOUT; while((!(testval & MIIM_PHYSTAT_SPDDONE)) && timeout--) { testval = read_phy_reg(regs,phy_id,MIIM_PHY_STATUS); } if(!(testval & MIIM_PHYSTAT_SPDDONE)) DBGPRINT("Enet: Speed not resolved\n"); testval=read_phy_reg(regs, phy_id, 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 } static void init_registers(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; } static void startup_tsec(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; tsec_t * regs = (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; tsec_t *regs = (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) { tsec_t *regs = (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); } #ifndef CONFIG_BITBANGMII /* * Read a MII PHY register. * * Returns: * 0 on success */ int miiphy_read(unsigned char addr, unsigned char reg, unsigned short *value) { tsec_t *regs; unsigned short rv; regs = (tsec_t *)(TSEC_BASE_ADDR); rv = (unsigned short)read_phy_reg(regs, addr, reg); *value = rv; return 0; } /* * Write a MII PHY register. * * Returns: * 0 on success */ int miiphy_write(unsigned char addr, unsigned char reg, unsigned short value) { tsec_t *regs; regs = (tsec_t *)(TSEC_BASE_ADDR); write_phy_reg(regs, addr, reg, value); return 0; } #endif /* CONFIG_BITBANGMII */ #endif /* CONFIG_TSEC_ENET */