A PCMCIA ethernet driver for SMC91c92-based cards.
This driver supports Megahertz PCMCIA ethernet cards; and Megahertz, Motorola, Ositech, and Psion Dacom ethernet/modem multifunction cards.
Copyright (C) 1999 David A. Hinds -- dahinds@users.sourceforge.net
smc91c92_cs.c 1.122 2002/10/25 06:26:39
This driver contains code written by Donald Becker (becker@scyld.com), Rowan Hughes (x-csrdh@jcu.edu.au), David Hinds (dahinds@users.sourceforge.net), and Erik Stahlman (erik@vt.edu). Donald wrote the SMC 91c92 code using parts of Erik's SMC 91c94 driver. Rowan wrote a similar driver, and I've incorporated some parts of his driver here. I (Dave) wrote most of the PCMCIA glue code, and the Ositech support code. Kelly Stephens (kstephen@holli.com) added support for the Motorola Mariner, with help from Allen Brost.
This software may be used and distributed according to the terms of the GNU General Public License, incorporated herein by reference.
/* the normal settings for the RCR register : */ #define RCR_NORMAL (RCR_STRIP_CRC | RCR_ENABLE) #define RCR_CLEAR 0x0 /* set it to a base state */ #define COUNTER 6
/* BANK 3 -- not the same values as in smc9194! */ #define MULTICAST0 0 #define MULTICAST2 2 #define MULTICAST4 4 #define MULTICAST6 6 #define MGMT 8 #define REVISION 0x0a
mhz_3288_power() is used to power up a 3288's ethernet chip. mhz_mfc_config() handles socket setup for multifunction (1144 and 3288) cards. mhz_setup() gets a card's hardware ethernet address.
/* The Megahertz combo cards have modem-like CIS entries, so
we have to explicitly try a bunch of port combinations. */ if (pcmcia_loop_config(link, mhz_mfc_config_check, NULL)) return -ENODEV;
dev->base_addr = link->resource[0]->start;
/* Allocate a memory window, for accessing the ISR */
link->resource[2]->flags = WIN_DATA_WIDTH_8|WIN_MEMORY_TYPE_AM|WIN_ENABLE;
link->resource[2]->start = link->resource[2]->end = 0;
i = pcmcia_request_window(link, link->resource[2], 0); if (i != 0) return -ENODEV;
/* Read the station address from the CIS. It is stored as the last
(fourth) string in the Version 1 Version/ID tuple. */ if ((link->prod_id[3]) &&
(cvt_ascii_address(dev, link->prod_id[3]) == 0)) return 0;
/* Workarounds for broken cards start here. */ /* Ugh -- the EM1144 card has two VERS_1 tuples!?! */ if (!pcmcia_loop_tuple(link, CISTPL_VERS_1, pcmcia_get_versmac, dev)) return 0;
/* Another possibility: for the EM3288, in a special tuple */
rc = -1;
len = pcmcia_get_tuple(link, 0x81, &buf); if (buf && len >= 13) {
buf[12] = '\0'; if (cvt_ascii_address(dev, buf) == 0)
rc = 0;
}
kfree(buf);
/* Check for a LAN function extension tuple */ if (!pcmcia_get_mac_from_cis(link, dev)) return 0;
/* Try the third string in the Version 1 Version/ID tuple. */ if (link->prod_id[2]) { if (cvt_ascii_address(dev, link->prod_id[2]) == 0) return 0;
} return -1;
}
SMC_SELECT_BANK(1); if (inw(ioaddr + BANK_SELECT) >> 8 != 0x33) { /* Try powering up the chip */
outw(0, ioaddr + CONTROL);
mdelay(55);
}
/* Try setting bus width */
width = (link->resource[0]->flags == IO_DATA_PATH_WIDTH_AUTO);
s = inb(ioaddr + CONFIG); if (width)
s |= CFG_16BIT; else
s &= ~CFG_16BIT;
outb(s, ioaddr + CONFIG);
/* Check Base Address Register to make sure bus width is OK */
s = inw(ioaddr + BASE_ADDR); if ((inw(ioaddr + BANK_SELECT) >> 8 == 0x33) &&
((s >> 8) != (s & 0xff))) {
SMC_SELECT_BANK(3);
s = inw(ioaddr + REVISION); return s & 0xff;
}
if (width) {
netdev_info(dev, "using 8-bit IO window\n");
switch (smc->manfid) { case MANFID_OSITECH: case MANFID_PSION:
i = osi_setup(link, smc->manfid, smc->cardid); break; case MANFID_SMC: case MANFID_NEW_MEDIA:
i = smc_setup(link); break; case 0x128: /* For broken Megahertz cards */ case MANFID_MEGAHERTZ:
i = mhz_setup(link); break; case MANFID_MOTOROLA: default: /* get the hw address from EEPROM */
i = mot_setup(link); break;
}
if (i != 0) {
dev_notice(&link->dev, "Unable to find hardware address.\n"); goto config_failed;
}
smc->duplex = 0;
smc->rx_ovrn = 0;
rev = check_sig(link);
name = "???"; if (rev > 0) switch (rev >> 4) { case 3: name = "92"; break; case 4: name = ((rev & 15) >= 6) ? "96" : "94"; break; case 5: name = "95"; break; case 7: name = "100"; break; case 8: name = "100-FD"; break; case 9: name = "110"; break;
}
if (smc->cfg & CFG_MII_SELECT) { if (smc->mii_if.phy_id != -1) {
netdev_dbg(dev, " MII transceiver at index %d, status %x\n",
smc->mii_if.phy_id, j);
} else {
netdev_notice(dev, " No MII transceivers found!\n");
}
} return 0;
staticint mdio_read(struct net_device *dev, int phy_id, int loc)
{ unsignedint addr = dev->base_addr + MGMT;
u_int cmd = (0x06<<10)|(phy_id<<5)|loc; int i, retval = 0;
mdio_sync(addr); for (i = 13; i >= 0; i--) { int dat = (cmd&(1<<i)) ? MDIO_DATA_WRITE1 : MDIO_DATA_WRITE0;
outb(dat, addr);
outb(dat | MDIO_SHIFT_CLK, addr);
} for (i = 19; i > 0; i--) {
outb(0, addr);
retval = (retval << 1) | ((inb(addr) & MDIO_DATA_READ) != 0);
outb(MDIO_SHIFT_CLK, addr);
} return (retval>>1) & 0xffff;
}
staticvoid mdio_write(struct net_device *dev, int phy_id, int loc, int value)
{ unsignedint addr = dev->base_addr + MGMT;
u_int cmd = (0x05<<28)|(phy_id<<23)|(loc<<18)|(1<<17)|value; int i;
mdio_sync(addr); for (i = 31; i >= 0; i--) { int dat = (cmd&(1<<i)) ? MDIO_DATA_WRITE1 : MDIO_DATA_WRITE0;
outb(dat, addr);
outb(dat | MDIO_SHIFT_CLK, addr);
} for (i = 1; i >= 0; i--) {
outb(0, addr);
outb(MDIO_SHIFT_CLK, addr);
}
}
/* Check that the PCMCIA card is still here. */ if (!pcmcia_dev_present(link)) return -ENODEV; /* Physical device present signature. */ if (check_sig(link) < 0) {
netdev_info(dev, "Yikes! Bad chip signature!\n"); return -ENODEV;
}
link->open++;
dev_dbg(&link->dev, "%s: smc_close(), status %4.4x.\n",
dev->name, inw(ioaddr + BANK_SELECT));
netif_stop_queue(dev);
/* Shut off all interrupts, and turn off the Tx and Rx sections.
Don't bother to check for chip present. */
SMC_SELECT_BANK(2); /* Nominally paranoia, but do no assume... */
outw(0, ioaddr + INTERRUPT);
SMC_SELECT_BANK(0);
mask_bits(0xff00, ioaddr + RCR);
mask_bits(0xff00, ioaddr + TCR);
/* Put the chip into power-down mode. */
SMC_SELECT_BANK(1);
outw(CTL_POWERDOWN, ioaddr + CONTROL );
if (!skb) {
netdev_err(dev, "In XMIT with no packet to send\n"); return;
}
/* There should be a packet slot waiting. */
packet_no = inw(ioaddr + PNR_ARR) >> 8; if (packet_no & 0x80) { /* If not, there is a hardware problem! Likely an ejected card. */
netdev_warn(dev, "hardware Tx buffer allocation failed, status %#2.2x\n",
packet_no);
dev_kfree_skb_irq(skb);
smc->saved_skb = NULL;
netif_start_queue(dev); return;
}
dev->stats.tx_bytes += skb->len; /* The card should use the just-allocated buffer. */
outw(packet_no, ioaddr + PNR_ARR); /* point to the beginning of the packet */
outw(PTR_AUTOINC , ioaddr + POINTER);
/* Send the packet length (+6 for status, length and ctl byte)
and the status word (set to zeros). */
{
u_char *buf = skb->data;
u_int length = skb->len; /* The chip will pad to ethernet min. */
netdev_dbg(dev, "Trying to xmit packet of length %d\n", length);
/* send the packet length: +6 for status word, length, and ctl */
outw(0, ioaddr + DATA_1);
outw(length + 6, ioaddr + DATA_1);
outsw(ioaddr + DATA_1, buf, length >> 1);
/* The odd last byte, if there is one, goes in the control word. */
outw((length & 1) ? 0x2000 | buf[length-1] : 0, ioaddr + DATA_1);
}
/* Enable the Tx interrupts, both Tx (TxErr) and TxEmpty. */
outw(((IM_TX_INT|IM_TX_EMPTY_INT)<<8) |
(inw(ioaddr + INTERRUPT) & 0xff00),
ioaddr + INTERRUPT);
/* The chip does the rest of the work. */
outw(MC_ENQUEUE , ioaddr + MMU_CMD);
if (smc->saved_skb) { /* THIS SHOULD NEVER HAPPEN. */
dev->stats.tx_aborted_errors++;
netdev_dbg(dev, "Internal error -- sent packet while busy\n"); return NETDEV_TX_BUSY;
}
smc->saved_skb = skb;
num_pages = skb->len >> 8;
if (num_pages > 7) {
netdev_err(dev, "Far too big packet error: %d pages\n", num_pages);
dev_kfree_skb (skb);
smc->saved_skb = NULL;
dev->stats.tx_dropped++; return NETDEV_TX_OK; /* Do not re-queue this packet. */
} /* A packet is now waiting. */
smc->packets_waiting++;
spin_lock_irqsave(&smc->lock, flags);
SMC_SELECT_BANK(2); /* Paranoia, we should always be in window 2 */
/* need MC_RESET to keep the memory consistent. errata? */ if (smc->rx_ovrn) {
outw(MC_RESET, ioaddr + MMU_CMD);
smc->rx_ovrn = 0;
}
/* Allocate the memory; send the packet now if we win. */
outw(MC_ALLOC | num_pages, ioaddr + MMU_CMD); for (time_out = MEMORY_WAIT_TIME; time_out >= 0; time_out--) {
ir = inw(ioaddr+INTERRUPT); if (ir & IM_ALLOC_INT) { /* Acknowledge the interrupt, send the packet. */
outw((ir&0xff00) | IM_ALLOC_INT, ioaddr + INTERRUPT);
smc_hardware_send_packet(dev); /* Send the packet now.. */
spin_unlock_irqrestore(&smc->lock, flags); return NETDEV_TX_OK;
}
}
/* Otherwise defer until the Tx-space-allocated interrupt. */
netdev_dbg(dev, "memory allocation deferred.\n");
outw((IM_ALLOC_INT << 8) | (ir & 0xff00), ioaddr + INTERRUPT);
spin_unlock_irqrestore(&smc->lock, flags);
static irqreturn_t smc_interrupt(int irq, void *dev_id)
{ struct net_device *dev = dev_id; struct smc_private *smc = netdev_priv(dev); unsignedint ioaddr;
u_short saved_bank, saved_pointer, mask, status; unsignedint handled = 1; char bogus_cnt = INTR_WORK; /* Work we are willing to do. */
if (!netif_device_present(dev)) return IRQ_NONE;
ioaddr = dev->base_addr;
netdev_dbg(dev, "SMC91c92 interrupt %d at %#x.\n",
irq, ioaddr);
spin_lock(&smc->lock);
smc->watchdog = 0;
saved_bank = inw(ioaddr + BANK_SELECT); if ((saved_bank & 0xff00) != 0x3300) { /* The device does not exist -- the card could be off-line, or
maybe it has been ejected. */
netdev_dbg(dev, "SMC91c92 interrupt %d for non-existent/ejected device.\n",
irq);
handled = 0; goto irq_done;
}
do { /* read the status flag, and mask it */
status = inw(ioaddr + INTERRUPT) & 0xff;
netdev_dbg(dev, "Status is %#2.2x (mask %#2.2x).\n",
status, mask); if ((status & mask) == 0) { if (bogus_cnt == INTR_WORK)
handled = 0; break;
} if (status & IM_RCV_INT) { /* Got a packet(s). */
smc_rx(dev);
} if (status & IM_TX_INT) {
smc_tx_err(dev);
outw(IM_TX_INT, ioaddr + INTERRUPT);
}
status &= mask; if (status & IM_TX_EMPTY_INT) {
outw(IM_TX_EMPTY_INT, ioaddr + INTERRUPT);
mask &= ~IM_TX_EMPTY_INT;
dev->stats.tx_packets += smc->packets_waiting;
smc->packets_waiting = 0;
} if (status & IM_ALLOC_INT) { /* Clear this interrupt so it doesn't happen again */
mask &= ~IM_ALLOC_INT;
smc_hardware_send_packet(dev);
/* enable xmit interrupts based on this */
mask |= (IM_TX_EMPTY_INT | IM_TX_INT);
/* and let the card send more packets to me */
netif_wake_queue(dev);
} if (status & IM_RX_OVRN_INT) {
dev->stats.rx_errors++;
dev->stats.rx_fifo_errors++; if (smc->duplex)
smc->rx_ovrn = 1; /* need MC_RESET outside smc_interrupt */
outw(IM_RX_OVRN_INT, ioaddr + INTERRUPT);
} if (status & IM_EPH_INT)
smc_eph_irq(dev);
} while (--bogus_cnt);
staticvoid smc_rx(struct net_device *dev)
{ unsignedint ioaddr = dev->base_addr; int rx_status; int packet_length; /* Caution: not frame length, rather words
to transfer from the chip. */
/* Assertion: we are in Window 2. */
if (inw(ioaddr + FIFO_PORTS) & FP_RXEMPTY) {
netdev_err(dev, "smc_rx() with nothing on Rx FIFO\n"); return;
}
/* Reset the read pointer, and read the status and packet length. */
outw(PTR_READ | PTR_RCV | PTR_AUTOINC, ioaddr + POINTER);
rx_status = inw(ioaddr + DATA_1);
packet_length = inw(ioaddr + DATA_1) & 0x07ff;
netdev_dbg(dev, "Receive status %4.4x length %d.\n",
rx_status, packet_length);
if (!(rx_status & RS_ERRORS)) { /* do stuff to make a new packet */ struct sk_buff *skb; struct smc_private *smc = netdev_priv(dev);
/* Note: packet_length adds 5 or 6 extra bytes here! */
skb = netdev_alloc_skb(dev, packet_length+2);
if (rx_status & RS_ALGNERR) dev->stats.rx_frame_errors++; if (rx_status & (RS_TOOSHORT | RS_TOOLONG))
dev->stats.rx_length_errors++; if (rx_status & RS_BADCRC) dev->stats.rx_crc_errors++;
} /* Let the MMU free the memory of this packet. */
outw(MC_RELEASE, ioaddr + MMU_CMD);
}
This routine is used by both the protocol level to notify us of promiscuous/multicast mode changes, and by the open/reset code to initialize the Rx registers. We always set the multicast list and leave the receiver running.
/* Load MC table and Rx setting into the chip without interrupts. */
spin_lock_irqsave(&smc->lock, flags);
SMC_SELECT_BANK(3); for (i = 0; i < 8; i++)
outb(multicast_table[i], ioaddr + MULTICAST0 + i);
SMC_SELECT_BANK(0);
outw(rx_cfg_setting, ioaddr + RCR);
SMC_SELECT_BANK(2);
spin_unlock_irqrestore(&smc->lock, flags);
}
/* Set transceiver type, perhaps to something other than what the user specified in dev->if_port.
*/ staticvoid smc_set_xcvr(struct net_device *dev, int if_port)
{ struct smc_private *smc = netdev_priv(dev); unsignedint ioaddr = dev->base_addr;
u_short saved_bank;
/* The first interaction must be a write to bring the chip out
of sleep mode. */
SMC_SELECT_BANK(0); /* Reset the chip. */
outw(RCR_SOFTRESET, ioaddr + RCR);
udelay(10);
/* Clear the transmit and receive configuration registers. */
outw(RCR_CLEAR, ioaddr + RCR);
outw(TCR_CLEAR, ioaddr + TCR);
/* Set the Window 1 control, configuration and station addr registers.
No point in writing the I/O base register ;-> */
SMC_SELECT_BANK(1); /* Automatically release successfully transmitted packets,
Accept link errors, counter and Tx error interrupts. */
outw(CTL_AUTO_RELEASE | CTL_TE_ENABLE | CTL_CR_ENABLE,
ioaddr + CONTROL);
smc_set_xcvr(dev, dev->if_port); if ((smc->manfid == MANFID_OSITECH) &&
(smc->cardid != PRODID_OSITECH_SEVEN))
outw((dev->if_port == 2 ? OSI_AUI_PWR : 0) |
(inw(ioaddr-0x10+OSITECH_AUI_PWR) & 0xff00),
ioaddr - 0x10 + OSITECH_AUI_PWR);
/* Fill in the physical address. The databook is wrong about the order! */ for (i = 0; i < 6; i += 2)
outw((dev->dev_addr[i+1]<<8)+dev->dev_addr[i],
ioaddr + ADDR0 + i);
/* Check for pending interrupt with watchdog flag set: with
this, we can limp along even if the interrupt is blocked */ if (smc->watchdog++ && ((i>>8) & i)) { if (!smc->fast_poll)
netdev_info(dev, "interrupt(s) dropped!\n");
local_irq_save(flags);
smc_interrupt(dev->irq, dev);
local_irq_restore(flags);
smc->fast_poll = HZ;
} if (smc->fast_poll) {
smc->fast_poll--;
smc->media.expires = jiffies + HZ/100;
add_timer(&smc->media); return;
}
spin_lock_irqsave(&smc->lock, flags);
saved_bank = inw(ioaddr + BANK_SELECT);
if (smc->cfg & CFG_MII_SELECT) { if (smc->mii_if.phy_id < 0) goto reschedule;
SMC_SELECT_BANK(3);
link = mdio_read(dev, smc->mii_if.phy_id, 1); if (!link || (link == 0xffff)) {
netdev_info(dev, "MII is missing!\n");
smc->mii_if.phy_id = -1; goto reschedule;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.