/* * 3c595.c -- 3COM 3C595 Fast Etherlink III PCI driver for etherboot * * Copyright (C) 2000 Shusuke Nisiyama <shu@athena.qe.eng.hokudai.ac.jp> * All rights reserved. * Mar. 14, 2000 * * This software may be used, modified, copied, distributed, and sold, in * both source and binary form provided that the above copyright and these * terms are retained. Under no circumstances are the authors responsible for * the proper functioning of this software, nor do the authors assume any * responsibility for damages incurred with its use. * * This code is based on Martin Renters' etherboot-4.4.3 3c509.c and * Herb Peyerl's FreeBSD 3.4-RELEASE if_vx.c driver. * * Copyright (C) 1993-1994, David Greenman, Martin Renters. * Copyright (C) 1993-1995, Andres Vega Garcia. * Copyright (C) 1995, Serge Babkin. * * Copyright (c) 1994 Herb Peyerl <hpeyerl@novatel.ca> * */ /* #define EDEBUG */ #include "etherboot.h" #include "nic.h" #include "pci.h" #include "3c595.h" #include "timer.h" static unsigned short eth_nic_base, eth_asic_base; static unsigned short vx_connector, vx_connectors; static struct connector_entry { int bit; char *name; } conn_tab[VX_CONNECTORS] = { #define CONNECTOR_UTP 0 { 0x08, "utp"}, #define CONNECTOR_AUI 1 { 0x20, "aui"}, /* dummy */ { 0, "???"}, #define CONNECTOR_BNC 3 { 0x10, "bnc"}, #define CONNECTOR_TX 4 { 0x02, "tx"}, #define CONNECTOR_FX 5 { 0x04, "fx"}, #define CONNECTOR_MII 6 { 0x40, "mii"}, { 0, "???"} }; static void vxgetlink(void); static void vxsetlink(void); #define udelay(n) waiton_timer2(((n)*TICKS_PER_MS)/1000) /************************************************************************** ETH_RESET - Reset adapter ***************************************************************************/ static void t595_reset(struct nic *nic) { int i, j; /*********************************************************** Reset 3Com 595 card *************************************************************/ /* stop card */ outw(RX_DISABLE, BASE + VX_COMMAND); outw(RX_DISCARD_TOP_PACK, BASE + VX_COMMAND); VX_BUSY_WAIT; outw(TX_DISABLE, BASE + VX_COMMAND); outw(STOP_TRANSCEIVER, BASE + VX_COMMAND); udelay(8000); outw(RX_RESET, BASE + VX_COMMAND); VX_BUSY_WAIT; outw(TX_RESET, BASE + VX_COMMAND); VX_BUSY_WAIT; outw(C_INTR_LATCH, BASE + VX_COMMAND); outw(SET_RD_0_MASK, BASE + VX_COMMAND); outw(SET_INTR_MASK, BASE + VX_COMMAND); outw(SET_RX_FILTER, BASE + VX_COMMAND); /* * initialize card */ VX_BUSY_WAIT; GO_WINDOW(0); /* Disable the card */ /* outw(0, BASE + VX_W0_CONFIG_CTRL); */ /* Configure IRQ to none */ /* outw(SET_IRQ(0), BASE + VX_W0_RESOURCE_CFG); */ /* Enable the card */ /* outw(ENABLE_DRQ_IRQ, BASE + VX_W0_CONFIG_CTRL); */ GO_WINDOW(2); /* Reload the ether_addr. */ for (i = 0; i < ETH_ALEN; i++) outb(nic->node_addr[i], BASE + VX_W2_ADDR_0 + i); outw(RX_RESET, BASE + VX_COMMAND); VX_BUSY_WAIT; outw(TX_RESET, BASE + VX_COMMAND); VX_BUSY_WAIT; /* Window 1 is operating window */ GO_WINDOW(1); for (i = 0; i < 31; i++) inb(BASE + VX_W1_TX_STATUS); outw(SET_RD_0_MASK | S_CARD_FAILURE | S_RX_COMPLETE | S_TX_COMPLETE | S_TX_AVAIL, BASE + VX_COMMAND); outw(SET_INTR_MASK | S_CARD_FAILURE | S_RX_COMPLETE | S_TX_COMPLETE | S_TX_AVAIL, BASE + VX_COMMAND); /* * Attempt to get rid of any stray interrupts that occured during * configuration. On the i386 this isn't possible because one may * already be queued. However, a single stray interrupt is * unimportant. */ outw(ACK_INTR | 0xff, BASE + VX_COMMAND); outw(SET_RX_FILTER | FIL_INDIVIDUAL | FIL_BRDCST, BASE + VX_COMMAND); vxsetlink(); /*{ int i,j; i = CONNECTOR_TX; GO_WINDOW(3); j = inl(BASE + VX_W3_INTERNAL_CFG) & ~INTERNAL_CONNECTOR_MASK; outl(BASE + VX_W3_INTERNAL_CFG, j | (i <<INTERNAL_CONNECTOR_BITS)); GO_WINDOW(4); outw(LINKBEAT_ENABLE, BASE + VX_W4_MEDIA_TYPE); GO_WINDOW(1); }*/ /* start tranciever and receiver */ outw(RX_ENABLE, BASE + VX_COMMAND); outw(TX_ENABLE, BASE + VX_COMMAND); } /************************************************************************** ETH_TRANSMIT - Transmit a frame ***************************************************************************/ static char padmap[] = { 0, 3, 2, 1}; static void t595_transmit( struct nic *nic, const char *d, /* Destination */ unsigned int t, /* Type */ unsigned int s, /* size */ const char *p) /* Packet */ { register int len; int pad; int status; #ifdef EDEBUG printf("{l=%d,t=%hX}",s+ETH_HLEN,t); #endif /* swap bytes of type */ t= htons(t); len=s+ETH_HLEN; /* actual length of packet */ pad = padmap[len & 3]; /* * The 3c595 automatically pads short packets to minimum ethernet length, * but we drop packets that are too large. Perhaps we should truncate * them instead? */ if (len + pad > ETH_FRAME_LEN) { return; } /* drop acknowledgements */ while(( status=inb(BASE + VX_W1_TX_STATUS) )& TXS_COMPLETE ) { if(status & (TXS_UNDERRUN|TXS_MAX_COLLISION|TXS_STATUS_OVERFLOW)) { outw(TX_RESET, BASE + VX_COMMAND); outw(TX_ENABLE, BASE + VX_COMMAND); } outb(0x0, BASE + VX_W1_TX_STATUS); } while (inw(BASE + VX_W1_FREE_TX) < len + pad + 4) { /* no room in FIFO */ } outw(len, BASE + VX_W1_TX_PIO_WR_1); outw(0x0, BASE + VX_W1_TX_PIO_WR_1); /* Second dword meaningless */ /* write packet */ outsw(BASE + VX_W1_TX_PIO_WR_1, d, ETH_ALEN/2); outsw(BASE + VX_W1_TX_PIO_WR_1, nic->node_addr, ETH_ALEN/2); outw(t, BASE + VX_W1_TX_PIO_WR_1); outsw(BASE + VX_W1_TX_PIO_WR_1, p, s / 2); if (s & 1) outb(*(p+s - 1), BASE + VX_W1_TX_PIO_WR_1); while (pad--) outb(0, BASE + VX_W1_TX_PIO_WR_1); /* Padding */ /* wait for Tx complete */ while((inw(BASE + VX_STATUS) & S_COMMAND_IN_PROGRESS) != 0) ; } /************************************************************************** ETH_POLL - Wait for a frame ***************************************************************************/ static int t595_poll(struct nic *nic) { /* common variables */ unsigned short type = 0; /* used by EDEBUG */ /* variables for 3C595 */ short status, cst; register short rx_fifo; cst=inw(BASE + VX_STATUS); #ifdef EDEBUG if(cst & 0x1FFF) printf("-%hX-",cst); #endif if( (cst & S_RX_COMPLETE)==0 ) { /* acknowledge everything */ outw(ACK_INTR | cst, BASE + VX_COMMAND); outw(C_INTR_LATCH, BASE + VX_COMMAND); return 0; } status = inw(BASE + VX_W1_RX_STATUS); #ifdef EDEBUG printf("*%hX*",status); #endif if (status & ERR_RX) { outw(RX_DISCARD_TOP_PACK, BASE + VX_COMMAND); return 0; } rx_fifo = status & RX_BYTES_MASK; if (rx_fifo==0) return 0; /* read packet */ #ifdef EDEBUG printf("[l=%d",rx_fifo); #endif insw(BASE + VX_W1_RX_PIO_RD_1, nic->packet, rx_fifo / 2); if(rx_fifo & 1) nic->packet[rx_fifo-1]=inb(BASE + VX_W1_RX_PIO_RD_1); nic->packetlen=rx_fifo; while(1) { status = inw(BASE + VX_W1_RX_STATUS); #ifdef EDEBUG printf("*%hX*",status); #endif rx_fifo = status & RX_BYTES_MASK; if(rx_fifo>0) { insw(BASE + VX_W1_RX_PIO_RD_1, nic->packet+nic->packetlen, rx_fifo / 2); if(rx_fifo & 1) nic->packet[nic->packetlen+rx_fifo-1]=inb(BASE + VX_W1_RX_PIO_RD_1); nic->packetlen+=rx_fifo; #ifdef EDEBUG printf("+%d",rx_fifo); #endif } if(( status & RX_INCOMPLETE )==0) { #ifdef EDEBUG printf("=%d",nic->packetlen); #endif break; } udelay(1000); } /* acknowledge reception of packet */ outw(RX_DISCARD_TOP_PACK, BASE + VX_COMMAND); while (inw(BASE + VX_STATUS) & S_COMMAND_IN_PROGRESS); #ifdef EDEBUG type = (nic->packet[12]<<8) | nic->packet[13]; if(nic->packet[0]+nic->packet[1]+nic->packet[2]+nic->packet[3]+nic->packet[4]+ nic->packet[5] == 0xFF*ETH_ALEN) printf(",t=%hX,b]",type); else printf(",t=%hX]",type); #endif return 1; } /************************************************************************* 3Com 595 - specific routines **************************************************************************/ static int eeprom_rdy() { int i; for (i = 0; is_eeprom_busy(BASE) && i < MAX_EEPROMBUSY; i++) udelay(1000); if (i >= MAX_EEPROMBUSY) { /* printf("3c595: eeprom failed to come ready.\n"); */ printf("3c595: eeprom is busy.\n"); /* memory in EPROM is tight */ return (0); } return (1); } /* * get_e: gets a 16 bits word from the EEPROM. we must have set the window * before */ static int get_e(offset) int offset; { if (!eeprom_rdy()) return (0xffff); outw(EEPROM_CMD_RD | offset, BASE + VX_W0_EEPROM_COMMAND); if (!eeprom_rdy()) return (0xffff); return (inw(BASE + VX_W0_EEPROM_DATA)); } static void vxgetlink(void) { int n, k; GO_WINDOW(3); vx_connectors = inw(BASE + VX_W3_RESET_OPT) & 0x7f; for (n = 0, k = 0; k < VX_CONNECTORS; k++) { if (vx_connectors & conn_tab[k].bit) { if (n > 0) { printf("/"); } printf(conn_tab[k].name); n++; } } if (vx_connectors == 0) { printf("no connectors!"); return; } GO_WINDOW(3); vx_connector = (inl(BASE + VX_W3_INTERNAL_CFG) & INTERNAL_CONNECTOR_MASK) >> INTERNAL_CONNECTOR_BITS; if (vx_connector & 0x10) { vx_connector &= 0x0f; printf("[*%s*]", conn_tab[vx_connector].name); printf(": disable 'auto select' with DOS util!"); } else { printf("[*%s*]", conn_tab[vx_connector].name); } } static void vxsetlink(void) { int i, j, k; char *reason, *warning; static short prev_flags; static char prev_conn = -1; if (prev_conn == -1) { prev_conn = vx_connector; } i = vx_connector; /* default in EEPROM */ reason = "default"; warning = 0; if ((vx_connectors & conn_tab[vx_connector].bit) == 0) { warning = "strange connector type in EEPROM."; reason = "forced"; i = CONNECTOR_UTP; } if (warning != 0) { printf("warning: %s\n", warning); } printf("selected %s. (%s)\n", conn_tab[i].name, reason); /* Set the selected connector. */ GO_WINDOW(3); j = inl(BASE + VX_W3_INTERNAL_CFG) & ~INTERNAL_CONNECTOR_MASK; outl(j | (i <<INTERNAL_CONNECTOR_BITS), BASE + VX_W3_INTERNAL_CFG); /* First, disable all. */ outw(STOP_TRANSCEIVER, BASE + VX_COMMAND); udelay(8000); GO_WINDOW(4); outw(0, BASE + VX_W4_MEDIA_TYPE); /* Second, enable the selected one. */ switch(i) { case CONNECTOR_UTP: GO_WINDOW(4); outw(ENABLE_UTP, BASE + VX_W4_MEDIA_TYPE); break; case CONNECTOR_BNC: outw(START_TRANSCEIVER,BASE + VX_COMMAND); udelay(8000); break; case CONNECTOR_TX: case CONNECTOR_FX: GO_WINDOW(4); outw(LINKBEAT_ENABLE, BASE + VX_W4_MEDIA_TYPE); break; default: /* AUI and MII fall here */ break; } GO_WINDOW(1); } static void t595_disable(struct nic *nic) { outw(STOP_TRANSCEIVER, BASE + VX_COMMAND); udelay(8000); GO_WINDOW(4); outw(0, BASE + VX_W4_MEDIA_TYPE); GO_WINDOW(1); } /************************************************************************** ETH_PROBE - Look for an adapter ***************************************************************************/ struct nic *t595_probe(struct nic *nic, unsigned short *probeaddrs, struct pci_device *pci) { int i; unsigned short *p; if (probeaddrs == 0 || probeaddrs[0] == 0) return 0; /* eth_nic_base = probeaddrs[0] & ~3; */ eth_nic_base = pci->ioaddr; GO_WINDOW(0); outw(GLOBAL_RESET, BASE + VX_COMMAND); VX_BUSY_WAIT; vxgetlink(); /* printf("\nEEPROM:"); for (i = 0; i < (EEPROMSIZE/2); i++) { printf("%hX:", get_e(i)); } printf("\n"); */ /* * Read the station address from the eeprom */ p = (unsigned short *) nic->node_addr; for (i = 0; i < 3; i++) { GO_WINDOW(0); p[i] = htons(get_e(EEPROM_OEM_ADDR_0 + i)); GO_WINDOW(2); outw(ntohs(p[i]), BASE + VX_W2_ADDR_0 + (i * 2)); } printf("Ethernet address: %!\n", nic->node_addr); t595_reset(nic); nic->reset = t595_reset; nic->poll = t595_poll; nic->transmit = t595_transmit; nic->disable = t595_disable; return nic; } /* * Local variables: * c-basic-offset: 8 * End: */