blob: 4a5731e6ae022e5d018933098337aac966e50aba [file] [log] [blame]
/*
* 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);
/* 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)
{
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, 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
}
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);
}
#endif /* CONFIG_TSEC_ENET */