/*====================================================================== A cs89x0 PCMCIA client driver Written by Danilo Beuche, danilo@first.gmd.de Newest version of this driver and additional information under http://www.first.gmd.de/~danilo This driver is under development und currently supports only basic rx/tx operations with CS8920 based PC-Card. It is based on the skeleton driver found in the pcmcia-cs package and other network drivers in this package. Additional information came from a crynwr packet driver source file supplied with my adapters driver package from IBM and the technical information for the CS8920 Chip by Crystal Seminconductors Corp. ( http://www.crystal.com ) It has been tested with a IBM EtherJet PC Card on 10BaseT and BNC connections. BUGS: - speaker goes off if the card is fully initialized, on succesfull initialization therefor only one beep is heard. Maybe be only a problem of my machine (SHARP PC-8800). - occasionally my machine freezes while the card is plugged in. Didn't traced the problem back yet. TODO: - checks for correct chip type have to be extend - configuration should be done by the driver instead of relying on the eeprom configuration data o hardware address should be taken from CIS tuple - try to use more sophisticated rx/tx modes o memory mode packet page access it looks like there is no access to the chips memory interface available o RxDMA mode - more statistics should be collected - document source file extensivly - remove every bug This software may be used and distributed according to the terms of the GNU Public License. CHANGES: ?.? - extensively modified by Andrew Tridgell (tridge@samba.anu.edu.au) to handle bugs in the RevB silicon. Also converted to 2.1 kernel and newer card services structures. Don't need THINKPADHACK any more 0.4 - restructured file organization 0.3 - fixed connection type problem ( now CONN_HDX, CONN_FDX should work ) (danilo) - workaround for IBM Thinkpad 760 Problems ( enable with #define THINKPADHACK 1 ) (danilo/emilio) - BNC/AUI works now (emilio) 0.2a - some cleanup 0.1 - first release ======================================================================*/ #include #include #define PCMCIA_DEBUG 1 #ifdef MODULE #define init_cs89x0 init_module #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cs89x0_cs.h" /* All the PCMCIA modules use PCMCIA_DEBUG to control debugging. If you do not define PCMCIA_DEBUG at all, all the debug code will be left out. If you compile with PCMCIA_DEBUG=0, the debug code will be present but disabled -- but it can then be enabled for specific modules at load time with a 'pc_debug=#' option to insmod. */ #ifdef PCMCIA_DEBUG static int pc_debug = PCMCIA_DEBUG; MODULE_PARM(pc_debug, "i"); static char *version = "cs89x0.c 0.4 1997/11/12 17:03:28 (Danilo Beuche)"; static int cs89x0_loop = 0; // if 1 enable internal digital loopback mode #else #define DEBUG(n, args...) #endif /*====================================================================*/ /* Parameters that can be set with 'insmod' */ /* Bit map of interrupts to choose from */ /* This means pick from 15, 14, 12, 11, 10, 9, 7, 5, 4, and 3 */ static u_long irq_mask = 0xdeb8; static char *if_names[] = { "Auto", "10baseT", "10base2"}; static int if_port = 0; // default ist to autodetect connection /* Time in jiffies before concluding the transmitter is hung. */ #define TX_TIMEOUT ((200*HZ)/1000) // CONN_AUTO(0) = autonegotiate connection type // CONN_HDX(1) = halfduplex connection forced // CONN_FDX(2) = fullduplex connection forced static int conn_type = CONN_AUTO; MODULE_PARM(irq_mask, "i"); MODULE_PARM(conn_type, "i"); MODULE_PARM(if_port, "i"); /*====================================================================*/ /* The event() function is this driver's Card Services event handler. It will be called by Card Services when an appropriate card status event is received. The config() and release() entry points are used to configure or release a socket, in response to card insertion and ejection events. They are invoked from the cs89x0 event handler. */ static void cs89x0_config(dev_link_t *link); static void cs89x0_release(u_long arg); static int cs89x0_event(event_t event, int priority, event_callback_args_t *args); /* The attach() and detach() entry points are used to create and destroy "instances" of the driver, where each instance represents everything needed to manage one actual PCMCIA card. */ static dev_link_t *cs89x0_attach(void); static void cs89x0_detach(dev_link_t *); /* You'll also need to prototype all the functions that will actually be used to talk to your device. See 'pcmem_cs' for a good example of a fully self-sufficient driver; the other drivers rely more or less on other parts of the kernel. */ static void cs89x0_interrupt IRQ(int irq, void *dev_id, struct pt_regs *regs); /* The dev_info variable is the "key" that is used to match up this device driver with appropriate cards, through the card configuration database. */ static dev_info_t dev_info = "cs89x0_cs"; /* A linked list of "instances" of the cs89x0 device. Each actual PCMCIA card corresponds to one device instance, and is described by one dev_link_t structure (defined in ds.h). You may not want to use a linked list for this -- for example, the memory card driver uses an array of dev_link_t pointers, where minor device numbers are used to derive the corresponding array index. */ static dev_link_t *dev_list = NULL; /* A dev_link_t structure has fields for most things that are needed to keep track of a socket, but there will usually be some device specific information that also needs to be kept track of. The 'priv' pointer in a dev_link_t structure can be used to point to a device-specific private data structure, like this. A driver needs to provide a dev_node_t structure for each device on a card. In some cases, there is only one device per card (for example, ethernet cards, modems). In other cases, there may be many actual or logical devices (SCSI adapters, memory cards with multiple partitions). The dev_node_t structures need to be kept in a linked list starting at the 'dev' field of a dev_link_t structure. We allocate them in the card's private data structure, because they generally can't be allocated dynamically. */ #define SET_PORT(a,base) outw((a),ADD_PORT + (base)) #define SET_DATA(d,base) outw((d),DATA_PORT + (base)) // we don't know where the problem is but at least there is a solution #define GET_DATA8(base) ((inw(DATA_PORT + (base))& 0xff) | ((inw(DATA_PORT + (base)+ 1) & 0xff) <<8)) #define GET_DATA(base) inw(DATA_PORT + (base)) static char *cs89x0_text[] = { "CS8900", "unknown", "CS8920", "CS8920M" }; #define STATUS_IDLE 0 #define STATUS_TRANSMITTING 1 #define STATUS_TRANSMIT_WAIT 2 typedef struct local_info_t { struct device dev; // device infos dev_node_t node; int status; struct timer_list tx_timer; int chip_id; // 0 = CS8900, 1 = CS8920 int chip_rev; // 0 = A, 1 = B, ... struct enet_statistics stats; // statistics e.g. used by ifconfig } local_info_t; static void set_dc_dc(struct device *dev, int on_not_off) { int ioaddr = dev->base_addr; unsigned int selfcontrol = 0 ; int timenow = jiffies; if (on_not_off == 1) selfcontrol = HCB1_ENBL|HCB1 ; SET_PORT(PP_SelfCTL,ioaddr); SET_DATA((GET_DATA(ioaddr) & 0x0fff)|selfcontrol,ioaddr); while (jiffies-timenow <100) ; } static int cs89x0_chipid(int ioaddr) { int i, x, x2; // wait 50ms udelay(50000L); /* this is really weird. On ThinkPads you _must_ access the word at ioaddr+1 or you get a trap and death. Why??? */ for(i = 500; i ; i--) { SET_PORT(PP_ChipID,ioaddr); x = GET_DATA(ioaddr); x2 = GET_DATA(ioaddr+1); if (x == 0x630e) break; udelay(1000); } return x; } static void cs89x0_reset(struct device *dev) { int ioaddr = dev->base_addr; int x; #ifdef PCMCIA_DEBUG if (pc_debug>1) printk(KERN_DEBUG "cs89x0_reset('%s')\n", dev->name); #endif x = cs89x0_chipid(ioaddr); if (x!= 0x630e) printk(KERN_NOTICE "cs89x0_cs: doesn't looks like a cs89x0 based card at 0x%03x\n",ioaddr); // turn off interrupts SET_PORT(PP_BusCTL,ioaddr); SET_DATA(0,ioaddr); switch(dev->if_port) { case 1: // 10BaseT set_dc_dc(dev,0); SET_PORT(PP_LineCTL,ioaddr); SET_DATA(GET_DATA(ioaddr) & ~(AUI_ON + TENBASET_ON),ioaddr); break; case 2: // AUI / 10Base2 set_dc_dc(dev,1); SET_PORT(PP_LineCTL,ioaddr); SET_DATA((GET_DATA(ioaddr) & ~AUTO_AUI_10BASET)| AUI_ONLY,ioaddr); break; case 0: // autodetect default: set_dc_dc(dev,1); SET_PORT(PP_LineCTL,ioaddr); SET_DATA((GET_DATA(ioaddr)& ~AUI_ON)|TENBASET_ON,ioaddr); break; } // enable everything SET_PORT(PP_LineCTL,ioaddr); SET_DATA(GET_DATA(ioaddr)|(SERIAL_RX_ON + SERIAL_TX_ON),ioaddr); // turn on xmitter and receiver SET_PORT(PP_RxCFG,ioaddr); SET_DATA(RX_OK_ENBL + RX_CRC_ERROR_ENBL,ioaddr); // interrupt on rx of correct packets and packets with crc errors SET_PORT(PP_BufCFG,ioaddr); SET_DATA(READY_FOR_TX_ENBL | RX_MISS_ENBL,ioaddr); SET_PORT(PP_TxCFG,ioaddr); SET_DATA(TX_OK_ENBL|TX_16_COL_ENBL|TX_LATE_COL_ENBL|TX_JBR_ENBL,ioaddr); // interrupt on successful tx of packet only outw(TX_FORCE,TX_CMD_PORT + ioaddr); outw(0,TX_LEN_PORT + ioaddr); // if any tx is underway stop it now #ifdef PCMCIA_DEBUG if (cs89x0_loop) { // digital loopback setup // we need full duplex for this, must turn off link pulse detection // and enable internal digital loopback conn_type = CONN_FDX; SET_PORT(PP_TestCTL,ioaddr); SET_DATA(GET_DATA(ioaddr)|(LINK_OFF + ENDEC_LOOPBACK),ioaddr); } #endif PCMCIA_DEBUG SET_PORT(PP_RxCTL,ioaddr); SET_DATA(DEF_RX_ACCEPT|RX_BROADCAST_ACCEPT,ioaddr); // receive only broadcast frames and frames for own address switch (conn_type) { case CONN_AUTO: { int i,x; SET_PORT(PP_AutoNegCTL,ioaddr); SET_DATA(ALLOW_FDX|AUTO_NEG_ENABLE|RE_NEG_NOW,ioaddr); // wait for auto neg to complete SET_PORT(PP_AutoNegST,ioaddr); x = 1000; while((i = GET_DATA(ioaddr)) & AUTO_NEG_BUSY && x) { if ((i & 0x03f) != 0x01e) break; udelay(10000); x--; } #ifdef PCMCIA_DEBUG if (pc_debug>1) printk(KERN_DEBUG "cs89x0_cs: looped %d times for autoneg ready\n",1000-x); #endif PCMCIA_DEBUG SET_PORT(PP_AutoNegST,ioaddr); if (GET_DATA(ioaddr)& AUTO_NEG_BUSY) { printk("%s: autonegotiation did not complete successfully\n", dev->name); printk("%s: maybe cable problem or transceiver mismatch\n", dev->name); } } break; case CONN_FDX: SET_PORT(PP_AutoNegCTL,ioaddr); SET_DATA(FORCE_FDX,ioaddr); #ifdef PCMCIA_DEBUG if (pc_debug>1) printk(KERN_DEBUG "forced full duplex\n"); #endif PCMCIA_DEBUG break; case CONN_HDX: default: if (conn_type != CONN_HDX) printk("%s: invalid conn_type %d, using halfduplex\n",dev->name,conn_type); SET_PORT(PP_AutoNegCTL,ioaddr); SET_DATA(NLP_ENABLE,ioaddr); #ifdef PCMCIA_DEBUG if (pc_debug>1) printk(KERN_DEBUG "forced half duplex\n"); #endif PCMCIA_DEBUG } #ifdef PCMCIA_DEBUG if (pc_debug>1) { printk(KERN_DEBUG "cs89x0_reset('%s') done\n", dev->name); SET_PORT(PP_AutoNegST,ioaddr); printk(KERN_DEBUG "cs89x0:AutoNegST = 0x%04x\n", GET_DATA(ioaddr)); } #endif SET_PORT(PP_BusCTL,ioaddr); SET_DATA(ENABLE_IRQ,ioaddr); // let us start work now } static struct enet_statistics *cs89x0_get_stats(struct device *dev) { static struct enet_statistics stat; struct local_info_t *lp = (struct local_info_t*) dev->priv; int ioaddr = dev->base_addr; /* If the card is stopped, just return the present stats. */ if (dev->start == 0) return &stat; /* Read the counter registers, assuming we are in page 0. */ // lp->stats.rx_frame_errors += 0; SET_PORT(PP_TxCol,ioaddr); lp->stats.collisions+= GET_DATA(ioaddr) >> 5; SET_PORT(PP_RxMiss,ioaddr); lp->stats.rx_missed_errors+= GET_DATA(ioaddr) >> 5; #ifdef PCMCIA_DEBUG if (pc_debug > 1) { printk(KERN_DEBUG "%s: Missed Packets = %ld\n", dev->name,(long)lp->stats.rx_missed_errors); printk(KERN_DEBUG "%s: Tx Col Packets = %ld\n", dev->name,(long)lp->stats.collisions); } #endif return &lp->stats; } static void cs89x0_RxEvent(local_info_t *lp,int isq); /* when the transmitter times out we try a force transmit to clear the blockage. Hopefully this won't happen too often */ static void cs89x0_tx_timeout(struct device *dev) { local_info_t* lp = (local_info_t*)dev->priv; int ioaddr = dev->base_addr; printk(KERN_NOTICE "%s: transmit timed out\n", dev->name); dev->trans_start = jiffies; dev->tbusy = 0; lp->status = STATUS_IDLE; del_timer(&lp->tx_timer); /* Issue TX_FORCE command. */ outw(TX_FORCE,TX_CMD_PORT + ioaddr); outw(0,TX_LEN_PORT + ioaddr); } /* after struggling with several hardware bugs in a RevB card I finally succumbed and put a timer in. The card sometimes refuses to tell us when a transmission is finished so we need this to reset the status after a short period (tridge) */ static void cs89x0_xmit_timer(u_long data) { struct device *dev = (struct device *)data; local_info_t* lp = (local_info_t*)dev->priv; if (lp->status == STATUS_TRANSMIT_WAIT) { lp->status = STATUS_IDLE; dev->tbusy = 0; mark_bh(NET_BH); } } /* do the actual data transfer for a transmit */ static void cs89x0_finish_xmit(struct device *dev, char *buf, int len) { unsigned long flags; int ioaddr = dev->base_addr; local_info_t* lp = (local_info_t*)dev->priv; outsl(ioaddr + TX_FRAME_PORT, buf, (len + 3) >> 2); save_flags(flags); cli(); if (lp->status == STATUS_TRANSMITTING) { lp->status = STATUS_TRANSMIT_WAIT; /* see the comments in xs89x0_xmit_timer */ if (lp->tx_timer.prev) { del_timer(&lp->tx_timer); } if (lp->tx_timer.prev == (struct timer_list *) NULL) { lp->tx_timer.function = &cs89x0_xmit_timer; lp->tx_timer.data = (u_long)dev; lp->tx_timer.expires = jiffies + 2; add_timer(&lp->tx_timer); } } else { lp->status = STATUS_IDLE; dev->tbusy = 0; } restore_flags(flags); lp->stats.tx_packets++; #ifdef PCMCIA_DEBUG if (pc_debug > 2) { int i; printk(KERN_DEBUG "%s: Tx pkt of length %d: \n", dev->name, len); if (pc_debug > 3) { for (i = 0; i < 32; i++) printk(KERN_DEBUG " %02x", buf[i]); printk(".\n"); } } #endif } static int cs89x0_start_xmit(struct sk_buff *skb, struct device *dev) { int ioaddr = dev->base_addr; local_info_t* lp = (local_info_t*)dev->priv; unsigned long flags; #ifdef PCMCIA_DEBUG if (pc_debug > 2) printk(KERN_DEBUG "%s: cs89x0_start_xmit\n", dev->name); #endif /* Transmitter timeout */ if ((test_and_set_bit(0, (void*)&dev->tbusy) != 0) || (lp->status != STATUS_IDLE)) { printk(KERN_NOTICE "%s: transmitter busy - status=%d tbusy=%ld\n", dev->name, lp->status, dev->tbusy); if (jiffies - dev->trans_start >= TX_TIMEOUT) { cs89x0_tx_timeout(dev); lp->stats.tx_errors++; } return 1; } #if (LINUX_VERSION_CODE < VERSION(2,1,25)) if (skb == NULL) { dev_tint(dev); return 0; } #endif if (skb->len <= 0 || skb->len >= 1519) { printk("%s: bad frame length %d\n", dev->name, skb->len); return 0; } dev->trans_start = jiffies; save_flags(flags); cli(); lp->status = STATUS_TRANSMITTING; if (lp->chip_rev <= 1) { // use AFTER_381 for RevB as recommended by spec (tridge) outw(TX_AFTER_381,TX_CMD_PORT + ioaddr); } else { outw(TX_NOW,TX_CMD_PORT + ioaddr); } outw(skb->len,TX_LEN_PORT + ioaddr); /* make sure we are ready for a transmit */ SET_PORT(PP_BusST,ioaddr); if (!(GET_DATA(ioaddr) & READY_FOR_TX_NOW)) { /* the card wasn't ready - that probably means something else is wrong. like maybe we didn't wait long enough between packets */ lp->status = STATUS_TRANSMIT_WAIT; restore_flags(flags); printk(KERN_NOTICE "%s: transmitter not ready\n", dev->name); return 1; } restore_flags(flags); cs89x0_finish_xmit(dev, skb->data, skb->len); DEV_KFREE_SKB(skb); return 0; } static void cs89x0_RxEvent(local_info_t *lp,int isq) { int ioaddr = lp->dev.base_addr; if (isq & RX_OK) { ushort pkt_len; struct sk_buff *skb; pkt_len = inw(ioaddr + RX_FRAME_PORT); // dummy read of rx status pkt_len = inw(ioaddr + RX_FRAME_PORT); // now get length of packet skb = ALLOC_SKB(pkt_len + 3); if (skb == NULL) { printk(KERN_NOTICE "%s: Memory squeeze, dropping " "packet (len %d).\n", lp->dev.name, pkt_len); SET_PORT(RX_BUF_CFG,ioaddr); SET_DATA(GET_DATA(ioaddr)|SKIP_1,ioaddr); lp->stats.rx_dropped++; return; } skb->dev = &lp->dev; #define BLOCK_INPUT(buf, len) \ insl((ioaddr + RX_FRAME_PORT), buf, (len + 3) >> 2) GET_PACKET((&lp->dev), skb, pkt_len); #ifdef PCMCIA_DEBUG if (pc_debug > 2) { int i; printk(KERN_DEBUG "%s: Rx pkt of length %d: \n", lp->dev.name, pkt_len); if (pc_debug > 3) { for (i = 0; i < 32; i++) printk(KERN_DEBUG " %02x",((skb->data)-14)[i]); printk(".\n"); } } #endif lp->stats.rx_packets++; netif_rx(skb); return; } if (isq & RX_CRC_ERROR) { lp->stats.rx_crc_errors++; lp->stats.rx_errors++; lp->stats.rx_packets++; SET_PORT(RX_BUF_CFG,ioaddr); SET_DATA(GET_DATA(ioaddr)|SKIP_1,ioaddr); return; } } static void cs89x0_set_multicast_list(struct device *dev) { short ioaddr = dev->base_addr; switch(dev->mc_count) { case -1: // promiscous mode , thats easy SET_PORT(PP_RxCTL,ioaddr); SET_DATA(GET_DATA(ioaddr)|RX_ALL_ACCEPT,ioaddr); break; case 0: // just broadcast and ia frames SET_PORT(PP_RxCTL,ioaddr); SET_DATA(DEF_RX_ACCEPT|RX_BROADCAST_ACCEPT,ioaddr); break; default: printk(KERN_INFO "%s: Multicast on cs89x0 for %d addrs NYI\n", dev->name, dev->mc_count); } } /*====================================================================*/ static void cs_error(int func, int ret) { CardServices(ReportError, dev_info, (void *)func, (void *)ret); } /*====================================================================== We never need to do anything when a pcnet device is "initialized" by the net software, because we only register already-found cards. ======================================================================*/ static int cs89x0_init(struct device *dev) { return 0; } /*====================================================================*/ static int cs89x0_open(struct device *dev) { dev_link_t *link; #ifdef PCMCIA_DEBUG if (pc_debug>1) printk(KERN_DEBUG "cs89x0_open('%s')\n", dev->name); #endif for (link = dev_list; link; link = link->next) if (link->priv == dev) break; if (!DEV_OK(link)) return -ENODEV; link->open++; MOD_INC_USE_COUNT; #if CS_RELEASE_CODE <= 0x2911 irq2dev_map[dev->irq] = dev; #endif dev->interrupt = 0; dev->tbusy = 0; dev->start = 1; cs89x0_reset(dev); #ifdef PCMCIA_DEBUG if (pc_debug>1) printk(KERN_DEBUG "cs89x0_open('%s') done\n", dev->name); #endif return 0; } /* cs89x0_open */ /*====================================================================*/ static int cs89x0_close(struct device *dev) { dev_link_t *link; #ifdef PCMCIA_DEBUG if (pc_debug>1) printk(KERN_DEBUG "cs89x0_close('%s')\n", dev->name); #endif for (link = dev_list; link; link = link->next) if (link->priv == dev) break; if (link == NULL) return -ENODEV; link->open--; dev->start = 0; if (link->state & DEV_STALE_CONFIG) { link->release.expires = RUN_AT(HZ/20); link->state |= DEV_RELEASE_PENDING; add_timer(&link->release); } MOD_DEC_USE_COUNT; return 0; } /* cs89x0_close */ /* ======================================================================= */ static int set_config(struct device *dev, struct ifmap *map) { if ((map->port != (u_char)(-1)) && (map->port != dev->if_port)) { if (map->port == 1) set_dc_dc(dev,0); else set_dc_dc(dev,1); if ((map->port == 1) || (map->port == 2)) { dev->if_port = map->port; printk(KERN_INFO "%s: switched to %s port\n", dev->name, if_names[dev->if_port]); } else return -EINVAL; } return 0; } /*====================================================================== cs89x0_attach() creates an "instance" of the driver, allocating local data structures for one device. The device is registered with Card Services. The dev_link structure is initialized, but we don't actually configure the card at this point -- we wait until we receive a card insertion event. ======================================================================*/ static dev_link_t *cs89x0_attach(void) { client_reg_t client_reg; dev_link_t *link; local_info_t *local; int ret; #ifdef PCMCIA_DEBUG if (pc_debug > 1) printk(KERN_DEBUG "cs89x0_attach()\n"); #endif /* Initialize the dev_link_t structure */ link = kmalloc(sizeof(struct dev_link_t), GFP_KERNEL); memset(link, 0, sizeof(struct dev_link_t)); link->release.function = &cs89x0_release; link->release.data = (u_long)link; /* The io structure describes IO port mapping */ link->io.NumPorts1 = 16; link->io.Attributes1 = IO_DATA_PATH_WIDTH_16; link->io.NumPorts2 = 0; link->io.Attributes2 = IO_DATA_PATH_WIDTH_16; link->io.IOAddrLines = 4; /* Interrupt setup */ link->irq.Attributes = IRQ_TYPE_EXCLUSIVE| IRQ_HANDLE_PRESENT; link->irq.IRQInfo1 = IRQ_INFO2_VALID|IRQ_LEVEL_ID; link->irq.IRQInfo2 = irq_mask; link->irq.Handler = &cs89x0_interrupt; /* General socket configuration */ link->conf.Attributes = CONF_ENABLE_IRQ; link->conf.Vcc = 50; link->conf.IntType = INT_MEMORY_AND_IO; link->conf.ConfigIndex = 1; link->conf.Present = PRESENT_OPTION; /* Allocate space for private device-specific data */ local = kmalloc(sizeof(local_info_t), GFP_KERNEL); memset(local, 0, sizeof(local_info_t)); link->priv = local; #if CS_RELEASE_CODE > 0x2911 link->irq.Instance = &local->dev; #endif // ethdev_init(local->dev); // NYI local->dev.name = local->node.dev_name; local->dev.init = &cs89x0_init; local->dev.open = &cs89x0_open; local->dev.stop = &cs89x0_close; local->dev.hard_start_xmit = &cs89x0_start_xmit; local->dev.get_stats = &cs89x0_get_stats; local->dev.set_config = &set_config; local->dev.set_multicast_list = &cs89x0_set_multicast_list; ether_setup(&(local->dev)); local->dev.tbusy = 1; /* Register with Card Services */ link->next = dev_list; dev_list = link; client_reg.dev_info = &dev_info; client_reg.Attributes = INFO_IO_CLIENT | INFO_CARD_SHARE; client_reg.EventMask = CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL | CS_EVENT_RESET_PHYSICAL | CS_EVENT_CARD_RESET | CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME; client_reg.event_handler = &cs89x0_event; client_reg.Version = 0x0210; client_reg.event_callback_args.client_data = link; ret = CardServices(RegisterClient, &link->handle, &client_reg); if (ret != CS_SUCCESS) { cs_error(RegisterClient, ret); cs89x0_detach(link); return NULL; } return link; } /* cs89x0_attach */ /*====================================================================== This deletes a driver "instance". The device is de-registered with Card Services. If it has been released, all local data structures are freed. Otherwise, the structures will be freed when the device is released. ======================================================================*/ static void cs89x0_detach(dev_link_t *link) { dev_link_t **linkp; #ifdef PCMCIA_DEBUG if (pc_debug>1) printk(KERN_DEBUG "cs89x0_detach(0x%p)\n", link); #endif /* Locate device structure */ for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next) if (*linkp == link) break; if (*linkp == NULL) return; /* If the device is currently configured and active, we won't actually delete it yet. Instead, it is marked so that when the release() function is called, that will trigger a proper detach(). */ if (link->state & DEV_CONFIG) { #ifdef PCMCIA_DEBUG printk(KERN_DEBUG "cs89x0_cs: detach postponed, '%s' " "still locked\n", link->dev->dev_name); #endif link->state |= DEV_STALE_LINK; return; } /* Break the link with Card Services */ if (link->handle) CardServices(DeregisterClient, link->handle); /* Unlink device structure, free pieces */ *linkp = link->next; if (link->priv) { kfree_s(link->priv, sizeof(local_info_t)); } kfree_s(link, sizeof(struct dev_link_t)); } /* cs89x0_detach */ /*====================================================================== cs89x0_config() is scheduled to run after a CARD_INSERTION event is received, to configure the PCMCIA socket, and to make the ethernet device available to the system. ======================================================================*/ static void cs89x0_config(dev_link_t *link) { client_handle_t handle; tuple_t tuple; cisparse_t parse; local_info_t *lp; int i, j; u_char buf[64]; win_req_t req; modwin_t mod; memreq_t mem; int ioaddr; handle = link->handle; lp = (local_info_t*)link->priv; #ifdef PCMCIA_DEBUG if (pc_debug>1) printk(KERN_DEBUG "cs89x0_config(0x%p)\n", link); #endif /* This reads the card's CONFIG tuple to find its configuration registers. */ do { tuple.DesiredTuple = CISTPL_CONFIG; i = CardServices(GetFirstTuple, handle, &tuple); if (i != CS_SUCCESS) break; tuple.TupleData = buf; tuple.TupleDataMax = 64; tuple.TupleOffset = 0; i = CardServices(GetTupleData, handle, &tuple); if (i != CS_SUCCESS) break; i = CardServices(ParseTuple, handle, &tuple, &parse); if (i != CS_SUCCESS) break; link->conf.ConfigBase = parse.config.base; } while (0); if (i != CS_SUCCESS) { cs_error(ParseTuple, i); link->state &= ~DEV_CONFIG_PENDING; return; } /* Configure card */ link->state |= DEV_CONFIG; do { /* Try allocating IO ports. This tries a few fixed addresses. If you want, you can also read the card's config table to pick addresses -- see the serial driver for an example. */ for (j = 0x290; j < 0x400; j += 0x10) { link->io.BasePort1 = j; link->io.BasePort2 = 0; i = CardServices(RequestIO, link->handle, &link->io); if (i == CS_SUCCESS) break; } if (i != CS_SUCCESS) { cs_error(RequestIO, i); break; } /* Now allocate an interrupt line. Note that this does not actually assign a handler to the interrupt. */ i = CardServices(RequestIRQ, link->handle, &link->irq); if (i != CS_SUCCESS) { cs_error(RequestIRQ, i); break; } // setup device entries lp->dev.base_addr = link->io.BasePort1; lp->dev.irq = link->irq.AssignedIRQ; lp->dev.if_port = if_port; // defaults to auto lp->dev.tbusy = 0; lp->dev.priv = link->priv; #if CS_RELEASE_CODE <= 0x2911 irq2dev_map[lp->dev.irq] = &lp->dev; #endif ioaddr = lp->dev.base_addr; /* This actually configures the PCMCIA socket -- setting up the I/O windows and the interrupt mapping. */ i = CardServices(RequestConfiguration, link->handle, &link->conf); if (i != CS_SUCCESS) { cs_error(RequestConfiguration, i); break; } /* Allocate a 4K memory window. Note that the dev_link_t structure provides space for one window handle -- if your device needs several windows, you'll need to keep track of the handles in your private data structure, link->priv. */ req.Attributes = WIN_DATA_WIDTH_8|WIN_MEMORY_TYPE_AM|WIN_ENABLE; req.Base = 0; req.Size = 0x1000; req.AccessSpeed = 0; link->win = (window_handle_t)link->handle; i = CardServices(RequestWindow, &link->win, &req); if (i != 0) { cs_error(RequestWindow, i); break; } mod.Attributes = WIN_DATA_WIDTH_16|WIN_MEMORY_TYPE_CM|WIN_ENABLE; i = CardServices(ModifyWindow, link->win, &mod); if (i != 0) { cs_error(ModifyWindow, i); break; } mem.CardOffset = 0x0; mem.Page = 0; i = CardServices(MapMemPage, link->win, &mem); if (i != 0) { cs_error(MapMemPage, i); break; } { // load ethernet hardware address from card // this should be read from CIS instead ! int x; x = cs89x0_chipid(ioaddr); if (x != 0x630e) { printk(KERN_NOTICE "cs89x0_cs: doesn't looks like a cs89x0 based card at 0x%03x\n",ioaddr); break; } SET_PORT(PRODUCT_ID_ADD,ioaddr); x = GET_DATA(ioaddr); switch (x & REVISION_MASK) { case CS8900: lp->chip_id = 0; break; case CS8920: lp->chip_id = 1; break; case CS8920M: lp->chip_id = 1; break; default: lp->chip_id = -1; } lp->chip_rev = (x & REVISION_BITS) >> 8; #ifdef PCMCIA_DEBUG if (pc_debug>1) printk(KERN_DEBUG "cs89x0_cs:found a %s Rev. %c\n", cs89x0_text[x >> 13], lp->chip_rev + 'A'); #endif for (i = 0 ; i < 3 ; i++) { SET_PORT( PP_IA + 2*i,ioaddr); ((short*)lp->dev.dev_addr)[i] = GET_DATA(ioaddr); } } i = register_netdev(&(lp->dev)); if (i != 0) { printk(KERN_NOTICE "cs89x0_cs: register_netdev() failed\n"); break; } } while (0); /* At this point, the dev_node_t structure(s) should be initialized and arranged in a linked list at link->dev. */ link->dev = &lp->node; link->state &= ~DEV_CONFIG_PENDING; /* If any step failed, release any partially configured state */ if (i != 0) { cs89x0_release((u_long)link); return; } printk(KERN_INFO "cs89x0 device loaded\n"); return; } /* cs89x0_config */ /*====================================================================== After a card is removed, cs89x0_release() will unregister the net device, and release the PCMCIA configuration. If the device is still open, this will be postponed until it is closed. ======================================================================*/ static void cs89x0_release(u_long arg) { dev_link_t *link = (dev_link_t *)arg; local_info_t *local = link->priv; #ifdef PCMCIA_DEBUG if (pc_debug>1) printk(KERN_DEBUG "cs89x0_release(0x%p)\n", link); #endif /* If the device is currently in use, we won't release until it is actually closed. */ if (link->open) { #ifdef PCMCIA_DEBUG if (pc_debug>1) printk(KERN_INFO "cs89x0_cs: release postponed, '%s' " "still open\n", link->dev->dev_name); #endif link->state |= DEV_STALE_CONFIG; return; } if (link->dev != '\0') unregister_netdev(&(local->dev)); /* Unlink the device chain */ link->dev = NULL; /* Don't bother checking to see if these succeed or not */ CardServices(ReleaseWindow, link->win); CardServices(ReleaseConfiguration, link->handle); CardServices(ReleaseIO, link->handle, &link->io); CardServices(ReleaseIRQ, link->handle, &link->irq); #if CS_RELEASE_CODE <= 0x2911 if (link->irq.AssignedIRQ != 0) irq2dev_map[link->irq.AssignedIRQ] = NULL; #endif link->state &= ~DEV_CONFIG; if (link->state & DEV_STALE_LINK) cs89x0_detach(link); } /* cs89x0_release */ /*====================================================================== The card status event handler. Mostly, this schedules other stuff to run after an event is received. A CARD_REMOVAL event also sets some flags to discourage the net drivers from trying to talk to the card any more. When a CARD_REMOVAL event is received, we immediately set a flag to block future accesses to this device. All the functions that actually access the device should check this flag to make sure the card is still present. ======================================================================*/ static int cs89x0_event(event_t event, int priority, event_callback_args_t *args) { dev_link_t *link = args->client_data; local_info_t *lp = (local_info_t*)link->priv; #ifdef PCMCIA_DEBUG if (pc_debug > 1) printk("cs89x0_event(0x%x)\n",event); #endif switch (event) { case CS_EVENT_CARD_REMOVAL: link->state &= ~DEV_PRESENT; if (link->state & DEV_CONFIG) { lp->dev.tbusy = 1; lp->dev.start = 0; link->release.expires = RUN_AT(HZ/20); add_timer(&link->release); } break; case CS_EVENT_CARD_INSERTION: link->state |= DEV_PRESENT; cs89x0_config(link); break; case CS_EVENT_PM_SUSPEND: link->state |= DEV_SUSPEND; /* Fall through... */ case CS_EVENT_RESET_PHYSICAL: if (link->state & DEV_CONFIG) { if (link->open) { lp->dev.tbusy = 1; lp->dev.start = 0; } CardServices(ReleaseConfiguration, link->handle); } break; case CS_EVENT_PM_RESUME: link->state &= ~DEV_SUSPEND; /* Fall through... */ case CS_EVENT_CARD_RESET: if (link->state & DEV_CONFIG) { CardServices(RequestConfiguration, link->handle, &link->conf); if (link->open) { lp->dev.interrupt = 0; lp->dev.tbusy = 0; lp->dev.start = 1; lp->status = STATUS_IDLE; cs89x0_reset(&lp->dev); } } break; } return 0; } /* cs89x0_event */ static void cs89x0_TxEvent(struct device *dev, struct local_info_t *lp, int isq) { if (!dev->tbusy || lp->status == STATUS_IDLE) { return; } isq &= (TX_OK | TX_LATE_COL | TX_JBR | TX_16_COL | TX_LOST_CRS | TX_SQE_ERROR); if (isq == 0) return; if (isq != TX_OK) { printk(KERN_DEBUG "%s: TxEvent transmit error isq=%x\n", dev->name, isq); } del_timer(&lp->tx_timer); if (lp->status == STATUS_TRANSMITTING) { lp->status = STATUS_IDLE; return; } lp->status = STATUS_IDLE; dev->tbusy = 0; mark_bh(NET_BH); } /*====================================================================*/ static void cs89x0_interrupt IRQ(int irq, void *dev_id, struct pt_regs *regs) { #if CS_RELEASE_CODE > 0x2911 struct device *dev = (struct device *)dev_id; #else struct device *dev = (struct device *)(irq2dev_map[irq]); #endif struct local_info_t *lp; int ioaddr, isq; if (dev == NULL) { printk(KERN_DEBUG "cs89x0_interrupt(): irq %d for unknown device\n", irq); return; } lp = (struct local_info_t*)dev->priv; if (dev->interrupt) { printk(KERN_NOTICE "%s: re-entering the interrupt handler.\n", dev->name); return; } dev->interrupt = 1; ioaddr = dev->base_addr; while ((isq = inw(ioaddr + ISQ_PORT))) { switch(isq & ISQ_EVENT_MASK) { case ISQ_RECEIVER_EVENT: #ifdef PCMCIA_DEBUG if (pc_debug > 2) printk(KERN_DEBUG "%s: cs89x0_cs: rx ev (isq = 0x%04x)\n", dev->name,isq); #endif cs89x0_RxEvent(lp,isq); break; case ISQ_BUFFER_EVENT: #ifdef PCMCIA_DEBUG if (pc_debug > 2) printk(KERN_DEBUG "cs89x0_cs: buff ev\n"); #endif break; case ISQ_TRANSMITTER_EVENT: #ifdef PCMCIA_DEBUG if (pc_debug > 2) printk(KERN_DEBUG "cs89x0_cs: tx ev\n"); #endif cs89x0_TxEvent(dev, lp, isq); break; case ISQ_RX_MISS_EVENT: #ifdef PCMCIA_DEBUG if (pc_debug > 2) printk(KERN_DEBUG "cs89x0_cs: rx_miss ev\n"); #endif break; case ISQ_TX_COL_EVENT: #ifdef PCMCIA_DEBUG if (pc_debug > 2) printk(KERN_DEBUG "cs89x0_cs: tx_col ev\n"); #endif break; case 0: break; default: #ifdef PCMCIA_DEBUG if (pc_debug > 1) printk(KERN_DEBUG "%s: unknown event (isq = 0x%04x) \n", dev->name, isq); #endif break; } } dev->interrupt = 0; } /* cs89x0_interrupt */ /*====================================================================*/ int init_cs89x0(void) { servinfo_t serv; #ifdef PCMCIA_DEBUG if (pc_debug) printk(KERN_DEBUG "%s\n", version); #endif CardServices(GetCardServicesInfo, &serv); if (serv.Revision != CS_RELEASE_CODE) { printk("cs89x0: Card Services release does not match!\n"); return -1; } register_pcmcia_driver(&dev_info, &cs89x0_attach, &cs89x0_detach); return 0; } #ifdef MODULE void cleanup_module(void) { #ifdef PCMCIA_DEBUG if (pc_debug>1) printk("cs89x0_cs: unloading\n"); #endif unregister_pcmcia_driver(&dev_info); while (dev_list != NULL) { if (dev_list->state & DEV_CONFIG) cs89x0_release((u_long)dev_list); cs89x0_detach(dev_list); } } #endif /* MODULE */ /* SET_PORT(PP_AutoNegST,ioaddr); printk("AutoNegST = 0x%04x\n",GET_DATA(ioaddr)); SET_PORT(PP_BusST,ioaddr); printk("BusST = 0x%04x\n",GET_DATA(ioaddr)); SET_PORT(PP_LineST,ioaddr); printk("LineST = 0x%04x\n",GET_DATA(ioaddr)); SET_PORT(PP_RxEvent,ioaddr); printk("RxEvent = 0x%04x\n",GET_DATA(ioaddr)); SET_PORT(PP_TxEvent,ioaddr); printk("TxEvent = 0x%04x\n",GET_DATA(ioaddr)); */