#define EB51
#ifdef EB50
#define __unused __attribute__((unused))
#endif
/**************************************************************************
*
* tlan.c -- Etherboot device driver for the Texas Instruments ThunderLAN
* Written 2003-2003 by Timothy Legge <tlegge@rogers.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* Portions of this code based on:
* lan.c: Linux ThunderLan Driver:
*
* by James Banks
*
* (C) 1997-1998 Caldera, Inc.
* (C) 1998 James Banks
* (C) 1999-2001 Torben Mathiasen
* (C) 2002 Samuel Chessman
*
* $Revision: 1.13 $
* $Author: timlegge $
* $Date 2003/07/18 $
*
* REVISION HISTORY:
* ================
* v1.0 07-08-2003 timlegge Initial not quite working version
* v1.1 07-27-2003 timlegge Sync 5.0 and 5.1 versions
*
* Indent Options: indent -kr -i8
***************************************************************************/
/* to get some global routines like printf */
#include "etherboot.h"
/* to get the interface to the body of the program */
#include "nic.h"
/* to get the PCI support functions, if this is a PCI NIC */
#include "pci.h"
/* to get the ISA support functions, if this is an ISA NIC */
/* #include "isa.h" */
#include "timer.h"
#include "tlan.h"
/* NIC specific static variables go here */
#define HZ 100
#define TX_TIME_OUT (6*HZ)
#ifdef EB50
#define cpu_to_le32(val) (val)
#define le32_to_cpu(val) (val)
#define virt_to_bus(x) ((unsigned long) x)
#define bus_to_virt(x) ((unsigned long) x)
#endif
/* Condensed operations for readability. */
#define virt_to_le32desc(addr) cpu_to_le32(virt_to_bus(addr))
#define le32desc_to_virt(addr) bus_to_virt(le32_to_cpu(addr))
int tx_started = 0;
static void TLan_ResetLists(struct nic *nic __unused);
static void TLan_ResetAdapter(struct nic *nic __unused);
static void TLan_FinishReset(struct nic *nic __unused);
static void TLan_EeSendStart(u16);
static int TLan_EeSendByte(u16, u8, int);
static void TLan_EeReceiveByte(u16, u8 *, int);
static int TLan_EeReadByte(u16 io_base, u8, u8 *);
static void TLan_PhyDetect(struct nic *nic);
static void TLan_PhyPowerDown(struct nic *nic);
static void TLan_PhyPowerUp(struct nic *nic);
static void TLan_SetMac(struct nic *nic __unused, int areg, char *mac);
static void TLan_PhyReset(struct nic *nic);
static void TLan_PhyStartLink(struct nic *nic);
static void TLan_PhyFinishAutoNeg(struct nic *nic);
#ifdef MONITOR
static void TLan_PhyMonitor(struct nic *nic);
#endif
static void refill_rx(struct nic *nic __unused);
static int TLan_MiiReadReg(struct nic *nic __unused, u16, u16, u16 *);
static void TLan_MiiSendData(u16, u32, unsigned);
static void TLan_MiiSync(u16);
static void TLan_MiiWriteReg(struct nic *nic __unused, u16, u16, u16);
const char *media[] = {
"10BaseT-HD ", "10BaseT-FD ", "100baseTx-HD ",
"100baseTx-FD", "100baseT4", 0
};
/* This much match tlan_pci_tbl[]! */
enum tlan_nics {
NETEL10 = 0, NETEL100 = 1, NETFLEX3I = 2, THUNDER = 3, NETFLEX3B =
4, NETEL100PI = 5,
NETEL100D = 6, NETEL100I = 7, OC2183 = 8, OC2325 = 9, OC2326 =
10, NETELLIGENT_10_100_WS_5100 = 11,
NETELLIGENT_10_T2 = 12
};
struct pci_id_info {
const char *name;
int nic_id;
struct match_info {
u32 pci, pci_mask, subsystem, subsystem_mask;
u32 revision, revision_mask; /* Only 8 bits. */
} id;
u32 flags;
u16 addrOfs; /* Address Offset */
};
static struct pci_id_info tlan_pci_tbl[] = {
{"Compaq Netelligent 10 T PCI UTP", NETEL10,
{0xae340e11, 0xffffffff, 0, 0, 0, 0},
TLAN_ADAPTER_ACTIVITY_LED, 0x83},
{"Compaq Netelligent 10/100 TX PCI UTP", NETEL100,
{0xae320e11, 0xffffffff, 0, 0, 0, 0},
TLAN_ADAPTER_ACTIVITY_LED, 0x83},
{"Compaq Integrated NetFlex-3/P", NETFLEX3I,
{0xae350e11, 0xffffffff, 0, 0, 0, 0},
TLAN_ADAPTER_NONE, 0x83},
{"Compaq NetFlex-3/P", THUNDER,
{0xf1300e11, 0xffffffff, 0, 0, 0, 0},
TLAN_ADAPTER_UNMANAGED_PHY | TLAN_ADAPTER_BIT_RATE_PHY, 0x83},
{"Compaq NetFlex-3/P", NETFLEX3B,
{0xf1500e11, 0xffffffff, 0, 0, 0, 0},
TLAN_ADAPTER_NONE, 0x83},
{"Compaq Netelligent Integrated 10/100 TX UTP", NETEL100PI,
{0xae430e11, 0xffffffff, 0, 0, 0, 0},
TLAN_ADAPTER_ACTIVITY_LED, 0x83},
{"Compaq Netelligent Dual 10/100 TX PCI UTP", NETEL100D,
{0xae400e11, 0xffffffff, 0, 0, 0, 0},
TLAN_ADAPTER_NONE, 0x83},
{"Compaq Netelligent 10/100 TX Embedded UTP", NETEL100I,
{0xb0110e11, 0xffffffff, 0, 0, 0, 0},
TLAN_ADAPTER_NONE, 0x83},
{"Olicom OC-2183/2185", OC2183,
{0x0013108d, 0xffffffff, 0, 0, 0, 0},
TLAN_ADAPTER_USE_INTERN_10, 0x83},
{"Olicom OC-2325", OC2325,
{0x0012108d, 0xffffffff, 0, 0, 0, 0},
TLAN_ADAPTER_UNMANAGED_PHY, 0xF8},
{"Olicom OC-2326", OC2326,
{0x0014108d, 0xffffffff, 0, 0, 0, 0},
TLAN_ADAPTER_USE_INTERN_10, 0xF8},
{"Compaq Netelligent 10/100 TX UTP", NETELLIGENT_10_100_WS_5100,
{0xb0300e11, 0xffffffff, 0, 0, 0, 0},
TLAN_ADAPTER_ACTIVITY_LED, 0x83},
{"Compaq Netelligent 10 T/2 PCI UTP/Coax", NETELLIGENT_10_T2,
{0xb0120e11, 0xffffffff, 0, 0, 0, 0},
TLAN_ADAPTER_NONE, 0x83},
{"Compaq NetFlex-3/E", 0, /* EISA card */
{0, 0, 0, 0, 0, 0},
TLAN_ADAPTER_ACTIVITY_LED | TLAN_ADAPTER_UNMANAGED_PHY |
TLAN_ADAPTER_BIT_RATE_PHY, 0x83},
{"Compaq NetFlex-3/E", 0, /* EISA card */
{0, 0, 0, 0, 0, 0},
TLAN_ADAPTER_ACTIVITY_LED, 0x83},
{0, 0,
{0, 0, 0, 0, 0, 0},
0, 0},
};
struct TLanList {
u32 forward;
u16 cStat;
u16 frameSize;
struct {
u32 count;
u32 address;
} buffer[TLAN_BUFFERS_PER_LIST];
};
struct TLanList tx_ring[TLAN_NUM_TX_LISTS];
static unsigned char txb[TLAN_MAX_FRAME_SIZE * TLAN_NUM_TX_LISTS];
struct TLanList rx_ring[TLAN_NUM_RX_LISTS];
static unsigned char rxb[TLAN_MAX_FRAME_SIZE * TLAN_NUM_RX_LISTS];
typedef u8 TLanBuffer[TLAN_MAX_FRAME_SIZE];
int chip_idx;
/*****************************************************************
* TLAN Private Information Structure
*
****************************************************************/
struct tlan_private {
unsigned short vendor_id; /* PCI Vendor code */
unsigned short dev_id; /* PCI Device code */
const char *nic_name;
u8 *padBuffer;
u8 *rxBuffer;
struct TLanList *rx_head_desc;
u32 rxHead;
u32 rxTail;
u32 rxEocCount;
unsigned int cur_rx, dirty_rx; /* Producer/consumer ring indicies */
unsigned int cur_tx, dirty_tx;
unsigned rx_buf_sz; /* Based on mtu + Slack */
struct TLanList *txList;
struct TLanList *rxList;
u8 *txBuffer;
u32 txHead;
u32 txInProgress;
u32 txTail;
int eoc;
u32 txBusyCount;
u32 phyOnline;
u32 timerSetAt;
u32 timerType;
u32 adapterRev;
u32 aui;
u32 debug;
u32 duplex;
u32 phy[2];
u32 phyNum;
u32 speed;
u8 tlanRev;
u8 tlanFullDuplex;
char devName[8];
u8 link;
u8 is_eisa;
u8 neg_be_verbose;
} TLanPrivateInfo;
static struct tlan_private *priv;
u32 BASE;
/***************************************************************
* TLan_ResetLists
*
* Returns:
* Nothing
* Parms:
* dev The device structure with the list
* stuctures to be reset.
*
* This routine sets the variables associated with managing
* the TLAN lists to their initial values.
*
**************************************************************/
void TLan_ResetLists(struct nic *nic __unused)
{
int i;
struct TLanList *list;
priv->txHead = 0;
priv->txTail = 0;
for (i = 0; i < TLAN_NUM_TX_LISTS; i++) {
list = &tx_ring[i];
list->cStat = TLAN_CSTAT_UNUSED;
list->buffer[0].address = 0;
list->buffer[2].count = 0;
list->buffer[2].address = 0;
list->buffer[9].address = 0;
list->forward = 0;
}
priv->cur_rx = 0;
priv->rx_buf_sz = (TLAN_MAX_FRAME_SIZE);
priv->rx_head_desc = &rx_ring[0];
/* Initialize all the Rx descriptors */
for (i = 0; i < TLAN_NUM_RX_LISTS; i++) {
rx_ring[i].forward = virt_to_le32desc(&rx_ring[i + 1]);
rx_ring[i].cStat = TLAN_CSTAT_READY;
rx_ring[i].frameSize = TLAN_MAX_FRAME_SIZE;
rx_ring[i].buffer[0].count =
TLAN_MAX_FRAME_SIZE | TLAN_LAST_BUFFER;
rx_ring[i].buffer[0].address =
virt_to_le32desc(&rxb[i * TLAN_MAX_FRAME_SIZE]);
rx_ring[i].buffer[1].count = 0;
rx_ring[i].buffer[1].address = 0;
}
/* Mark the last entry as wrapping the ring */
rx_ring[i - 1].forward = virt_to_le32desc(&rx_ring[0]);
priv->dirty_rx = (unsigned int) (i - TLAN_NUM_RX_LISTS);
} /* TLan_ResetLists */
/***************************************************************
* TLan_Reset
*
* Returns:
* 0
* Parms:
* dev Pointer to device structure of adapter
* to be reset.
*
* This function resets the adapter and it's physical
* device. See Chap. 3, pp. 9-10 of the "ThunderLAN
* Programmer's Guide" for details. The routine tries to
* implement what is detailed there, though adjustments
* have been made.
*
**************************************************************/
void TLan_ResetAdapter(struct nic *nic __unused)
{
int i;
u32 addr;
u32 data;
u8 data8;
priv->tlanFullDuplex = FALSE;
priv->phyOnline = 0;
/* 1. Assert reset bit. */
data = inl(BASE + TLAN_HOST_CMD);
data |= TLAN_HC_AD_RST;
outl(data, BASE + TLAN_HOST_CMD);
udelay(1000);
/* 2. Turn off interrupts. ( Probably isn't necessary ) */
data = inl(BASE + TLAN_HOST_CMD);
data |= TLAN_HC_INT_OFF;
outl(data, BASE + TLAN_HOST_CMD);
/* 3. Clear AREGs and HASHs. */
for (i = TLAN_AREG_0; i <= TLAN_HASH_2; i += 4) {
TLan_DioWrite32(BASE, (u16) i, 0);
}
/* 4. Setup NetConfig register. */
data =
TLAN_NET_CFG_1FRAG | TLAN_NET_CFG_1CHAN | TLAN_NET_CFG_PHY_EN;
TLan_DioWrite16(BASE, TLAN_NET_CONFIG, (u16) data);
/* 5. Load Ld_Tmr and Ld_Thr in HOST_CMD. */
outl(TLAN_HC_LD_TMR | 0x3f, BASE + TLAN_HOST_CMD);
outl(TLAN_HC_LD_THR | 0x0, BASE + TLAN_HOST_CMD);
/* 6. Unreset the MII by setting NMRST (in NetSio) to 1. */
outw(TLAN_NET_SIO, BASE + TLAN_DIO_ADR);
addr = BASE + TLAN_DIO_DATA + TLAN_NET_SIO;
TLan_SetBit(TLAN_NET_SIO_NMRST, addr);
/* 7. Setup the remaining registers. */
if (priv->tlanRev >= 0x30) {
data8 = TLAN_ID_TX_EOC | TLAN_ID_RX_EOC;
TLan_DioWrite8(BASE, TLAN_INT_DIS, data8);
}
TLan_PhyDetect(nic);
data = TLAN_NET_CFG_1FRAG | TLAN_NET_CFG_1CHAN;
if (tlan_pci_tbl[chip_idx].flags & TLAN_ADAPTER_BIT_RATE_PHY) {
data |= TLAN_NET_CFG_BIT;
if (priv->aui == 1) {
TLan_DioWrite8(BASE, TLAN_ACOMMIT, 0x0a);
} else if (priv->duplex == TLAN_DUPLEX_FULL) {
TLan_DioWrite8(BASE, TLAN_ACOMMIT, 0x00);
priv->tlanFullDuplex = TRUE;
} else {
TLan_DioWrite8(BASE, TLAN_ACOMMIT, 0x08);
}
}
if (priv->phyNum == 0) {
data |= TLAN_NET_CFG_PHY_EN;
}
TLan_DioWrite16(BASE, TLAN_NET_CONFIG, (u16) data);
if (tlan_pci_tbl[chip_idx].flags & TLAN_ADAPTER_UNMANAGED_PHY) {
TLan_FinishReset(nic);
} else {
TLan_PhyPowerDown(nic);
}
} /* TLan_ResetAdapter */
void TLan_FinishReset(struct nic *nic)
{
u8 data;
u32 phy;
u8 sio;
u16 status;
u16 partner;
u16 tlphy_ctl;
u16 tlphy_par;
u16 tlphy_id1, tlphy_id2;
int i;
phy = priv->phy[priv->phyNum];
data = TLAN_NET_CMD_NRESET | TLAN_NET_CMD_NWRAP;
if (priv->tlanFullDuplex) {
data |= TLAN_NET_CMD_DUPLEX;
}
TLan_DioWrite8(BASE, TLAN_NET_CMD, data);
data = TLAN_NET_MASK_MASK4 | TLAN_NET_MASK_MASK5;
if (priv->phyNum == 0) {
data |= TLAN_NET_MASK_MASK7;
}
TLan_DioWrite8(BASE, TLAN_NET_MASK, data);
TLan_DioWrite16(BASE, TLAN_MAX_RX, ((1536) + 7) & ~7);
TLan_MiiReadReg(nic, phy, MII_GEN_ID_HI, &tlphy_id1);
TLan_MiiReadReg(nic, phy, MII_GEN_ID_LO, &tlphy_id2);
if ((tlan_pci_tbl[chip_idx].flags & TLAN_ADAPTER_UNMANAGED_PHY)
|| (priv->aui)) {
status = MII_GS_LINK;
printf("TLAN: %s: Link forced.\n", priv->nic_name);
} else {
TLan_MiiReadReg(nic, phy, MII_GEN_STS, &status);
udelay(1000);
TLan_MiiReadReg(nic, phy, MII_GEN_STS, &status);
if ((status & MII_GS_LINK) && /* We only support link info on Nat.Sem. PHY's */
(tlphy_id1 == NAT_SEM_ID1)
&& (tlphy_id2 == NAT_SEM_ID2)) {
TLan_MiiReadReg(nic, phy, MII_AN_LPA, &partner);
TLan_MiiReadReg(nic, phy, TLAN_TLPHY_PAR,
&tlphy_par);
printf("TLAN: %s: Link active with ",
priv->nic_name);
if (!(tlphy_par & TLAN_PHY_AN_EN_STAT)) {
printf("forced 10%sMbps %s-Duplex\n",
tlphy_par & TLAN_PHY_SPEED_100 ? ""
: "0",
tlphy_par & TLAN_PHY_DUPLEX_FULL ?
"Full" : "Half");
} else {
printf
("AutoNegotiation enabled, at 10%sMbps %s-Duplex\n",
tlphy_par & TLAN_PHY_SPEED_100 ? "" :
"0",
tlphy_par & TLAN_PHY_DUPLEX_FULL ?
"Full" : "Half");
printf("TLAN: Partner capability: ");
for (i = 5; i <= 10; i++)
if (partner & (1 << i))
printf("%s", media[i - 5]);
printf("\n");
}
TLan_DioWrite8(BASE, TLAN_LED_REG, TLAN_LED_LINK);
#ifdef MONITOR
/* We have link beat..for now anyway */
priv->link = 1;
/*Enabling link beat monitoring */
/* TLan_SetTimer( nic, (10*HZ), TLAN_TIMER_LINK_BEAT ); */
mdelay(10000);
TLan_PhyMonitor(nic);
#endif
} else if (status & MII_GS_LINK) {
printf("TLAN: %s: Link active\n", priv->nic_name);
TLan_DioWrite8(BASE, TLAN_LED_REG, TLAN_LED_LINK);
}
}
if (priv->phyNum == 0) {
TLan_MiiReadReg(nic, phy, TLAN_TLPHY_CTL, &tlphy_ctl);
tlphy_ctl |= TLAN_TC_INTEN;
TLan_MiiWriteReg(nic, phy, TLAN_TLPHY_CTL, tlphy_ctl);
sio = TLan_DioRead8(BASE, TLAN_NET_SIO);
sio |= TLAN_NET_SIO_MINTEN;
TLan_DioWrite8(BASE, TLAN_NET_SIO, sio);
}
if (status & MII_GS_LINK) {
TLan_SetMac(nic, 0, nic->node_addr);
priv->phyOnline = 1;
outb((TLAN_HC_INT_ON >> 8), BASE + TLAN_HOST_CMD + 1);
/* if ( debug >= 1 && debug != TLAN_DEBUG_PROBE ) {
outb( ( TLAN_HC_REQ_INT >> 8 ), BASE + TLAN_HOST_CMD + 1 );
}
*/
outl(virt_to_bus(&rx_ring), BASE + TLAN_CH_PARM);
outl(TLAN_HC_GO | TLAN_HC_RT, BASE + TLAN_HOST_CMD);
} else {
printf
("TLAN: %s: Link inactive, will retry in 10 secs...\n",
priv->nic_name);
/* TLan_SetTimer( nic, (10*HZ), TLAN_TIMER_FINISH_RESET ); */
mdelay(10000);
TLan_FinishReset(nic);
return;
}
} /* TLan_FinishReset */
/**************************************************************************
POLL - Wait for a frame
***************************************************************************/
static int tlan_poll(struct nic *nic)
{
/* return true if there's an