#include "etherboot.h"
#include "nic.h"
#include "pci.h"
#include "timer.h"
#include "mii.h"
#define drv_version "v1.12"
#define drv_date "2004-03-21"
typedef unsigned char u8;
typedef signed char s8;
typedef unsigned short u16;
typedef signed short s16;
typedef unsigned int u32;
typedef signed int s32;
#define HZ 100
#define virt_to_le32desc(addr) cpu_to_le32(virt_to_bus(addr))
#define le32desc_to_virt(addr) bus_to_virt(le32_to_cpu(addr))
struct mii_if_info {
int phy_id;
int advertising;
unsigned int full_duplex:1;
};
#ifdef EDEBUG
#define dprintf(x) printf x
#else
#define dprintf(x)
#endif
#if defined(__sun)
#define strcasecmp grub_strcmp
#endif
static int mtu = 1514;
static int rx_copybreak = 0;
static int flowctrl = 1;
static char media[] = "autosense";
#define TX_RING_SIZE 2
#define TX_QUEUE_LEN 10
#define RX_RING_SIZE 4
#define TX_TIME_OUT (4*HZ)
#define PKT_BUF_SZ 1536
enum alta_offsets {
DMACtrl = 0x00,
TxListPtr = 0x04,
TxDMABurstThresh = 0x08,
TxDMAUrgentThresh = 0x09,
TxDMAPollPeriod = 0x0a,
RxDMAStatus = 0x0c,
RxListPtr = 0x10,
DebugCtrl0 = 0x1a,
DebugCtrl1 = 0x1c,
RxDMABurstThresh = 0x14,
RxDMAUrgentThresh = 0x15,
RxDMAPollPeriod = 0x16,
LEDCtrl = 0x1a,
ASICCtrl = 0x30,
EEData = 0x34,
EECtrl = 0x36,
TxStartThresh = 0x3c,
RxEarlyThresh = 0x3e,
FlashAddr = 0x40,
FlashData = 0x44,
TxStatus = 0x46,
TxFrameId = 0x47,
DownCounter = 0x18,
IntrClear = 0x4a,
IntrEnable = 0x4c,
IntrStatus = 0x4e,
MACCtrl0 = 0x50,
MACCtrl1 = 0x52,
StationAddr = 0x54,
MaxFrameSize = 0x5A,
RxMode = 0x5c,
MIICtrl = 0x5e,
MulticastFilter0 = 0x60,
MulticastFilter1 = 0x64,
RxOctetsLow = 0x68,
RxOctetsHigh = 0x6a,
TxOctetsLow = 0x6c,
TxOctetsHigh = 0x6e,
TxFramesOK = 0x70,
RxFramesOK = 0x72,
StatsCarrierError = 0x74,
StatsLateColl = 0x75,
StatsMultiColl = 0x76,
StatsOneColl = 0x77,
StatsTxDefer = 0x78,
RxMissed = 0x79,
StatsTxXSDefer = 0x7a,
StatsTxAbort = 0x7b,
StatsBcastTx = 0x7c,
StatsBcastRx = 0x7d,
StatsMcastTx = 0x7e,
StatsMcastRx = 0x7f,
RxStatus = 0x0c,
};
enum ASICCtrl_HiWord_bit {
GlobalReset = 0x0001,
RxReset = 0x0002,
TxReset = 0x0004,
DMAReset = 0x0008,
FIFOReset = 0x0010,
NetworkReset = 0x0020,
HostReset = 0x0040,
ResetBusy = 0x0400,
};
enum intr_status_bits {
IntrSummary = 0x0001, IntrPCIErr = 0x0002, IntrMACCtrl = 0x0008,
IntrTxDone = 0x0004, IntrRxDone = 0x0010, IntrRxStart = 0x0020,
IntrDrvRqst = 0x0040,
StatsMax = 0x0080, LinkChange = 0x0100,
IntrTxDMADone = 0x0200, IntrRxDMADone = 0x0400,
};
enum rx_mode_bits {
AcceptAllIPMulti = 0x20, AcceptMultiHash = 0x10, AcceptAll = 0x08,
AcceptBroadcast = 0x04, AcceptMulticast = 0x02, AcceptMyPhys =
0x01,
};
enum mac_ctrl0_bits {
EnbFullDuplex = 0x20, EnbRcvLargeFrame = 0x40,
EnbFlowCtrl = 0x100, EnbPassRxCRC = 0x200,
};
enum mac_ctrl1_bits {
StatsEnable = 0x0020, StatsDisable = 0x0040, StatsEnabled = 0x0080,
TxEnable = 0x0100, TxDisable = 0x0200, TxEnabled = 0x0400,
RxEnable = 0x0800, RxDisable = 0x1000, RxEnabled = 0x2000,
};
struct netdev_desc {
u32 next_desc;
u32 status;
u32 addr;
u32 length;
};
enum desc_status_bits {
DescOwn = 0x8000,
DescEndPacket = 0x4000,
DescEndRing = 0x2000,
LastFrag = 0x80000000,
DescIntrOnTx = 0x8000,
DescIntrOnDMADone = 0x80000000,
DisableAlign = 0x00000001,
};
static struct netdev_desc tx_ring[TX_RING_SIZE];
static unsigned char txb[PKT_BUF_SZ * TX_RING_SIZE];
static struct netdev_desc rx_ring[RX_RING_SIZE];
static unsigned char rxb[RX_RING_SIZE * PKT_BUF_SZ];
static u32 BASE;
#define EEPROM_SIZE 128
enum pci_id_flags_bits {
PCI_USES_IO = 1, PCI_USES_MEM = 2, PCI_USES_MASTER = 4,
PCI_ADDR0 = 0 << 4, PCI_ADDR1 = 1 << 4, PCI_ADDR2 =
2 << 4, PCI_ADDR3 = 3 << 4,
};
enum chip_capability_flags { CanHaveMII = 1, KendinPktDropBug = 2, };
#define PCI_IOTYPE (PCI_USES_MASTER | PCI_USES_IO | PCI_ADDR0)
#define MII_CNT 4
struct sundance_private {
const char *nic_name;
unsigned int cur_rx;
unsigned int mtu;
unsigned int flowctrl:1;
unsigned int an_enable:1;
unsigned int speed;
struct mii_if_info mii_if;
int mii_preamble_required;
unsigned char phys[MII_CNT];
unsigned char pci_rev_id;
} sdx;
static struct sundance_private *sdc;
#define EEPROM_SA_OFFSET 0x10
#define DEFAULT_INTR (IntrRxDMADone | IntrPCIErr | \
IntrDrvRqst | IntrTxDone | StatsMax | \
LinkChange)
static int eeprom_read(long ioaddr, int location);
static int mdio_read(struct nic *nic, int phy_id, unsigned int location);
static void mdio_write(struct nic *nic, int phy_id, unsigned int location,
int value);
static void set_rx_mode(struct nic *nic);
static void check_duplex(struct nic *nic)
{
int mii_lpa = mdio_read(nic, sdc->phys[0], MII_LPA);
int negotiated = mii_lpa & sdc->mii_if.advertising;
int duplex;
if (!sdc->an_enable || mii_lpa == 0xffff) {
if (sdc->mii_if.full_duplex)
outw(inw(BASE + MACCtrl0) | EnbFullDuplex,
BASE + MACCtrl0);
return;
}
duplex = (negotiated & 0x0100) || (negotiated & 0x01C0) == 0x0040;
if (sdc->mii_if.full_duplex != duplex) {
sdc->mii_if.full_duplex = duplex;
dprintf(("%s: Setting %s-duplex based on MII #%d "
"negotiated capability %4.4x.\n", sdc->nic_name,
duplex ? "full" : "half", sdc->phys[0],
negotiated));
outw(inw(BASE + MACCtrl0) | duplex ? 0x20 : 0,
BASE + MACCtrl0);
}
}
static void init_ring(struct nic *nic __unused)
{
int i;
sdc->cur_rx = 0;
for (i = 0; i < RX_RING_SIZE; i++) {
rx_ring[i].next_desc = virt_to_le32desc(&rx_ring[i + 1]);
rx_ring[i].status = 0;
rx_ring[i].length = 0;
rx_ring[i].addr = 0;
}
rx_ring[i - 1].next_desc = virt_to_le32desc(&rx_ring[0]);
for (i = 0; i < RX_RING_SIZE; i++) {
rx_ring[i].addr = virt_to_le32desc(&rxb[i * PKT_BUF_SZ]);
rx_ring[i].length = cpu_to_le32(PKT_BUF_SZ | LastFrag);
}
tx_ring[0].status = 0x00000000;
tx_ring[0].addr = virt_to_bus(&txb[0]);
tx_ring[0].next_desc = 0;
tx_ring[1].status = 0x00000000;
tx_ring[1].addr = 0;
tx_ring[1].next_desc = 0;
tx_ring[1].length = cpu_to_le32(LastFrag | PKT_BUF_SZ);
}
static void sundance_reset(struct nic *nic)
{
int i;
init_ring(nic);
outl(virt_to_le32desc(&rx_ring[0]), BASE + RxListPtr);
{
u16 addr16;
addr16 = (nic->node_addr[0] | (nic->node_addr[1] << 8));
outw(addr16, BASE + StationAddr);
addr16 = (nic->node_addr[2] | (nic->node_addr[3] << 8));
outw(addr16, BASE + StationAddr + 2);
addr16 = (nic->node_addr[4] | (nic->node_addr[5] << 8));
outw(addr16, BASE + StationAddr + 4);
}
outw(sdc->mtu + 14, BASE + MaxFrameSize);
if (sdc->mtu > 2047)
outl(inl(BASE + ASICCtrl) | 0x0c, BASE + ASICCtrl);
set_rx_mode(nic);
outw(0, BASE + DownCounter);
outb(100, BASE + RxDMAPollPeriod);
if (sdc->pci_rev_id >= 0x14)
writeb(0x01, BASE + DebugCtrl1);
outw(RxEnable | TxEnable, BASE + MACCtrl1);
for (i = 0; i < 192; i++)
txb[i] = 0xFF;
txb[0] = nic->node_addr[0];
txb[1] = nic->node_addr[1];
txb[2] = nic->node_addr[2];
txb[3] = nic->node_addr[3];
txb[4] = nic->node_addr[4];
txb[5] = nic->node_addr[5];
dprintf(("%s: Done sundance_reset, status: Rx %hX Tx %hX "
"MAC Control %hX, %hX %hX\n",
sdc->nic_name, (int) inl(BASE + RxStatus),
(int) inw(BASE + TxStatus), (int) inl(BASE + MACCtrl0),
(int) inw(BASE + MACCtrl1), (int) inw(BASE + MACCtrl0)));
}
void sundance_irq ( struct nic *nic, irq_action_t action ) {
unsigned int intr_status;
switch ( action ) {
case DISABLE :
case ENABLE :
intr_status = inw(nic->ioaddr + IntrStatus);
intr_status = intr_status & ~DEFAULT_INTR;
if ( action == ENABLE )
intr_status = intr_status | DEFAULT_INTR;
outw(intr_status, nic->ioaddr + IntrEnable);
break;
case FORCE :
outw(0x0200, BASE + ASICCtrl);
break;
}
}
static int sundance_poll(struct nic *nic, int retreive)
{
int entry = sdc->cur_rx % RX_RING_SIZE;
u32 frame_status = le32_to_cpu(rx_ring[entry].status);
int intr_status;
int pkt_len = 0;
if (!(frame_status & DescOwn))
return 0;
if(!retreive)
return 1;
intr_status = inw(nic->ioaddr + IntrStatus);
outw(intr_status, nic->ioaddr + IntrStatus);
pkt_len = frame_status & 0x1fff;
if (frame_status & 0x001f4000) {
dprintf(("Polling frame_status error\n"));
} else {
if (pkt_len < rx_copybreak) {
printf("Poll Error: pkt_len < rx_copybreak");
} else {
nic->packetlen = pkt_len;
memcpy(nic->packet, rxb +
(sdc->cur_rx * PKT_BUF_SZ), nic->packetlen);
}
}
rx_ring[entry].length = cpu_to_le32(PKT_BUF_SZ | LastFrag);
rx_ring[entry].status = 0;
entry++;
sdc->cur_rx = entry % RX_RING_SIZE;
outw(DEFAULT_INTR & ~(IntrRxDone|IntrRxDMADone),
nic->ioaddr + IntrStatus);
return 1;
}
static void sundance_transmit(struct nic *nic, const char *d,
unsigned int t,
unsigned int s,
const char *p)
{
u16 nstype;
u32 to;
outw(TxDisable, BASE + MACCtrl1);
memcpy(txb, d, ETH_ALEN);
memcpy(txb + ETH_ALEN, nic->node_addr, ETH_ALEN);
nstype = htons((u16) t);
memcpy(txb + 2 * ETH_ALEN, (u8 *) & nstype, 2);
memcpy(txb + ETH_HLEN, p, s);
s += ETH_HLEN;
s &= 0x0FFF;
while (s < ETH_ZLEN)
txb[s++] = '\0';
tx_ring[0].length = cpu_to_le32(s | LastFrag);
tx_ring[0].status = cpu_to_le32(0x00000001);
outl(virt_to_le32desc(&tx_ring[0]), BASE + TxListPtr);
outw(TxEnable, BASE + MACCtrl1);
outw(0, BASE + TxStatus);
to = currticks() + TX_TIME_OUT;
while (!(tx_ring[0].status & 0x00010000) && (currticks() < to));
if (currticks() >= to) {
printf("TX Time Out");
}
outw(TxDisable, BASE + MACCtrl1);
}
static void sundance_disable(struct dev *dev __unused)
{
outw(0x0000, BASE + IntrEnable);
outw(TxDisable | RxDisable | StatsDisable, BASE + MACCtrl1);
}
static int sundance_probe(struct dev *dev, struct pci_device *pci)
{
struct nic *nic = (struct nic *) dev;
u8 ee_data[EEPROM_SIZE];
u16 mii_ctl;
int i;
int speed;
if (pci->ioaddr == 0)
return 0;
BASE = pci->ioaddr;
printf(" sundance.c: Found %s Vendor=0x%hX Device=0x%hX\n",
pci->name, pci->vendor, pci->dev_id);
for (i = 0; i < 3; i++) {
((u16 *) ee_data)[i] =
le16_to_cpu(eeprom_read(BASE, i + EEPROM_SA_OFFSET));
}
for (i = 0; i < ETH_ALEN; i++) {
nic->node_addr[i] = ee_data[i];
}
adjust_pci_device(pci);
sdc = &sdx;
sdc->nic_name = pci->name;
sdc->mtu = mtu;
pci_read_config_byte(pci, PCI_REVISION_ID, &sdc->pci_rev_id);
dprintf(("Device revision id: %hx\n", sdc->pci_rev_id));
printf("%s: %! at ioaddr %hX, ", pci->name, nic->node_addr, BASE);
sdc->mii_preamble_required = 0;
if (1) {
int phy, phy_idx = 0;
sdc->phys[0] = 1;
sdc->mii_preamble_required++;
for (phy = 1; phy < 32 && phy_idx < MII_CNT; phy++) {
int mii_status = mdio_read(nic, phy, MII_BMSR);
if (mii_status != 0xffff && mii_status != 0x0000) {
sdc->phys[phy_idx++] = phy;
sdc->mii_if.advertising =
mdio_read(nic, phy, MII_ADVERTISE);
if ((mii_status & 0x0040) == 0)
sdc->mii_preamble_required++;
dprintf
(("%s: MII PHY found at address %d, status " "%hX advertising %hX\n", sdc->nic_name, phy, mii_status, sdc->mii_if.advertising));
}
}
sdc->mii_preamble_required--;
if (phy_idx == 0)
printf("%s: No MII transceiver found!\n",
sdc->nic_name);
sdc->mii_if.phy_id = sdc->phys[0];
}
sdc->an_enable = 1;
if (strcasecmp(media, "autosense") != 0) {
sdc->an_enable = 0;
if (strcasecmp(media, "100mbps_fd") == 0 ||
strcasecmp(media, "4") == 0) {
sdc->speed = 100;
sdc->mii_if.full_duplex = 1;
} else if (strcasecmp(media, "100mbps_hd") == 0
|| strcasecmp(media, "3") == 0) {
sdc->speed = 100;
sdc->mii_if.full_duplex = 0;
} else if (strcasecmp(media, "10mbps_fd") == 0 ||
strcasecmp(media, "2") == 0) {
sdc->speed = 10;
sdc->mii_if.full_duplex = 1;
} else if (strcasecmp(media, "10mbps_hd") == 0 ||
strcasecmp(media, "1") == 0) {
sdc->speed = 10;
sdc->mii_if.full_duplex = 0;
} else {
sdc->an_enable = 1;
}
}
if (flowctrl == 1)
sdc->flowctrl = 1;
if (inl(BASE + ASICCtrl) & 0x80) {
if (sdc->an_enable) {
sdc->speed = 100;
sdc->mii_if.full_duplex = 1;
sdc->an_enable = 0;
}
}
if (!sdc->an_enable) {
mii_ctl = 0;
mii_ctl |= (sdc->speed == 100) ? BMCR_SPEED100 : 0;
mii_ctl |= (sdc->mii_if.full_duplex) ? BMCR_FULLDPLX : 0;
mdio_write(nic, sdc->phys[0], MII_BMCR, mii_ctl);
printf("Override speed=%d, %s duplex\n",
sdc->speed,
sdc->mii_if.full_duplex ? "Full" : "Half");
}
dprintf(("ASIC Control is %x.\n", inl(BASE + ASICCtrl)));
outw(0x007f, BASE + ASICCtrl + 2);
dprintf(("ASIC Control is now %x.\n", inl(BASE + ASICCtrl)));
sundance_reset(nic);
if (sdc->an_enable) {
u16 mii_advertise, mii_lpa;
mii_advertise =
mdio_read(nic, sdc->phys[0], MII_ADVERTISE);
mii_lpa = mdio_read(nic, sdc->phys[0], MII_LPA);
mii_advertise &= mii_lpa;
if (mii_advertise & ADVERTISE_100FULL)
sdc->speed = 100;
else if (mii_advertise & ADVERTISE_100HALF)
sdc->speed = 100;
else if (mii_advertise & ADVERTISE_10FULL)
sdc->speed = 10;
else if (mii_advertise & ADVERTISE_10HALF)
sdc->speed = 10;
} else {
mii_ctl = mdio_read(nic, sdc->phys[0], MII_BMCR);
speed = (mii_ctl & BMCR_SPEED100) ? 100 : 10;
sdc->speed = speed;
printf("%s: Link changed: %dMbps ,", sdc->nic_name, speed);
printf("%s duplex.\n", (mii_ctl & BMCR_FULLDPLX) ?
"full" : "half");
}
check_duplex(nic);
if (sdc->flowctrl && sdc->mii_if.full_duplex) {
outw(inw(BASE + MulticastFilter1 + 2) | 0x0200,
BASE + MulticastFilter1 + 2);
outw(inw(BASE + MACCtrl0) | EnbFlowCtrl, BASE + MACCtrl0);
}
printf("%dMbps, %s-Duplex\n", sdc->speed,
sdc->mii_if.full_duplex ? "Full" : "Half");
dev->disable = sundance_disable;
nic->poll = sundance_poll;
nic->transmit = sundance_transmit;
nic->irqno = pci->irq;
nic->irq = sundance_irq;
nic->ioaddr = BASE;
return 1;
}
static int eeprom_read(long ioaddr, int location)
{
int boguscnt = 10000;
outw(0x0200 | (location & 0xff), ioaddr + EECtrl);
do {
if (!(inw(ioaddr + EECtrl) & 0x8000)) {
return inw(ioaddr + EEData);
}
}
while (--boguscnt > 0);
return 0;
}
#define mdio_in(mdio_addr) inb(mdio_addr)
#define mdio_out(value, mdio_addr) outb(value, mdio_addr)
#define mdio_delay(mdio_addr) inb(mdio_addr)
enum mii_reg_bits {
MDIO_ShiftClk = 0x0001, MDIO_Data = 0x0002, MDIO_EnbOutput =
0x0004,
};
#define MDIO_EnbIn (0)
#define MDIO_WRITE0 (MDIO_EnbOutput)
#define MDIO_WRITE1 (MDIO_Data | MDIO_EnbOutput)
static void mdio_sync(long mdio_addr)
{
int bits = 32;
while (--bits >= 0) {
mdio_out(MDIO_WRITE1, mdio_addr);
mdio_delay(mdio_addr);
mdio_out(MDIO_WRITE1 | MDIO_ShiftClk, mdio_addr);
mdio_delay(mdio_addr);
}
}
static int
mdio_read(struct nic *nic __unused, int phy_id, unsigned int location)
{
long mdio_addr = BASE + MIICtrl;
int mii_cmd = (0xf6 << 10) | (phy_id << 5) | location;
int i, retval = 0;
if (sdc->mii_preamble_required)
mdio_sync(mdio_addr);
for (i = 15; i >= 0; i--) {
int dataval =
(mii_cmd & (1 << i)) ? MDIO_WRITE1 : MDIO_WRITE0;
mdio_out(dataval, mdio_addr);
mdio_delay(mdio_addr);
mdio_out(dataval | MDIO_ShiftClk, mdio_addr);
mdio_delay(mdio_addr);
}
for (i = 19; i > 0; i--) {
mdio_out(MDIO_EnbIn, mdio_addr);
mdio_delay(mdio_addr);
retval = (retval << 1) | ((mdio_in(mdio_addr) & MDIO_Data)
? 1 : 0);
mdio_out(MDIO_EnbIn | MDIO_ShiftClk, mdio_addr);
mdio_delay(mdio_addr);
}
return (retval >> 1) & 0xffff;
}
static void
mdio_write(struct nic *nic __unused, int phy_id,
unsigned int location, int value)
{
long mdio_addr = BASE + MIICtrl;
int mii_cmd =
(0x5002 << 16) | (phy_id << 23) | (location << 18) | value;
int i;
if (sdc->mii_preamble_required)
mdio_sync(mdio_addr);
for (i = 31; i >= 0; i--) {
int dataval =
(mii_cmd & (1 << i)) ? MDIO_WRITE1 : MDIO_WRITE0;
mdio_out(dataval, mdio_addr);
mdio_delay(mdio_addr);
mdio_out(dataval | MDIO_ShiftClk, mdio_addr);
mdio_delay(mdio_addr);
}
for (i = 2; i > 0; i--) {
mdio_out(MDIO_EnbIn, mdio_addr);
mdio_delay(mdio_addr);
mdio_out(MDIO_EnbIn | MDIO_ShiftClk, mdio_addr);
mdio_delay(mdio_addr);
}
return;
}
static void set_rx_mode(struct nic *nic __unused)
{
int i;
u16 mc_filter[4];
u32 rx_mode;
memset(mc_filter, 0xff, sizeof(mc_filter));
rx_mode = AcceptBroadcast | AcceptMulticast | AcceptMyPhys;
if (sdc->mii_if.full_duplex && sdc->flowctrl)
mc_filter[3] |= 0x0200;
for (i = 0; i < 4; i++)
outw(mc_filter[i], BASE + MulticastFilter0 + i * 2);
outb(rx_mode, BASE + RxMode);
return;
}
static struct pci_id sundance_nics[] = {
PCI_ROM(0x13f0, 0x0201, "sundance", "ST201 Sundance 'Alta' based Adaptor"),
PCI_ROM(0x1186, 0x1002, "dfe530txs", "D-Link DFE530TXS (Sundance ST201 Alta)"),
};
struct pci_driver sundance_driver = {
.type = NIC_DRIVER,
.name = "SUNDANCE/PCI",
.probe = sundance_probe,
.ids = sundance_nics,
.id_count = sizeof(sundance_nics) / sizeof(sundance_nics[0]),
.class = 0,
};