diff options
author | Thomas Schwinge <tschwinge@gnu.org> | 2006-07-26 22:52:56 +0000 |
---|---|---|
committer | Thomas Schwinge <tschwinge@gnu.org> | 2009-06-18 00:26:38 +0200 |
commit | affb871b497129f7f191cfaf7bd458342b22c05f (patch) | |
tree | a4397a3b44fd945c92f7e6f8905dc52044f5dff6 | |
parent | 93e37a911f657e6e0b1f9b47a0761bb5fbf4ac35 (diff) |
2006-07-27 Thomas Schwinge <tschwinge@gnu.org>
Import a number of files from the pcmcia-cs package, version 3.2.8,
available from <http://pcmcia-cs.sourceforge.net/>.
* linux/pcmcia-cs/clients/3c574_cs.c: Import file.
* linux/pcmcia-cs/clients/3c589_cs.c: Likewise.
* linux/pcmcia-cs/clients/ax8390.h: Likewise.
* linux/pcmcia-cs/clients/axnet_cs.c: Likewise.
* linux/pcmcia-cs/clients/fmvj18x_cs.c: Likewise.
* linux/pcmcia-cs/clients/nmclan_cs.c: Likewise.
* linux/pcmcia-cs/clients/ositech.h: Likewise.
* linux/pcmcia-cs/clients/pcnet_cs.c: Likewise.
* linux/pcmcia-cs/clients/smc91c92_cs.c: Likewise.
* linux/pcmcia-cs/clients/xirc2ps_cs.c: Likewise.
* linux/pcmcia-cs/include/linux/crc32.h: Likewise.
* linux/pcmcia-cs/include/linux/init.h: Likewise.
* linux/pcmcia-cs/include/linux/slab.h: Likewise.
* linux/pcmcia-cs/include/pcmcia/bulkmem.h: Likewise.
* linux/pcmcia-cs/include/pcmcia/bus_ops.h: Likewise.
* linux/pcmcia-cs/include/pcmcia/ciscode.h: Likewise.
* linux/pcmcia-cs/include/pcmcia/cisreg.h: Likewise.
* linux/pcmcia-cs/include/pcmcia/cistpl.h: Likewise.
* linux/pcmcia-cs/include/pcmcia/cs.h: Likewise.
* linux/pcmcia-cs/include/pcmcia/cs_types.h: Likewise.
* linux/pcmcia-cs/include/pcmcia/driver_ops.h: Likewise.
* linux/pcmcia-cs/include/pcmcia/ds.h: Likewise.
* linux/pcmcia-cs/include/pcmcia/mem_op.h: Likewise.
* linux/pcmcia-cs/include/pcmcia/ss.h: Likewise.
* linux/pcmcia-cs/include/pcmcia/version.h: Likewise.
* linux/pcmcia-cs/modules/bulkmem.c: Likewise.
* linux/pcmcia-cs/modules/cirrus.h: Likewise.
* linux/pcmcia-cs/modules/cistpl.c: Likewise.
* linux/pcmcia-cs/modules/cs.c: Likewise.
* linux/pcmcia-cs/modules/cs_internal.h: Likewise.
* linux/pcmcia-cs/modules/ds.c: Likewise.
* linux/pcmcia-cs/modules/ene.h: Likewise.
* linux/pcmcia-cs/modules/i82365.c: Likewise.
* linux/pcmcia-cs/modules/i82365.h: Likewise.
* linux/pcmcia-cs/modules/o2micro.h: Likewise.
* linux/pcmcia-cs/modules/pci_fixup.c: Likewise.
* linux/pcmcia-cs/modules/ricoh.h: Likewise.
* linux/pcmcia-cs/modules/rsrc_mgr.c: Likewise.
* linux/pcmcia-cs/modules/smc34c90.h: Likewise.
* linux/pcmcia-cs/modules/ti113x.h: Likewise.
* linux/pcmcia-cs/modules/topic.h: Likewise.
* linux/pcmcia-cs/modules/vg468.h: Likewise.
* linux/pcmcia-cs/modules/yenta.h: Likewise.
* linux/pcmcia-cs/wireless/hermes.c: Likewise.
* linux/pcmcia-cs/wireless/hermes.h: Likewise.
* linux/pcmcia-cs/wireless/hermes_rid.h: Likewise.
* linux/pcmcia-cs/wireless/ieee802_11.h: Likewise.
* linux/pcmcia-cs/wireless/orinoco.c: Likewise.
* linux/pcmcia-cs/wireless/orinoco.h: Likewise.
* linux/pcmcia-cs/wireless/orinoco_cs.c: Likewise.
51 files changed, 33998 insertions, 3 deletions
@@ -1,3 +1,59 @@ +2006-07-27 Thomas Schwinge <tschwinge@gnu.org> + + Import a number of files from the pcmcia-cs package, version 3.2.8, + available from <http://pcmcia-cs.sourceforge.net/>. + + * linux/pcmcia-cs/clients/3c574_cs.c: Import file. + * linux/pcmcia-cs/clients/3c589_cs.c: Likewise. + * linux/pcmcia-cs/clients/ax8390.h: Likewise. + * linux/pcmcia-cs/clients/axnet_cs.c: Likewise. + * linux/pcmcia-cs/clients/fmvj18x_cs.c: Likewise. + * linux/pcmcia-cs/clients/nmclan_cs.c: Likewise. + * linux/pcmcia-cs/clients/ositech.h: Likewise. + * linux/pcmcia-cs/clients/pcnet_cs.c: Likewise. + * linux/pcmcia-cs/clients/smc91c92_cs.c: Likewise. + * linux/pcmcia-cs/clients/xirc2ps_cs.c: Likewise. + * linux/pcmcia-cs/include/linux/crc32.h: Likewise. + * linux/pcmcia-cs/include/linux/init.h: Likewise. + * linux/pcmcia-cs/include/linux/slab.h: Likewise. + * linux/pcmcia-cs/include/pcmcia/bulkmem.h: Likewise. + * linux/pcmcia-cs/include/pcmcia/bus_ops.h: Likewise. + * linux/pcmcia-cs/include/pcmcia/ciscode.h: Likewise. + * linux/pcmcia-cs/include/pcmcia/cisreg.h: Likewise. + * linux/pcmcia-cs/include/pcmcia/cistpl.h: Likewise. + * linux/pcmcia-cs/include/pcmcia/cs.h: Likewise. + * linux/pcmcia-cs/include/pcmcia/cs_types.h: Likewise. + * linux/pcmcia-cs/include/pcmcia/driver_ops.h: Likewise. + * linux/pcmcia-cs/include/pcmcia/ds.h: Likewise. + * linux/pcmcia-cs/include/pcmcia/mem_op.h: Likewise. + * linux/pcmcia-cs/include/pcmcia/ss.h: Likewise. + * linux/pcmcia-cs/include/pcmcia/version.h: Likewise. + * linux/pcmcia-cs/modules/bulkmem.c: Likewise. + * linux/pcmcia-cs/modules/cirrus.h: Likewise. + * linux/pcmcia-cs/modules/cistpl.c: Likewise. + * linux/pcmcia-cs/modules/cs.c: Likewise. + * linux/pcmcia-cs/modules/cs_internal.h: Likewise. + * linux/pcmcia-cs/modules/ds.c: Likewise. + * linux/pcmcia-cs/modules/ene.h: Likewise. + * linux/pcmcia-cs/modules/i82365.c: Likewise. + * linux/pcmcia-cs/modules/i82365.h: Likewise. + * linux/pcmcia-cs/modules/o2micro.h: Likewise. + * linux/pcmcia-cs/modules/pci_fixup.c: Likewise. + * linux/pcmcia-cs/modules/ricoh.h: Likewise. + * linux/pcmcia-cs/modules/rsrc_mgr.c: Likewise. + * linux/pcmcia-cs/modules/smc34c90.h: Likewise. + * linux/pcmcia-cs/modules/ti113x.h: Likewise. + * linux/pcmcia-cs/modules/topic.h: Likewise. + * linux/pcmcia-cs/modules/vg468.h: Likewise. + * linux/pcmcia-cs/modules/yenta.h: Likewise. + * linux/pcmcia-cs/wireless/hermes.c: Likewise. + * linux/pcmcia-cs/wireless/hermes.h: Likewise. + * linux/pcmcia-cs/wireless/hermes_rid.h: Likewise. + * linux/pcmcia-cs/wireless/ieee802_11.h: Likewise. + * linux/pcmcia-cs/wireless/orinoco.c: Likewise. + * linux/pcmcia-cs/wireless/orinoco.h: Likewise. + * linux/pcmcia-cs/wireless/orinoco_cs.c: Likewise. + 2006-07-24 Thomas Schwinge <tschwinge@gnu.org> * Makerules.in (%.h %_user.c): Add `%.cli.d' to the target list. @@ -1296,9 +1352,9 @@ * linux/src/include/asm-i386/hardirq.h: Likewise. * linux/src/include/linux/spinlock.h: Likewise. * linux/src/include/linux/compatmac.h: Likewise. - * linux/dev/drivers/net/eepro100.c: New file, modified version from - the above to glue it in gnumach. - Submitted by Kapil H. Paranjape <kapil@imsc.ernet.in>. + * linux/dev/drivers/net/eepro100.c: New file, modified version from + the above to glue it in gnumach. + Submitted by Kapil H. Paranjape <kapil@imsc.ernet.in>. 2001-08-17 Roland McGrath <roland@frob.com> diff --git a/linux/pcmcia-cs/clients/3c574_cs.c b/linux/pcmcia-cs/clients/3c574_cs.c new file mode 100644 index 0000000..9dc045a --- /dev/null +++ b/linux/pcmcia-cs/clients/3c574_cs.c @@ -0,0 +1,1349 @@ +/* 3c574.c: A PCMCIA ethernet driver for the 3com 3c574 "RoadRunner". + + Written 1993-1998 by + Donald Becker, becker@scyld.com, (driver core) and + David Hinds, dahinds@users.sourceforge.net (from his PC card code). + + This software may be used and distributed according to the terms of + the GNU General Public License, incorporated herein by reference. + + This driver derives from Donald Becker's 3c509 core, which has the + following copyright: + Copyright 1993 United States Government as represented by the + Director, National Security Agency. + +*/ + +/* + Theory of Operation + +I. Board Compatibility + +This device driver is designed for the 3Com 3c574 PC card Fast Ethernet +Adapter. + +II. Board-specific settings + +None -- PC cards are autoconfigured. + +III. Driver operation + +The 3c574 uses a Boomerang-style interface, without the bus-master capability. +See the Boomerang driver and documentation for most details. + +IV. Notes and chip documentation. + +Two added registers are used to enhance PIO performance, RunnerRdCtrl and +RunnerWrCtrl. These are 11 bit down-counters that are preloaded with the +count of word (16 bits) reads or writes the driver is about to do to the Rx +or Tx FIFO. The chip is then able to hide the internal-PCI-bus to PC-card +translation latency by buffering the I/O operations with an 8 word FIFO. +Note: No other chip accesses are permitted when this buffer is used. + +A second enhancement is that both attribute and common memory space +0x0800-0x0fff can translated to the PIO FIFO. Thus memory operations (faster +with *some* PCcard bridges) may be used instead of I/O operations. +This is enabled by setting the 0x10 bit in the PCMCIA LAN COR. + +Some slow PC card bridges work better if they never see a WAIT signal. +This is configured by setting the 0x20 bit in the PCMCIA LAN COR. +Only do this after testing that it is reliable and improves performance. + +The upper five bits of RunnerRdCtrl are used to window into PCcard +configuration space registers. Window 0 is the regular Boomerang/Odie +register set, 1-5 are various PC card control registers, and 16-31 are +the (reversed!) CIS table. + +A final note: writing the InternalConfig register in window 3 with an +invalid ramWidth is Very Bad. + +V. References + +http://www.scyld.com/expert/NWay.html +http://www.national.com/pf/DP/DP83840.html + +Thanks to Terry Murphy of 3Com for providing development information for +earlier 3Com products. + +*/ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/in.h> +#include <linux/delay.h> +#include <asm/io.h> +#include <asm/system.h> +#include <asm/bitops.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/if_arp.h> +#include <linux/ioport.h> + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/cisreg.h> +#include <pcmcia/ciscode.h> +#include <pcmcia/ds.h> +#include <pcmcia/mem_op.h> + +/*====================================================================*/ + +/* Module parameters */ + +MODULE_AUTHOR("David Hinds <dahinds@users.sourceforge.net>"); +MODULE_DESCRIPTION("3Com 3c574 series PCMCIA ethernet driver"); +MODULE_LICENSE("GPL"); + +#define INT_MODULE_PARM(n, v) static int n = v; MODULE_PARM(n, "i") + +/* Now-standard PC card module parameters. */ +INT_MODULE_PARM(irq_mask, 0xdeb8); +static int irq_list[4] = { -1 }; +MODULE_PARM(irq_list, "1-4i"); + +/* Maximum events (Rx packets, etc.) to handle at each interrupt. */ +INT_MODULE_PARM(max_interrupt_work, 32); + +/* Force full duplex modes? */ +INT_MODULE_PARM(full_duplex, 0); + +/* Autodetect link polarity reversal? */ +INT_MODULE_PARM(auto_polarity, 1); + +#ifdef PCMCIA_DEBUG +INT_MODULE_PARM(pc_debug, PCMCIA_DEBUG); +#define DEBUG(n, args...) if (pc_debug>(n)) printk(KERN_DEBUG args) +static char *version = +"3c574_cs.c 1.70 2003/08/25 15:57:40 Donald Becker/David Hinds, becker@scyld.com.\n"; +#else +#define DEBUG(n, args...) +#endif + +/*====================================================================*/ + +/* Time in jiffies before concluding the transmitter is hung. */ +#define TX_TIMEOUT ((800*HZ)/1000) + +/* To minimize the size of the driver source and make the driver more + readable not all constants are symbolically defined. + You'll need the manual if you want to understand driver details anyway. */ +/* Offsets from base I/O address. */ +#define EL3_DATA 0x00 +#define EL3_CMD 0x0e +#define EL3_STATUS 0x0e + +#define EL3WINDOW(win_num) outw(SelectWindow + (win_num), ioaddr + EL3_CMD) + +/* The top five bits written to EL3_CMD are a command, the lower + 11 bits are the parameter, if applicable. */ +enum el3_cmds { + TotalReset = 0<<11, SelectWindow = 1<<11, StartCoax = 2<<11, + RxDisable = 3<<11, RxEnable = 4<<11, RxReset = 5<<11, RxDiscard = 8<<11, + TxEnable = 9<<11, TxDisable = 10<<11, TxReset = 11<<11, + FakeIntr = 12<<11, AckIntr = 13<<11, SetIntrEnb = 14<<11, + SetStatusEnb = 15<<11, SetRxFilter = 16<<11, SetRxThreshold = 17<<11, + SetTxThreshold = 18<<11, SetTxStart = 19<<11, StatsEnable = 21<<11, + StatsDisable = 22<<11, StopCoax = 23<<11, +}; + +enum elxl_status { + IntLatch = 0x0001, AdapterFailure = 0x0002, TxComplete = 0x0004, + TxAvailable = 0x0008, RxComplete = 0x0010, RxEarly = 0x0020, + IntReq = 0x0040, StatsFull = 0x0080, CmdBusy = 0x1000 }; + +/* The SetRxFilter command accepts the following classes: */ +enum RxFilter { + RxStation = 1, RxMulticast = 2, RxBroadcast = 4, RxProm = 8 +}; + +enum Window0 { + Wn0EepromCmd = 10, Wn0EepromData = 12, /* EEPROM command/address, data. */ + IntrStatus=0x0E, /* Valid in all windows. */ +}; +/* These assumes the larger EEPROM. */ +enum Win0_EEPROM_cmds { + EEPROM_Read = 0x200, EEPROM_WRITE = 0x100, EEPROM_ERASE = 0x300, + EEPROM_EWENB = 0x30, /* Enable erasing/writing for 10 msec. */ + EEPROM_EWDIS = 0x00, /* Disable EWENB before 10 msec timeout. */ +}; + +/* Register window 1 offsets, the window used in normal operation. + On the "Odie" this window is always mapped at offsets 0x10-0x1f. + Except for TxFree, which is overlapped by RunnerWrCtrl. */ +enum Window1 { + TX_FIFO = 0x10, RX_FIFO = 0x10, RxErrors = 0x14, + RxStatus = 0x18, Timer=0x1A, TxStatus = 0x1B, + TxFree = 0x0C, /* Remaining free bytes in Tx buffer. */ + RunnerRdCtrl = 0x16, RunnerWrCtrl = 0x1c, +}; + +enum Window3 { /* Window 3: MAC/config bits. */ + Wn3_Config=0, Wn3_MAC_Ctrl=6, Wn3_Options=8, +}; +union wn3_config { + int i; + struct w3_config_fields { + unsigned int ram_size:3, ram_width:1, ram_speed:2, rom_size:2; + int pad8:8; + unsigned int ram_split:2, pad18:2, xcvr:3, pad21:1, autoselect:1; + int pad24:7; + } u; +}; + +enum Window4 { /* Window 4: Xcvr/media bits. */ + Wn4_FIFODiag = 4, Wn4_NetDiag = 6, Wn4_PhysicalMgmt=8, Wn4_Media = 10, +}; + + +#define MEDIA_TP 0x00C0 /* Enable link beat and jabber for 10baseT. */ + +struct el3_private { + dev_link_t link; + struct net_device dev; + dev_node_t node; + struct net_device_stats stats; + u16 advertising, partner; /* NWay media advertisement */ + unsigned char phys; /* MII device address */ + unsigned int + autoselect:1, default_media:3; /* Read from the EEPROM/Wn3_Config. */ + /* for transceiver monitoring */ + struct timer_list media; + u_short media_status; + u_short fast_poll; + u_long last_irq; +}; + +/* Set iff a MII transceiver on any interface requires mdio preamble. + This only set with the original DP83840 on older 3c905 boards, so the extra + code size of a per-interface flag is not worthwhile. */ +static char mii_preamble_required = 0; + +/* Index of functions. */ + +static void tc574_config(dev_link_t *link); +static void tc574_release(u_long arg); +static int tc574_event(event_t event, int priority, + event_callback_args_t *args); + +static void mdio_sync(ioaddr_t ioaddr, int bits); +static int mdio_read(ioaddr_t ioaddr, int phy_id, int location); +static void mdio_write(ioaddr_t ioaddr, int phy_id, int location, int value); +static u_short read_eeprom(ioaddr_t ioaddr, int index); +static void tc574_wait_for_completion(struct net_device *dev, int cmd); + +static void tc574_reset(struct net_device *dev); +static void media_check(u_long arg); +static int el3_open(struct net_device *dev); +static int el3_start_xmit(struct sk_buff *skb, struct net_device *dev); +static void el3_interrupt(int irq, void *dev_id, struct pt_regs *regs); +static void update_stats(struct net_device *dev); +static struct net_device_stats *el3_get_stats(struct net_device *dev); +static int el3_rx(struct net_device *dev, int worklimit); +static int el3_close(struct net_device *dev); +static void el3_tx_timeout(struct net_device *dev); +static int el3_ioctl(struct net_device *dev, struct ifreq *rq, int cmd); +static void set_rx_mode(struct net_device *dev); + +static dev_info_t dev_info = "3c574_cs"; + +static dev_link_t *tc574_attach(void); +static void tc574_detach(dev_link_t *); + +static dev_link_t *dev_list = NULL; + +static void flush_stale_links(void) +{ + dev_link_t *link, *next; + for (link = dev_list; link; link = next) { + next = link->next; + if (link->state & DEV_STALE_LINK) + tc574_detach(link); + } +} + +static void cs_error(client_handle_t handle, int func, int ret) +{ +#if CS_RELEASE_CODE < 0x2911 + CardServices(ReportError, dev_info, (void *)func, (void *)ret); +#else + error_info_t err = { func, ret }; + CardServices(ReportError, handle, &err); +#endif +} + +/* + tc574_attach() creates an "instance" of the driver, allocating + local data structures for one device. The device is registered + with Card Services. +*/ + +static dev_link_t *tc574_attach(void) +{ + struct el3_private *lp; + client_reg_t client_reg; + dev_link_t *link; + struct net_device *dev; + int i, ret; + + DEBUG(0, "3c574_attach()\n"); + flush_stale_links(); + + /* Create the PC card device object. */ + lp = kmalloc(sizeof(*lp), GFP_KERNEL); + if (!lp) return NULL; + memset(lp, 0, sizeof(*lp)); + link = &lp->link; dev = &lp->dev; + link->priv = dev->priv = link->irq.Instance = lp; + + init_timer(&link->release); + link->release.function = &tc574_release; + link->release.data = (u_long)link; + link->io.NumPorts1 = 32; + link->io.Attributes1 = IO_DATA_PATH_WIDTH_16; + link->irq.Attributes = IRQ_TYPE_EXCLUSIVE | IRQ_HANDLE_PRESENT; + link->irq.IRQInfo1 = IRQ_INFO2_VALID|IRQ_LEVEL_ID; + if (irq_list[0] == -1) + link->irq.IRQInfo2 = irq_mask; + else + for (i = 0; i < 4; i++) + link->irq.IRQInfo2 |= 1 << irq_list[i]; + link->irq.Handler = &el3_interrupt; + 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; + + /* The EL3-specific entries in the device structure. */ + dev->hard_start_xmit = &el3_start_xmit; + dev->get_stats = &el3_get_stats; + dev->do_ioctl = &el3_ioctl; + dev->set_multicast_list = &set_rx_mode; + ether_setup(dev); + init_dev_name(dev, lp->node); + dev->open = &el3_open; + dev->stop = &el3_close; +#ifdef HAVE_TX_TIMEOUT + dev->tx_timeout = el3_tx_timeout; + dev->watchdog_timeo = TX_TIMEOUT; +#endif + + /* 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 = &tc574_event; + client_reg.Version = 0x0210; + client_reg.event_callback_args.client_data = link; + ret = CardServices(RegisterClient, &link->handle, &client_reg); + if (ret != 0) { + cs_error(link->handle, RegisterClient, ret); + tc574_detach(link); + return NULL; + } + + return link; +} /* tc574_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 tc574_detach(dev_link_t *link) +{ + struct el3_private *lp = link->priv; + dev_link_t **linkp; + + DEBUG(0, "3c574_detach(0x%p)\n", link); + + /* Locate device structure */ + for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next) + if (*linkp == link) break; + if (*linkp == NULL) + return; + + del_timer(&link->release); + if (link->state & DEV_CONFIG) { + tc574_release((u_long)link); + if (link->state & DEV_STALE_CONFIG) { + link->state |= DEV_STALE_LINK; + return; + } + } + + if (link->handle) + CardServices(DeregisterClient, link->handle); + + /* Unlink device structure, free bits */ + *linkp = link->next; + if (link->dev) + unregister_netdev(&lp->dev); + kfree(lp); + +} /* tc574_detach */ + +/* + tc574_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. +*/ + +#define CS_CHECK(fn, args...) \ +while ((last_ret=CardServices(last_fn=(fn), args))!=0) goto cs_failed + +static void tc574_config(dev_link_t *link) +{ + client_handle_t handle = link->handle; + struct el3_private *lp = link->priv; + struct net_device *dev = &lp->dev; + tuple_t tuple; + cisparse_t parse; + u_short buf[32]; + int last_fn, last_ret, i, j; + ioaddr_t ioaddr; + u16 *phys_addr; + char *cardname; + + phys_addr = (u16 *)dev->dev_addr; + + DEBUG(0, "3c574_config(0x%p)\n", link); + + tuple.Attributes = 0; + tuple.DesiredTuple = CISTPL_CONFIG; + CS_CHECK(GetFirstTuple, handle, &tuple); + tuple.TupleData = (cisdata_t *)buf; + tuple.TupleDataMax = 64; + tuple.TupleOffset = 0; + CS_CHECK(GetTupleData, handle, &tuple); + CS_CHECK(ParseTuple, handle, &tuple, &parse); + link->conf.ConfigBase = parse.config.base; + link->conf.Present = parse.config.rmask[0]; + + /* Configure card */ + link->state |= DEV_CONFIG; + + link->io.IOAddrLines = 16; + for (i = j = 0; j < 0x400; j += 0x20) { + link->io.BasePort1 = j ^ 0x300; + i = CardServices(RequestIO, link->handle, &link->io); + if (i == CS_SUCCESS) break; + } + if (i != CS_SUCCESS) { + cs_error(link->handle, RequestIO, i); + goto failed; + } + CS_CHECK(RequestIRQ, link->handle, &link->irq); + CS_CHECK(RequestConfiguration, link->handle, &link->conf); + + dev->irq = link->irq.AssignedIRQ; + dev->base_addr = link->io.BasePort1; + + if (register_netdev(dev) != 0) { + printk(KERN_NOTICE "3c574_cs: register_netdev() failed\n"); + goto failed; + } + + ioaddr = dev->base_addr; + copy_dev_name(lp->node, dev); + link->dev = &lp->node; + + /* The 3c574 normally uses an EEPROM for configuration info, including + the hardware address. The future products may include a modem chip + and put the address in the CIS. */ + tuple.DesiredTuple = 0x88; + if (CardServices(GetFirstTuple, handle, &tuple) == CS_SUCCESS) { + CardServices(GetTupleData, handle, &tuple); + for (i = 0; i < 3; i++) + phys_addr[i] = htons(buf[i]); + } else { + EL3WINDOW(0); + for (i = 0; i < 3; i++) + phys_addr[i] = htons(read_eeprom(ioaddr, i + 10)); + if (phys_addr[0] == 0x6060) { + printk(KERN_NOTICE "3c574_cs: IO port conflict at 0x%03lx" + "-0x%03lx\n", dev->base_addr, dev->base_addr+15); + goto failed; + } + } + tuple.DesiredTuple = CISTPL_VERS_1; + if (CardServices(GetFirstTuple, handle, &tuple) == CS_SUCCESS && + CardServices(GetTupleData, handle, &tuple) == CS_SUCCESS && + CardServices(ParseTuple, handle, &tuple, &parse) == CS_SUCCESS) { + cardname = parse.version_1.str + parse.version_1.ofs[1]; + } else + cardname = "3Com 3c574"; + + printk(KERN_INFO "%s: %s at io %#3lx, irq %d, hw_addr ", + dev->name, cardname, dev->base_addr, dev->irq); + + for (i = 0; i < 6; i++) + printk("%02X%s", dev->dev_addr[i], ((i<5) ? ":" : ".\n")); + + { + u_char mcr, *ram_split[] = {"5:3", "3:1", "1:1", "3:5"}; + union wn3_config config; + outw(2<<11, ioaddr + RunnerRdCtrl); + mcr = inb(ioaddr + 2); + outw(0<<11, ioaddr + RunnerRdCtrl); + printk(KERN_INFO " ASIC rev %d,", mcr>>3); + EL3WINDOW(3); + config.i = inl(ioaddr + Wn3_Config); + printk(" %dK FIFO split %s Rx:Tx, %sMII interface.\n", + 8 << config.u.ram_size, ram_split[config.u.ram_split], + config.u.autoselect ? "autoselect " : ""); + lp->default_media = config.u.xcvr; + lp->autoselect = config.u.autoselect; + } + + { + int phy; + + /* Roadrunner only: Turn on the MII transceiver */ + outw(0x8040, ioaddr + Wn3_Options); + mdelay(1); + outw(0xc040, ioaddr + Wn3_Options); + tc574_wait_for_completion(dev, TxReset); + tc574_wait_for_completion(dev, RxReset); + mdelay(1); + outw(0x8040, ioaddr + Wn3_Options); + + EL3WINDOW(4); + for (phy = 1; phy <= 32; phy++) { + int mii_status; + mdio_sync(ioaddr, 32); + mii_status = mdio_read(ioaddr, phy & 0x1f, 1); + if (mii_status != 0xffff) { + lp->phys = phy & 0x1f; + DEBUG(0, " MII transceiver at index %d, status %x.\n", + phy, mii_status); + if ((mii_status & 0x0040) == 0) + mii_preamble_required = 1; + break; + } + } + if (phy > 32) { + printk(KERN_NOTICE " No MII transceivers found!\n"); + goto failed; + } + i = mdio_read(ioaddr, lp->phys, 16) | 0x40; + mdio_write(ioaddr, lp->phys, 16, i); + lp->advertising = mdio_read(ioaddr, lp->phys, 4); + if (full_duplex) { + /* Only advertise the FD media types. */ + lp->advertising &= ~0x02a0; + mdio_write(ioaddr, lp->phys, 4, lp->advertising); + } + } + + link->state &= ~DEV_CONFIG_PENDING; + return; + +cs_failed: + cs_error(link->handle, last_fn, last_ret); +failed: + tc574_release((u_long)link); + link->state &= ~DEV_CONFIG_PENDING; + return; + +} /* tc574_config */ + +/* + After a card is removed, tc574_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 tc574_release(u_long arg) +{ + dev_link_t *link = (dev_link_t *)arg; + + DEBUG(0, "3c574_release(0x%p)\n", link); + + if (link->open) { + DEBUG(1, "3c574_cs: release postponed, '%s' still open\n", + link->dev->dev_name); + link->state |= DEV_STALE_CONFIG; + return; + } + + CardServices(ReleaseConfiguration, link->handle); + CardServices(ReleaseIO, link->handle, &link->io); + CardServices(ReleaseIRQ, link->handle, &link->irq); + + link->state &= ~DEV_CONFIG; + +} /* tc574_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. +*/ + +static int tc574_event(event_t event, int priority, + event_callback_args_t *args) +{ + dev_link_t *link = args->client_data; + struct el3_private *lp = link->priv; + struct net_device *dev = &lp->dev; + + DEBUG(1, "3c574_event(0x%06x)\n", event); + + switch (event) { + case CS_EVENT_CARD_REMOVAL: + link->state &= ~DEV_PRESENT; + if (link->state & DEV_CONFIG) { + netif_device_detach(dev); + mod_timer(&link->release, jiffies + HZ/20); + } + break; + case CS_EVENT_CARD_INSERTION: + link->state |= DEV_PRESENT | DEV_CONFIG_PENDING; + tc574_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) + netif_device_detach(dev); + 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) { + tc574_reset(dev); + netif_device_attach(dev); + } + } + break; + } + return 0; +} /* tc574_event */ + +static void dump_status(struct net_device *dev) +{ + ioaddr_t ioaddr = dev->base_addr; + EL3WINDOW(1); + printk(KERN_INFO " irq status %04x, rx status %04x, tx status " + "%02x, tx free %04x\n", inw(ioaddr+EL3_STATUS), + inw(ioaddr+RxStatus), inb(ioaddr+TxStatus), + inw(ioaddr+TxFree)); + EL3WINDOW(4); + printk(KERN_INFO " diagnostics: fifo %04x net %04x ethernet %04x" + " media %04x\n", inw(ioaddr+0x04), inw(ioaddr+0x06), + inw(ioaddr+0x08), inw(ioaddr+0x0a)); + EL3WINDOW(1); +} + +/* + Use this for commands that may take time to finish +*/ +static void tc574_wait_for_completion(struct net_device *dev, int cmd) +{ + int i = 1500; + outw(cmd, dev->base_addr + EL3_CMD); + while (--i > 0) + if (!(inw(dev->base_addr + EL3_STATUS) & 0x1000)) break; + if (i == 0) + printk(KERN_NOTICE "%s: command 0x%04x did not complete!\n", + dev->name, cmd); +} + +/* Read a word from the EEPROM using the regular EEPROM access register. + Assume that we are in register window zero. + */ +static u_short read_eeprom(ioaddr_t ioaddr, int index) +{ + int timer; + outw(EEPROM_Read + index, ioaddr + Wn0EepromCmd); + /* Pause for at least 162 usec for the read to take place. */ + for (timer = 1620; timer >= 0; timer--) { + if ((inw(ioaddr + Wn0EepromCmd) & 0x8000) == 0) + break; + } + return inw(ioaddr + Wn0EepromData); +} + +/* MII transceiver control section. + Read and write the MII registers using software-generated serial + MDIO protocol. See the MII specifications or DP83840A data sheet + for details. + The maxium data clock rate is 2.5 Mhz. The timing is easily met by the + slow PC card interface. */ + +#define MDIO_SHIFT_CLK 0x01 +#define MDIO_DIR_WRITE 0x04 +#define MDIO_DATA_WRITE0 (0x00 | MDIO_DIR_WRITE) +#define MDIO_DATA_WRITE1 (0x02 | MDIO_DIR_WRITE) +#define MDIO_DATA_READ 0x02 +#define MDIO_ENB_IN 0x00 + +/* Generate the preamble required for initial synchronization and + a few older transceivers. */ +static void mdio_sync(ioaddr_t ioaddr, int bits) +{ + int mdio_addr = ioaddr + Wn4_PhysicalMgmt; + + /* Establish sync by sending at least 32 logic ones. */ + while (-- bits >= 0) { + outw(MDIO_DATA_WRITE1, mdio_addr); + outw(MDIO_DATA_WRITE1 | MDIO_SHIFT_CLK, mdio_addr); + } +} + +static int mdio_read(ioaddr_t ioaddr, int phy_id, int location) +{ + int i; + int read_cmd = (0xf6 << 10) | (phy_id << 5) | location; + unsigned int retval = 0; + int mdio_addr = ioaddr + Wn4_PhysicalMgmt; + + if (mii_preamble_required) + mdio_sync(ioaddr, 32); + + /* Shift the read command bits out. */ + for (i = 14; i >= 0; i--) { + int dataval = (read_cmd&(1<<i)) ? MDIO_DATA_WRITE1 : MDIO_DATA_WRITE0; + outw(dataval, mdio_addr); + outw(dataval | MDIO_SHIFT_CLK, mdio_addr); + } + /* Read the two transition, 16 data, and wire-idle bits. */ + for (i = 19; i > 0; i--) { + outw(MDIO_ENB_IN, mdio_addr); + retval = (retval << 1) | ((inw(mdio_addr) & MDIO_DATA_READ) ? 1 : 0); + outw(MDIO_ENB_IN | MDIO_SHIFT_CLK, mdio_addr); + } + return (retval>>1) & 0xffff; +} + +static void mdio_write(ioaddr_t ioaddr, int phy_id, int location, int value) +{ + int write_cmd = 0x50020000 | (phy_id << 23) | (location << 18) | value; + int mdio_addr = ioaddr + Wn4_PhysicalMgmt; + int i; + + if (mii_preamble_required) + mdio_sync(ioaddr, 32); + + /* Shift the command bits out. */ + for (i = 31; i >= 0; i--) { + int dataval = (write_cmd&(1<<i)) ? MDIO_DATA_WRITE1 : MDIO_DATA_WRITE0; + outw(dataval, mdio_addr); + outw(dataval | MDIO_SHIFT_CLK, mdio_addr); + } + /* Leave the interface idle. */ + for (i = 1; i >= 0; i--) { + outw(MDIO_ENB_IN, mdio_addr); + outw(MDIO_ENB_IN | MDIO_SHIFT_CLK, mdio_addr); + } + + return; +} + +/* Reset and restore all of the 3c574 registers. */ +static void tc574_reset(struct net_device *dev) +{ + struct el3_private *lp = (struct el3_private *)dev->priv; + int i, ioaddr = dev->base_addr; + + tc574_wait_for_completion(dev, TotalReset|0x10); + + /* Clear any transactions in progress. */ + outw(0, ioaddr + RunnerWrCtrl); + outw(0, ioaddr + RunnerRdCtrl); + + /* Set the station address and mask. */ + EL3WINDOW(2); + for (i = 0; i < 6; i++) + outb(dev->dev_addr[i], ioaddr + i); + for (; i < 12; i+=2) + outw(0, ioaddr + i); + + /* Reset config options */ + EL3WINDOW(3); + outb((dev->mtu > 1500 ? 0x40 : 0), ioaddr + Wn3_MAC_Ctrl); + outl((lp->autoselect ? 0x01000000 : 0) | 0x0062001b, + ioaddr + Wn3_Config); + + /* Roadrunner only: Turn on the MII transceiver. */ + outw(0x8040, ioaddr + Wn3_Options); + mdelay(1); + outw(0xc040, ioaddr + Wn3_Options); + tc574_wait_for_completion(dev, TxReset); + tc574_wait_for_completion(dev, RxReset); + mdelay(1); + outw(0x8040, ioaddr + Wn3_Options); + + /* Switch to the stats window, and clear all stats by reading. */ + outw(StatsDisable, ioaddr + EL3_CMD); + EL3WINDOW(6); + for (i = 0; i < 10; i++) + inb(ioaddr + i); + inw(ioaddr + 10); + inw(ioaddr + 12); + EL3WINDOW(4); + inb(ioaddr + 12); + inb(ioaddr + 13); + + /* .. enable any extra statistics bits.. */ + outw(0x0040, ioaddr + Wn4_NetDiag); + /* .. re-sync MII and re-fill what NWay is advertising. */ + mdio_sync(ioaddr, 32); + mdio_write(ioaddr, lp->phys, 4, lp->advertising); + if (!auto_polarity) { + /* works for TDK 78Q2120 series MII's */ + int i = mdio_read(ioaddr, lp->phys, 16) | 0x20; + mdio_write(ioaddr, lp->phys, 16, i); + } + + /* Switch to register set 1 for normal use, just for TxFree. */ + EL3WINDOW(1); + + set_rx_mode(dev); + outw(StatsEnable, ioaddr + EL3_CMD); /* Turn on statistics. */ + outw(RxEnable, ioaddr + EL3_CMD); /* Enable the receiver. */ + outw(TxEnable, ioaddr + EL3_CMD); /* Enable transmitter. */ + /* Allow status bits to be seen. */ + outw(SetStatusEnb | 0xff, ioaddr + EL3_CMD); + /* Ack all pending events, and set active indicator mask. */ + outw(AckIntr | IntLatch | TxAvailable | RxEarly | IntReq, + ioaddr + EL3_CMD); + outw(SetIntrEnb | IntLatch | TxAvailable | RxComplete | StatsFull + | AdapterFailure | RxEarly, ioaddr + EL3_CMD); +} + +static int el3_open(struct net_device *dev) +{ + struct el3_private *lp = (struct el3_private *)dev->priv; + dev_link_t *link = &lp->link; + + if (!DEV_OK(link)) + return -ENODEV; + + link->open++; + MOD_INC_USE_COUNT; + netif_start_queue(dev); + netif_mark_up(dev); + + tc574_reset(dev); + lp->media.function = &media_check; + lp->media.data = (u_long)lp; + lp->media.expires = jiffies + HZ; + add_timer(&lp->media); + + DEBUG(2, "%s: opened, status %4.4x.\n", + dev->name, inw(dev->base_addr + EL3_STATUS)); + + return 0; +} + +static void el3_tx_timeout(struct net_device *dev) +{ + struct el3_private *lp = (struct el3_private *)dev->priv; + ioaddr_t ioaddr = dev->base_addr; + + printk(KERN_NOTICE "%s: Transmit timed out!\n", dev->name); + dump_status(dev); + lp->stats.tx_errors++; + dev->trans_start = jiffies; + /* Issue TX_RESET and TX_START commands. */ + tc574_wait_for_completion(dev, TxReset); + outw(TxEnable, ioaddr + EL3_CMD); + netif_wake_queue(dev); +} + +static void pop_tx_status(struct net_device *dev) +{ + struct el3_private *lp = (struct el3_private *)dev->priv; + ioaddr_t ioaddr = dev->base_addr; + int i; + + /* Clear the Tx status stack. */ + for (i = 32; i > 0; i--) { + u_char tx_status = inb(ioaddr + TxStatus); + if (!(tx_status & 0x84)) break; + /* reset transmitter on jabber error or underrun */ + if (tx_status & 0x30) + tc574_wait_for_completion(dev, TxReset); + if (tx_status & 0x38) { + DEBUG(1, "%s: transmit error: status 0x%02x\n", + dev->name, tx_status); + outw(TxEnable, ioaddr + EL3_CMD); + lp->stats.tx_aborted_errors++; + } + outb(0x00, ioaddr + TxStatus); /* Pop the status stack. */ + } +} + +static int el3_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + ioaddr_t ioaddr = dev->base_addr; + + tx_timeout_check(dev, el3_tx_timeout); + skb_tx_check(dev, skb); + + DEBUG(3, "%s: el3_start_xmit(length = %ld) called, " + "status %4.4x.\n", dev->name, (long)skb->len, + inw(ioaddr + EL3_STATUS)); + + outw(skb->len, ioaddr + TX_FIFO); + outw(0, ioaddr + TX_FIFO); + outsl(ioaddr + TX_FIFO, skb->data, (skb->len+3)>>2); + + dev->trans_start = jiffies; + + /* TxFree appears only in Window 1, not offset 0x1c. */ + if (inw(ioaddr + TxFree) > 1536) { + netif_start_queue(dev); + } else + /* Interrupt us when the FIFO has room for max-sized packet. + The threshold is in units of dwords. */ + outw(SetTxThreshold + (1536>>2), ioaddr + EL3_CMD); + + DEV_KFREE_SKB (skb); + pop_tx_status(dev); + + return 0; +} + +/* The EL3 interrupt handler. */ +static void el3_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct el3_private *lp = dev_id; + struct net_device *dev = &lp->dev; + ioaddr_t ioaddr, status; + int work_budget = max_interrupt_work; + + if (!netif_device_present(dev)) + return; + ioaddr = dev->base_addr; + + DEBUG(3, "%s: interrupt, status %4.4x.\n", + dev->name, inw(ioaddr + EL3_STATUS)); + + while ((status = inw(ioaddr + EL3_STATUS)) & + (IntLatch | RxComplete | RxEarly | StatsFull)) { + if (!netif_device_present(dev) || + ((status & 0xe000) != 0x2000)) { + DEBUG(1, "%s: Interrupt from dead card\n", dev->name); + break; + } + + if (status & RxComplete) + work_budget = el3_rx(dev, work_budget); + + if (status & TxAvailable) { + DEBUG(3, " TX room bit was handled.\n"); + /* There's room in the FIFO for a full-sized packet. */ + outw(AckIntr | TxAvailable, ioaddr + EL3_CMD); + netif_wake_queue(dev); + } + + if (status & TxComplete) + pop_tx_status(dev); + + if (status & (AdapterFailure | RxEarly | StatsFull)) { + /* Handle all uncommon interrupts. */ + if (status & StatsFull) + update_stats(dev); + if (status & RxEarly) { + work_budget = el3_rx(dev, work_budget); + outw(AckIntr | RxEarly, ioaddr + EL3_CMD); + } + if (status & AdapterFailure) { + u16 fifo_diag; + EL3WINDOW(4); + fifo_diag = inw(ioaddr + Wn4_FIFODiag); + EL3WINDOW(1); + printk(KERN_NOTICE "%s: adapter failure, FIFO diagnostic" + " register %04x.\n", dev->name, fifo_diag); + if (fifo_diag & 0x0400) { + /* Tx overrun */ + tc574_wait_for_completion(dev, TxReset); + outw(TxEnable, ioaddr + EL3_CMD); + } + if (fifo_diag & 0x2000) { + /* Rx underrun */ + tc574_wait_for_completion(dev, RxReset); + set_rx_mode(dev); + outw(RxEnable, ioaddr + EL3_CMD); + } + outw(AckIntr | AdapterFailure, ioaddr + EL3_CMD); + } + } + + if (--work_budget < 0) { + DEBUG(0, "%s: Too much work in interrupt, " + "status %4.4x.\n", dev->name, status); + /* Clear all interrupts */ + outw(AckIntr | 0xFF, ioaddr + EL3_CMD); + break; + } + /* Acknowledge the IRQ. */ + outw(AckIntr | IntReq | IntLatch, ioaddr + EL3_CMD); + } + + DEBUG(3, "%s: exiting interrupt, status %4.4x.\n", + dev->name, inw(ioaddr + EL3_STATUS)); + return; +} + +/* + This timer serves two purposes: to check for missed interrupts + (and as a last resort, poll the NIC for events), and to monitor + the MII, reporting changes in cable status. +*/ +static void media_check(u_long arg) +{ + struct el3_private *lp = (struct el3_private *)arg; + struct net_device *dev = &lp->dev; + ioaddr_t ioaddr = dev->base_addr; + u_long flags; + u_short /* cable, */ media, partner; + + if (!netif_device_present(dev)) + goto reschedule; + + /* Check for pending interrupt with expired latency timer: with + this, we can limp along even if the interrupt is blocked */ + if ((inw(ioaddr + EL3_STATUS) & IntLatch) && + (inb(ioaddr + Timer) == 0xff)) { + if (!lp->fast_poll) + printk(KERN_INFO "%s: interrupt(s) dropped!\n", dev->name); + el3_interrupt(dev->irq, lp, NULL); + lp->fast_poll = HZ; + } + if (lp->fast_poll) { + lp->fast_poll--; + lp->media.expires = jiffies + 2; + add_timer(&lp->media); + return; + } + + save_flags(flags); + cli(); +#if 0 + outw(2<<11, ioaddr + RunnerRdCtrl); + cable = inb(ioaddr); + outb(0x20, ioaddr); + outw(0, ioaddr + RunnerRdCtrl); +#endif + EL3WINDOW(4); + media = mdio_read(ioaddr, lp->phys, 1); + partner = mdio_read(ioaddr, lp->phys, 5); + EL3WINDOW(1); + restore_flags(flags); + +#if 0 + if (cable & 0x20) + printk(KERN_INFO "%s: cable %s\n", dev->name, + ((cable & 0x08) ? "fixed" : "problem")); +#endif + if (media != lp->media_status) { + if ((media ^ lp->media_status) & 0x0004) + printk(KERN_INFO "%s: %s link beat\n", dev->name, + (lp->media_status & 0x0004) ? "lost" : "found"); + if ((media ^ lp->media_status) & 0x0020) { + lp->partner = 0; + if (lp->media_status & 0x0020) { + printk(KERN_INFO "%s: autonegotiation restarted\n", + dev->name); + } else if (partner) { + partner &= lp->advertising; + lp->partner = partner; + printk(KERN_INFO "%s: autonegotiation complete: " + "%sbaseT-%cD selected\n", dev->name, + ((partner & 0x0180) ? "100" : "10"), + ((partner & 0x0140) ? 'F' : 'H')); + } else { + printk(KERN_INFO "%s: link partner did not autonegotiate\n", + dev->name); + } + + EL3WINDOW(3); + outb((partner & 0x0140 ? 0x20 : 0) | + (dev->mtu > 1500 ? 0x40 : 0), ioaddr + Wn3_MAC_Ctrl); + EL3WINDOW(1); + + } + if (media & 0x0010) + printk(KERN_INFO "%s: remote fault detected\n", + dev->name); + if (media & 0x0002) + printk(KERN_INFO "%s: jabber detected\n", dev->name); + lp->media_status = media; + } + +reschedule: + lp->media.expires = jiffies + HZ; + add_timer(&lp->media); +} + +static struct net_device_stats *el3_get_stats(struct net_device *dev) +{ + struct el3_private *lp = (struct el3_private *)dev->priv; + + if (netif_device_present(dev)) + update_stats(dev); + return &lp->stats; +} + +/* Update statistics. + Suprisingly this need not be run single-threaded, but it effectively is. + The counters clear when read, so the adds must merely be atomic. + */ +static void update_stats(struct net_device *dev) +{ + struct el3_private *lp = (struct el3_private *)dev->priv; + ioaddr_t ioaddr = dev->base_addr; + u8 rx, tx, up; + + DEBUG(2, "%s: updating the statistics.\n", dev->name); + + if (inw(ioaddr+EL3_STATUS) == 0xffff) /* No card. */ + return; + + /* Unlike the 3c509 we need not turn off stats updates while reading. */ + /* Switch to the stats window, and read everything. */ + EL3WINDOW(6); + lp->stats.tx_carrier_errors += inb(ioaddr + 0); + lp->stats.tx_heartbeat_errors += inb(ioaddr + 1); + /* Multiple collisions. */ inb(ioaddr + 2); + lp->stats.collisions += inb(ioaddr + 3); + lp->stats.tx_window_errors += inb(ioaddr + 4); + lp->stats.rx_fifo_errors += inb(ioaddr + 5); + lp->stats.tx_packets += inb(ioaddr + 6); + up = inb(ioaddr + 9); + lp->stats.tx_packets += (up&0x30) << 4; + /* Rx packets */ inb(ioaddr + 7); + /* Tx deferrals */ inb(ioaddr + 8); + rx = inw(ioaddr + 10); + tx = inw(ioaddr + 12); + + EL3WINDOW(4); + /* BadSSD */ inb(ioaddr + 12); + up = inb(ioaddr + 13); + + add_tx_bytes(&lp->stats, tx + ((up & 0xf0) << 12)); + + EL3WINDOW(1); +} + +static int el3_rx(struct net_device *dev, int worklimit) +{ + struct el3_private *lp = (struct el3_private *)dev->priv; + ioaddr_t ioaddr = dev->base_addr; + short rx_status; + + DEBUG(3, "%s: in rx_packet(), status %4.4x, rx_status %4.4x.\n", + dev->name, inw(ioaddr+EL3_STATUS), inw(ioaddr+RxStatus)); + while (!((rx_status = inw(ioaddr + RxStatus)) & 0x8000) && + (--worklimit >= 0)) { + if (rx_status & 0x4000) { /* Error, update stats. */ + short error = rx_status & 0x3800; + lp->stats.rx_errors++; + switch (error) { + case 0x0000: lp->stats.rx_over_errors++; break; + case 0x0800: lp->stats.rx_length_errors++; break; + case 0x1000: lp->stats.rx_frame_errors++; break; + case 0x1800: lp->stats.rx_length_errors++; break; + case 0x2000: lp->stats.rx_frame_errors++; break; + case 0x2800: lp->stats.rx_crc_errors++; break; + } + } else { + short pkt_len = rx_status & 0x7ff; + struct sk_buff *skb; + + skb = dev_alloc_skb(pkt_len+5); + + DEBUG(3, " Receiving packet size %d status %4.4x.\n", + pkt_len, rx_status); + if (skb != NULL) { + skb->dev = dev; + skb_reserve(skb, 2); + insl(ioaddr+RX_FIFO, skb_put(skb, pkt_len), + ((pkt_len+3)>>2)); + skb->protocol = eth_type_trans(skb, dev); + netif_rx(skb); + dev->last_rx = jiffies; + lp->stats.rx_packets++; + add_rx_bytes(&lp->stats, pkt_len); + } else { + DEBUG(1, "%s: couldn't allocate a sk_buff of" + " size %d.\n", dev->name, pkt_len); + lp->stats.rx_dropped++; + } + } + tc574_wait_for_completion(dev, RxDiscard); + } + + return worklimit; +} + +/* Provide ioctl() calls to examine the MII xcvr state. */ +static int el3_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) +{ + struct el3_private *lp = (struct el3_private *)dev->priv; + ioaddr_t ioaddr = dev->base_addr; + u16 *data = (u16 *)&rq->ifr_data; + int phy = lp->phys & 0x1f; + + DEBUG(2, "%s: In ioct(%-.6s, %#4.4x) %4.4x %4.4x %4.4x %4.4x.\n", + dev->name, rq->ifr_ifrn.ifrn_name, cmd, + data[0], data[1], data[2], data[3]); + + switch(cmd) { + case SIOCDEVPRIVATE: /* Get the address of the PHY in use. */ + data[0] = phy; + case SIOCDEVPRIVATE+1: /* Read the specified MII register. */ + { + int saved_window; + long flags; + + save_flags(flags); + cli(); + saved_window = inw(ioaddr + EL3_CMD) >> 13; + EL3WINDOW(4); + data[3] = mdio_read(ioaddr, data[0] & 0x1f, data[1] & 0x1f); + EL3WINDOW(saved_window); + restore_flags(flags); + return 0; + } + case SIOCDEVPRIVATE+2: /* Write the specified MII register */ + { + int saved_window; + long flags; + + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + save_flags(flags); + cli(); + saved_window = inw(ioaddr + EL3_CMD) >> 13; + EL3WINDOW(4); + mdio_write(ioaddr, data[0] & 0x1f, data[1] & 0x1f, data[2]); + EL3WINDOW(saved_window); + restore_flags(flags); + return 0; + } + default: + return -EOPNOTSUPP; + } +} + +/* The Odie chip has a 64 bin multicast filter, but the bit layout is not + documented. Until it is we revert to receiving all multicast frames when + any multicast reception is desired. + Note: My other drivers emit a log message whenever promiscuous mode is + entered to help detect password sniffers. This is less desirable on + typical PC card machines, so we omit the message. + */ + +static void set_rx_mode(struct net_device *dev) +{ + ioaddr_t ioaddr = dev->base_addr; + + if (dev->flags & IFF_PROMISC) + outw(SetRxFilter | RxStation | RxMulticast | RxBroadcast | RxProm, + ioaddr + EL3_CMD); + else if (dev->mc_count || (dev->flags & IFF_ALLMULTI)) + outw(SetRxFilter|RxStation|RxMulticast|RxBroadcast, ioaddr + EL3_CMD); + else + outw(SetRxFilter | RxStation | RxBroadcast, ioaddr + EL3_CMD); +} + +static int el3_close(struct net_device *dev) +{ + ioaddr_t ioaddr = dev->base_addr; + struct el3_private *lp = dev->priv; + dev_link_t *link = &lp->link; + + DEBUG(2, "%s: shutting down ethercard.\n", dev->name); + + if (DEV_OK(link)) { + /* Turn off statistics ASAP. We update lp->stats below. */ + outw(StatsDisable, ioaddr + EL3_CMD); + + /* Disable the receiver and transmitter. */ + outw(RxDisable, ioaddr + EL3_CMD); + outw(TxDisable, ioaddr + EL3_CMD); + + /* Note: Switching to window 0 may disable the IRQ. */ + EL3WINDOW(0); + + update_stats(dev); + } + + link->open--; + netif_stop_queue(dev); + netif_mark_down(dev); + del_timer(&lp->media); + if (link->state & DEV_STALE_CONFIG) + mod_timer(&link->release, jiffies + HZ/20); + + MOD_DEC_USE_COUNT; + + return 0; +} + +static int __init init_3c574_cs(void) +{ + servinfo_t serv; + + DEBUG(0, "%s\n", version); + CardServices(GetCardServicesInfo, &serv); + if (serv.Revision != CS_RELEASE_CODE) { + printk(KERN_NOTICE "3c574_cs: Card Services release " + "does not match!\n"); + return -EINVAL; + } + register_pccard_driver(&dev_info, &tc574_attach, &tc574_detach); + return 0; +} + +static void __exit exit_3c574_cs(void) +{ + DEBUG(0, "3c574_cs: unloading\n"); + unregister_pccard_driver(&dev_info); + while (dev_list != NULL) + tc574_detach(dev_list); +} + +module_init(init_3c574_cs); +module_exit(exit_3c574_cs); + +/* + * Local variables: + * compile-command: "make 3c574_cs.o" + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 4 + * End: + */ diff --git a/linux/pcmcia-cs/clients/3c589_cs.c b/linux/pcmcia-cs/clients/3c589_cs.c new file mode 100644 index 0000000..9794b82 --- /dev/null +++ b/linux/pcmcia-cs/clients/3c589_cs.c @@ -0,0 +1,1107 @@ +/*====================================================================== + + A PCMCIA ethernet driver for the 3com 3c589 card. + + Copyright (C) 1999 David A. Hinds -- dahinds@users.sourceforge.net + + 3c589_cs.c 1.167 2003/08/25 15:57:40 + + The network driver code is based on Donald Becker's 3c589 code: + + Written 1994 by Donald Becker. + Copyright 1993 United States Government as represented by the + Director, National Security Agency. This software may be used and + distributed according to the terms of the GNU General Public License, + incorporated herein by reference. + Donald Becker may be reached at becker@scyld.com + +======================================================================*/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/ptrace.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/in.h> +#include <linux/delay.h> +#include <asm/io.h> +#include <asm/system.h> +#include <asm/bitops.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/if_arp.h> +#include <linux/ioport.h> + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/cisreg.h> +#include <pcmcia/ciscode.h> +#include <pcmcia/ds.h> + +/* To minimize the size of the driver source I only define operating + constants if they are used several times. You'll need the manual + if you want to understand driver details. */ +/* Offsets from base I/O address. */ +#define EL3_DATA 0x00 +#define EL3_TIMER 0x0a +#define EL3_CMD 0x0e +#define EL3_STATUS 0x0e + +#define EEPROM_READ 0x0080 +#define EEPROM_BUSY 0x8000 + +#define EL3WINDOW(win_num) outw(SelectWindow + (win_num), ioaddr + EL3_CMD) + +/* The top five bits written to EL3_CMD are a command, the lower + 11 bits are the parameter, if applicable. */ +enum c509cmd { + TotalReset = 0<<11, SelectWindow = 1<<11, StartCoax = 2<<11, + RxDisable = 3<<11, RxEnable = 4<<11, RxReset = 5<<11, RxDiscard = 8<<11, + TxEnable = 9<<11, TxDisable = 10<<11, TxReset = 11<<11, + FakeIntr = 12<<11, AckIntr = 13<<11, SetIntrEnb = 14<<11, + SetStatusEnb = 15<<11, SetRxFilter = 16<<11, SetRxThreshold = 17<<11, + SetTxThreshold = 18<<11, SetTxStart = 19<<11, StatsEnable = 21<<11, + StatsDisable = 22<<11, StopCoax = 23<<11, +}; + +enum c509status { + IntLatch = 0x0001, AdapterFailure = 0x0002, TxComplete = 0x0004, + TxAvailable = 0x0008, RxComplete = 0x0010, RxEarly = 0x0020, + IntReq = 0x0040, StatsFull = 0x0080, CmdBusy = 0x1000 +}; + +/* The SetRxFilter command accepts the following classes: */ +enum RxFilter { + RxStation = 1, RxMulticast = 2, RxBroadcast = 4, RxProm = 8 +}; + +/* Register window 1 offsets, the window used in normal operation. */ +#define TX_FIFO 0x00 +#define RX_FIFO 0x00 +#define RX_STATUS 0x08 +#define TX_STATUS 0x0B +#define TX_FREE 0x0C /* Remaining free bytes in Tx buffer. */ + +#define WN0_IRQ 0x08 /* Window 0: Set IRQ line in bits 12-15. */ +#define WN4_MEDIA 0x0A /* Window 4: Various transcvr/media bits. */ +#define MEDIA_TP 0x00C0 /* Enable link beat and jabber for 10baseT. */ +#define MEDIA_LED 0x0001 /* Enable link light on 3C589E cards. */ + +/* Time in jiffies before concluding Tx hung */ +#define TX_TIMEOUT ((400*HZ)/1000) + +struct el3_private { + dev_link_t link; + struct net_device dev; + dev_node_t node; + struct net_device_stats stats; + /* For transceiver monitoring */ + struct timer_list media; + u_short media_status; + u_short fast_poll; + u_long last_irq; +}; + +static char *if_names[] = { "auto", "10baseT", "10base2", "AUI" }; + +/*====================================================================*/ + +/* Module parameters */ + +MODULE_AUTHOR("David Hinds <dahinds@users.sourceforge.net>"); +MODULE_DESCRIPTION("3Com 3c589 series PCMCIA ethernet driver"); +MODULE_LICENSE("GPL"); + +#define INT_MODULE_PARM(n, v) static int n = v; MODULE_PARM(n, "i") + +/* Special hook for setting if_port when module is loaded */ +INT_MODULE_PARM(if_port, 0); + +/* Bit map of interrupts to choose from */ +INT_MODULE_PARM(irq_mask, 0xdeb8); +static int irq_list[4] = { -1 }; +MODULE_PARM(irq_list, "1-4i"); + +#ifdef PCMCIA_DEBUG +INT_MODULE_PARM(pc_debug, PCMCIA_DEBUG); +#define DEBUG(n, args...) if (pc_debug>(n)) printk(KERN_DEBUG args) +static char *version = +"3c589_cs.c 1.167 2003/08/25 15:57:40 (David Hinds)"; +#else +#define DEBUG(n, args...) +#endif + +/*====================================================================*/ + +static void tc589_config(dev_link_t *link); +static void tc589_release(u_long arg); +static int tc589_event(event_t event, int priority, + event_callback_args_t *args); + +static u_short read_eeprom(ioaddr_t ioaddr, int index); +static void tc589_reset(struct net_device *dev); +static void media_check(u_long arg); +static int el3_config(struct net_device *dev, struct ifmap *map); +static int el3_open(struct net_device *dev); +static int el3_start_xmit(struct sk_buff *skb, struct net_device *dev); +static void el3_interrupt(int irq, void *dev_id, struct pt_regs *regs); +static void update_stats(struct net_device *dev); +static struct net_device_stats *el3_get_stats(struct net_device *dev); +static int el3_rx(struct net_device *dev); +static int el3_close(struct net_device *dev); +static void el3_tx_timeout(struct net_device *dev); +static void set_multicast_list(struct net_device *dev); + +static dev_info_t dev_info = "3c589_cs"; + +static dev_link_t *tc589_attach(void); +static void tc589_detach(dev_link_t *); + +static dev_link_t *dev_list = NULL; + +/*====================================================================== + + This bit of code is used to avoid unregistering network devices + at inappropriate times. 2.2 and later kernels are fairly picky + about when this can happen. + +======================================================================*/ + +static void flush_stale_links(void) +{ + dev_link_t *link, *next; + for (link = dev_list; link; link = next) { + next = link->next; + if (link->state & DEV_STALE_LINK) + tc589_detach(link); + } +} + +/*====================================================================*/ + +static void cs_error(client_handle_t handle, int func, int ret) +{ + error_info_t err = { func, ret }; + CardServices(ReportError, handle, &err); +} + +/*====================================================================== + + tc589_attach() creates an "instance" of the driver, allocating + local data structures for one device. The device is registered + with Card Services. + +======================================================================*/ + +static dev_link_t *tc589_attach(void) +{ + struct el3_private *lp; + client_reg_t client_reg; + dev_link_t *link; + struct net_device *dev; + int i, ret; + + DEBUG(0, "3c589_attach()\n"); + flush_stale_links(); + + /* Create new ethernet device */ + lp = kmalloc(sizeof(*lp), GFP_KERNEL); + if (!lp) return NULL; + memset(lp, 0, sizeof(*lp)); + link = &lp->link; dev = &lp->dev; + link->priv = dev->priv = link->irq.Instance = lp; + + init_timer(&link->release); + link->release.function = &tc589_release; + link->release.data = (u_long)link; + link->io.NumPorts1 = 16; + link->io.Attributes1 = IO_DATA_PATH_WIDTH_16; + link->irq.Attributes = IRQ_TYPE_EXCLUSIVE | IRQ_HANDLE_PRESENT; + link->irq.IRQInfo1 = IRQ_INFO2_VALID|IRQ_LEVEL_ID; + if (irq_list[0] == -1) + link->irq.IRQInfo2 = irq_mask; + else + for (i = 0; i < 4; i++) + link->irq.IRQInfo2 |= 1 << irq_list[i]; + link->irq.Handler = &el3_interrupt; + 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; + + /* The EL3-specific entries in the device structure. */ + dev->hard_start_xmit = &el3_start_xmit; + dev->set_config = &el3_config; + dev->get_stats = &el3_get_stats; + dev->set_multicast_list = &set_multicast_list; + ether_setup(dev); + init_dev_name(dev, lp->node); + dev->open = &el3_open; + dev->stop = &el3_close; +#ifdef HAVE_TX_TIMEOUT + dev->tx_timeout = el3_tx_timeout; + dev->watchdog_timeo = TX_TIMEOUT; +#endif + + /* 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 = &tc589_event; + client_reg.Version = 0x0210; + client_reg.event_callback_args.client_data = link; + ret = CardServices(RegisterClient, &link->handle, &client_reg); + if (ret != 0) { + cs_error(link->handle, RegisterClient, ret); + tc589_detach(link); + return NULL; + } + + return link; +} /* tc589_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 tc589_detach(dev_link_t *link) +{ + struct el3_private *lp = link->priv; + dev_link_t **linkp; + + DEBUG(0, "3c589_detach(0x%p)\n", link); + + /* Locate device structure */ + for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next) + if (*linkp == link) break; + if (*linkp == NULL) + return; + + del_timer(&link->release); + if (link->state & DEV_CONFIG) { + tc589_release((u_long)link); + if (link->state & DEV_STALE_CONFIG) { + link->state |= DEV_STALE_LINK; + return; + } + } + + if (link->handle) + CardServices(DeregisterClient, link->handle); + + /* Unlink device structure, free bits */ + *linkp = link->next; + if (link->dev) + unregister_netdev(&lp->dev); + kfree(lp); + +} /* tc589_detach */ + +/*====================================================================== + + tc589_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. + +======================================================================*/ + +#define CS_CHECK(fn, args...) \ +while ((last_ret=CardServices(last_fn=(fn), args))!=0) goto cs_failed + +static void tc589_config(dev_link_t *link) +{ + client_handle_t handle = link->handle; + struct el3_private *lp = link->priv; + struct net_device *dev = &lp->dev; + tuple_t tuple; + cisparse_t parse; + u_short buf[32], *phys_addr; + int last_fn, last_ret, i, j, multi = 0; + ioaddr_t ioaddr; + char *ram_split[] = {"5:3", "3:1", "1:1", "3:5"}; + + DEBUG(0, "3c589_config(0x%p)\n", link); + + phys_addr = (u_short *)dev->dev_addr; + tuple.Attributes = 0; + tuple.DesiredTuple = CISTPL_CONFIG; + CS_CHECK(GetFirstTuple, handle, &tuple); + tuple.TupleData = (cisdata_t *)buf; + tuple.TupleDataMax = sizeof(buf); + tuple.TupleOffset = 0; + CS_CHECK(GetTupleData, handle, &tuple); + CS_CHECK(ParseTuple, handle, &tuple, &parse); + link->conf.ConfigBase = parse.config.base; + link->conf.Present = parse.config.rmask[0]; + + /* Is this a 3c562? */ + tuple.DesiredTuple = CISTPL_MANFID; + tuple.Attributes = TUPLE_RETURN_COMMON; + if ((CardServices(GetFirstTuple, handle, &tuple) == CS_SUCCESS) && + (CardServices(GetTupleData, handle, &tuple) == CS_SUCCESS)) { + if (le16_to_cpu(buf[0]) != MANFID_3COM) + printk(KERN_INFO "3c589_cs: hmmm, is this really a " + "3Com card??\n"); + multi = (le16_to_cpu(buf[1]) == PRODID_3COM_3C562); + } + + /* Configure card */ + link->state |= DEV_CONFIG; + + /* For the 3c562, the base address must be xx00-xx7f */ + link->io.IOAddrLines = 16; + for (i = j = 0; j < 0x400; j += 0x10) { + if (multi && (j & 0x80)) continue; + link->io.BasePort1 = j ^ 0x300; + i = CardServices(RequestIO, link->handle, &link->io); + if (i == CS_SUCCESS) break; + } + if (i != CS_SUCCESS) { + cs_error(link->handle, RequestIO, i); + goto failed; + } + CS_CHECK(RequestIRQ, link->handle, &link->irq); + CS_CHECK(RequestConfiguration, link->handle, &link->conf); + + dev->irq = link->irq.AssignedIRQ; + dev->base_addr = link->io.BasePort1; + if (register_netdev(dev) != 0) { + printk(KERN_NOTICE "3c589_cs: register_netdev() failed\n"); + goto failed; + } + + ioaddr = dev->base_addr; + EL3WINDOW(0); + + /* The 3c589 has an extra EEPROM for configuration info, including + the hardware address. The 3c562 puts the address in the CIS. */ + tuple.DesiredTuple = 0x88; + if (CardServices(GetFirstTuple, handle, &tuple) == CS_SUCCESS) { + CardServices(GetTupleData, handle, &tuple); + for (i = 0; i < 3; i++) + phys_addr[i] = htons(buf[i]); + } else { + for (i = 0; i < 3; i++) + phys_addr[i] = htons(read_eeprom(ioaddr, i)); + if (phys_addr[0] == 0x6060) { + printk(KERN_NOTICE "3c589_cs: IO port conflict at 0x%03lx" + "-0x%03lx\n", dev->base_addr, dev->base_addr+15); + goto failed; + } + } + + copy_dev_name(lp->node, dev); + link->dev = &lp->node; + + /* The address and resource configuration register aren't loaded from + the EEPROM and *must* be set to 0 and IRQ3 for the PCMCIA version. */ + outw(0x3f00, ioaddr + 8); + + /* The if_port symbol can be set when the module is loaded */ + if ((if_port >= 0) && (if_port <= 3)) + dev->if_port = if_port; + else + printk(KERN_NOTICE "3c589_cs: invalid if_port requested\n"); + + printk(KERN_INFO "%s: 3Com 3c%s, io %#3lx, irq %d, hw_addr ", + dev->name, (multi ? "562" : "589"), dev->base_addr, + dev->irq); + for (i = 0; i < 6; i++) + printk("%02X%s", dev->dev_addr[i], ((i<5) ? ":" : "\n")); + i = inl(ioaddr); + printk(KERN_INFO " %dK FIFO split %s Rx:Tx, %s xcvr\n", + (i & 7) ? 32 : 8, ram_split[(i >> 16) & 3], + if_names[dev->if_port]); + link->state &= ~DEV_CONFIG_PENDING; + return; + +cs_failed: + cs_error(link->handle, last_fn, last_ret); +failed: + tc589_release((u_long)link); + link->state &= ~DEV_CONFIG_PENDING; + return; + +} /* tc589_config */ + +/*====================================================================== + + After a card is removed, tc589_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 tc589_release(u_long arg) +{ + dev_link_t *link = (dev_link_t *)arg; + + DEBUG(0, "3c589_release(0x%p)\n", link); + + if (link->open) { + DEBUG(1, "3c589_cs: release postponed, '%s' still open\n", + link->dev->dev_name); + link->state |= DEV_STALE_CONFIG; + return; + } + + CardServices(ReleaseConfiguration, link->handle); + CardServices(ReleaseIO, link->handle, &link->io); + CardServices(ReleaseIRQ, link->handle, &link->irq); + + link->state &= ~DEV_CONFIG; + +} /* tc589_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. + +======================================================================*/ + +static int tc589_event(event_t event, int priority, + event_callback_args_t *args) +{ + dev_link_t *link = args->client_data; + struct el3_private *lp = link->priv; + struct net_device *dev = &lp->dev; + + DEBUG(1, "3c589_event(0x%06x)\n", event); + + switch (event) { + case CS_EVENT_CARD_REMOVAL: + link->state &= ~DEV_PRESENT; + if (link->state & DEV_CONFIG) { + netif_device_detach(dev); + mod_timer(&link->release, jiffies + HZ/20); + } + break; + case CS_EVENT_CARD_INSERTION: + link->state |= DEV_PRESENT | DEV_CONFIG_PENDING; + tc589_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) + netif_device_detach(dev); + 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) { + tc589_reset(dev); + netif_device_attach(dev); + } + } + break; + } + return 0; +} /* tc589_event */ + +/*====================================================================*/ + +/* + Use this for commands that may take time to finish +*/ +static void tc589_wait_for_completion(struct net_device *dev, int cmd) +{ + int i = 100; + outw(cmd, dev->base_addr + EL3_CMD); + while (--i > 0) + if (!(inw(dev->base_addr + EL3_STATUS) & 0x1000)) break; + if (i == 0) + printk(KERN_NOTICE "%s: command 0x%04x did not complete!\n", + dev->name, cmd); +} + +/* + Read a word from the EEPROM using the regular EEPROM access register. + Assume that we are in register window zero. +*/ +static u_short read_eeprom(ioaddr_t ioaddr, int index) +{ + int i; + outw(EEPROM_READ + index, ioaddr + 10); + /* Reading the eeprom takes 162 us */ + for (i = 1620; i >= 0; i--) + if ((inw(ioaddr + 10) & EEPROM_BUSY) == 0) + break; + return inw(ioaddr + 12); +} + +/* + Set transceiver type, perhaps to something other than what the user + specified in dev->if_port. +*/ +static void tc589_set_xcvr(struct net_device *dev, int if_port) +{ + struct el3_private *lp = (struct el3_private *)dev->priv; + ioaddr_t ioaddr = dev->base_addr; + + EL3WINDOW(0); + switch (if_port) { + case 0: case 1: outw(0, ioaddr + 6); break; + case 2: outw(3<<14, ioaddr + 6); break; + case 3: outw(1<<14, ioaddr + 6); break; + } + /* On PCMCIA, this just turns on the LED */ + outw((if_port == 2) ? StartCoax : StopCoax, ioaddr + EL3_CMD); + /* 10baseT interface, enable link beat and jabber check. */ + EL3WINDOW(4); + outw(MEDIA_LED | ((if_port < 2) ? MEDIA_TP : 0), ioaddr + WN4_MEDIA); + EL3WINDOW(1); + if (if_port == 2) + lp->media_status = ((dev->if_port == 0) ? 0x8000 : 0x4000); + else + lp->media_status = ((dev->if_port == 0) ? 0x4010 : 0x8800); +} + +static void dump_status(struct net_device *dev) +{ + ioaddr_t ioaddr = dev->base_addr; + EL3WINDOW(1); + printk(KERN_INFO " irq status %04x, rx status %04x, tx status " + "%02x tx free %04x\n", inw(ioaddr+EL3_STATUS), + inw(ioaddr+RX_STATUS), inb(ioaddr+TX_STATUS), + inw(ioaddr+TX_FREE)); + EL3WINDOW(4); + printk(KERN_INFO " diagnostics: fifo %04x net %04x ethernet %04x" + " media %04x\n", inw(ioaddr+0x04), inw(ioaddr+0x06), + inw(ioaddr+0x08), inw(ioaddr+0x0a)); + EL3WINDOW(1); +} + +/* Reset and restore all of the 3c589 registers. */ +static void tc589_reset(struct net_device *dev) +{ + ioaddr_t ioaddr = dev->base_addr; + int i; + + EL3WINDOW(0); + outw(0x0001, ioaddr + 4); /* Activate board. */ + outw(0x3f00, ioaddr + 8); /* Set the IRQ line. */ + + /* Set the station address in window 2. */ + EL3WINDOW(2); + for (i = 0; i < 6; i++) + outb(dev->dev_addr[i], ioaddr + i); + + tc589_set_xcvr(dev, dev->if_port); + + /* Switch to the stats window, and clear all stats by reading. */ + outw(StatsDisable, ioaddr + EL3_CMD); + EL3WINDOW(6); + for (i = 0; i < 9; i++) + inb(ioaddr+i); + inw(ioaddr + 10); + inw(ioaddr + 12); + + /* Switch to register set 1 for normal use. */ + EL3WINDOW(1); + + /* Accept b-cast and phys addr only. */ + outw(SetRxFilter | RxStation | RxBroadcast, ioaddr + EL3_CMD); + outw(StatsEnable, ioaddr + EL3_CMD); /* Turn on statistics. */ + outw(RxEnable, ioaddr + EL3_CMD); /* Enable the receiver. */ + outw(TxEnable, ioaddr + EL3_CMD); /* Enable transmitter. */ + /* Allow status bits to be seen. */ + outw(SetStatusEnb | 0xff, ioaddr + EL3_CMD); + /* Ack all pending events, and set active indicator mask. */ + outw(AckIntr | IntLatch | TxAvailable | RxEarly | IntReq, + ioaddr + EL3_CMD); + outw(SetIntrEnb | IntLatch | TxAvailable | RxComplete | StatsFull + | AdapterFailure, ioaddr + EL3_CMD); +} + +static int el3_config(struct net_device *dev, struct ifmap *map) +{ + if ((map->port != (u_char)(-1)) && (map->port != dev->if_port)) { + if (map->port <= 3) { + dev->if_port = map->port; + printk(KERN_INFO "%s: switched to %s port\n", + dev->name, if_names[dev->if_port]); + tc589_set_xcvr(dev, dev->if_port); + } else + return -EINVAL; + } + return 0; +} + +static int el3_open(struct net_device *dev) +{ + struct el3_private *lp = (struct el3_private *)dev->priv; + dev_link_t *link = &lp->link; + + if (!DEV_OK(link)) + return -ENODEV; + + link->open++; + MOD_INC_USE_COUNT; + netif_start_queue(dev); + netif_mark_up(dev); + + tc589_reset(dev); + lp->media.function = &media_check; + lp->media.data = (u_long)lp; + lp->media.expires = jiffies + HZ; + add_timer(&lp->media); + + DEBUG(1, "%s: opened, status %4.4x.\n", + dev->name, inw(dev->base_addr + EL3_STATUS)); + + return 0; +} + +static void el3_tx_timeout(struct net_device *dev) +{ + struct el3_private *lp = (struct el3_private *)dev->priv; + ioaddr_t ioaddr = dev->base_addr; + + printk(KERN_NOTICE "%s: Transmit timed out!\n", dev->name); + dump_status(dev); + lp->stats.tx_errors++; + dev->trans_start = jiffies; + /* Issue TX_RESET and TX_START commands. */ + tc589_wait_for_completion(dev, TxReset); + outw(TxEnable, ioaddr + EL3_CMD); + netif_wake_queue(dev); +} + +static void pop_tx_status(struct net_device *dev) +{ + struct el3_private *lp = (struct el3_private *)dev->priv; + ioaddr_t ioaddr = dev->base_addr; + int i; + + /* Clear the Tx status stack. */ + for (i = 32; i > 0; i--) { + u_char tx_status = inb(ioaddr + TX_STATUS); + if (!(tx_status & 0x84)) break; + /* reset transmitter on jabber error or underrun */ + if (tx_status & 0x30) + tc589_wait_for_completion(dev, TxReset); + if (tx_status & 0x38) { + DEBUG(1, "%s: transmit error: status 0x%02x\n", + dev->name, tx_status); + outw(TxEnable, ioaddr + EL3_CMD); + lp->stats.tx_aborted_errors++; + } + outb(0x00, ioaddr + TX_STATUS); /* Pop the status stack. */ + } +} + +static int el3_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + ioaddr_t ioaddr = dev->base_addr; + + tx_timeout_check(dev, el3_tx_timeout); + skb_tx_check(dev, skb); + + DEBUG(3, "%s: el3_start_xmit(length = %ld) called, " + "status %4.4x.\n", dev->name, (long)skb->len, + inw(ioaddr + EL3_STATUS)); + + add_tx_bytes(&((struct el3_private *)dev->priv)->stats, skb->len); + + /* Put out the doubleword header... */ + outw(skb->len, ioaddr + TX_FIFO); + outw(0x00, ioaddr + TX_FIFO); + /* ... and the packet rounded to a doubleword. */ + outsl(ioaddr + TX_FIFO, skb->data, (skb->len + 3) >> 2); + + dev->trans_start = jiffies; + if (inw(ioaddr + TX_FREE) > 1536) { + netif_start_queue(dev); + } else + /* Interrupt us when the FIFO has room for max-sized packet. */ + outw(SetTxThreshold + 1536, ioaddr + EL3_CMD); + + DEV_KFREE_SKB(skb); + pop_tx_status(dev); + + return 0; +} + +/* The EL3 interrupt handler. */ +static void el3_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct el3_private *lp = dev_id; + struct net_device *dev = &lp->dev; + ioaddr_t ioaddr, status; + int i = 0; + + if (!netif_device_present(dev)) + return; + ioaddr = dev->base_addr; + + DEBUG(3, "%s: interrupt, status %4.4x.\n", + dev->name, inw(ioaddr + EL3_STATUS)); + + while ((status = inw(ioaddr + EL3_STATUS)) & + (IntLatch | RxComplete | StatsFull)) { + if (!netif_device_present(dev) || + ((status & 0xe000) != 0x2000)) { + DEBUG(1, "%s: interrupt from dead card\n", dev->name); + break; + } + + if (status & RxComplete) + el3_rx(dev); + + if (status & TxAvailable) { + DEBUG(3, " TX room bit was handled.\n"); + /* There's room in the FIFO for a full-sized packet. */ + outw(AckIntr | TxAvailable, ioaddr + EL3_CMD); + netif_wake_queue(dev); + } + + if (status & TxComplete) + pop_tx_status(dev); + + if (status & (AdapterFailure | RxEarly | StatsFull)) { + /* Handle all uncommon interrupts. */ + if (status & StatsFull) /* Empty statistics. */ + update_stats(dev); + if (status & RxEarly) { /* Rx early is unused. */ + el3_rx(dev); + outw(AckIntr | RxEarly, ioaddr + EL3_CMD); + } + if (status & AdapterFailure) { + u16 fifo_diag; + EL3WINDOW(4); + fifo_diag = inw(ioaddr + 4); + EL3WINDOW(1); + printk(KERN_NOTICE "%s: adapter failure, FIFO diagnostic" + " register %04x.\n", dev->name, fifo_diag); + if (fifo_diag & 0x0400) { + /* Tx overrun */ + tc589_wait_for_completion(dev, TxReset); + outw(TxEnable, ioaddr + EL3_CMD); + } + if (fifo_diag & 0x2000) { + /* Rx underrun */ + tc589_wait_for_completion(dev, RxReset); + set_multicast_list(dev); + outw(RxEnable, ioaddr + EL3_CMD); + } + outw(AckIntr | AdapterFailure, ioaddr + EL3_CMD); + } + } + + if (++i > 10) { + printk(KERN_NOTICE "%s: infinite loop in interrupt, " + "status %4.4x.\n", dev->name, status); + /* Clear all interrupts */ + outw(AckIntr | 0xFF, ioaddr + EL3_CMD); + break; + } + /* Acknowledge the IRQ. */ + outw(AckIntr | IntReq | IntLatch, ioaddr + EL3_CMD); + } + + lp->last_irq = jiffies; + DEBUG(3, "%s: exiting interrupt, status %4.4x.\n", + dev->name, inw(ioaddr + EL3_STATUS)); + return; +} + +static void media_check(u_long arg) +{ + struct el3_private *lp = (struct el3_private *)(arg); + struct net_device *dev = &lp->dev; + ioaddr_t ioaddr = dev->base_addr; + u_short media, errs; + u_long flags; + + if (!netif_device_present(dev)) goto reschedule; + + EL3WINDOW(1); + /* Check for pending interrupt with expired latency timer: with + this, we can limp along even if the interrupt is blocked */ + if ((inw(ioaddr + EL3_STATUS) & IntLatch) && + (inb(ioaddr + EL3_TIMER) == 0xff)) { + if (!lp->fast_poll) + printk(KERN_INFO "%s: interrupt(s) dropped!\n", dev->name); + el3_interrupt(dev->irq, lp, NULL); + lp->fast_poll = HZ; + } + if (lp->fast_poll) { + lp->fast_poll--; + lp->media.expires = jiffies + 1; + add_timer(&lp->media); + return; + } + + save_flags(flags); + cli(); + EL3WINDOW(4); + media = inw(ioaddr+WN4_MEDIA) & 0xc810; + + /* Ignore collisions unless we've had no irq's recently */ + if (jiffies - lp->last_irq < HZ) { + media &= ~0x0010; + } else { + /* Try harder to detect carrier errors */ + EL3WINDOW(6); + outw(StatsDisable, ioaddr + EL3_CMD); + errs = inb(ioaddr + 0); + outw(StatsEnable, ioaddr + EL3_CMD); + lp->stats.tx_carrier_errors += errs; + if (errs || (lp->media_status & 0x0010)) media |= 0x0010; + } + + if (media != lp->media_status) { + if ((media & lp->media_status & 0x8000) && + ((lp->media_status ^ media) & 0x0800)) + printk(KERN_INFO "%s: %s link beat\n", dev->name, + (lp->media_status & 0x0800 ? "lost" : "found")); + else if ((media & lp->media_status & 0x4000) && + ((lp->media_status ^ media) & 0x0010)) + printk(KERN_INFO "%s: coax cable %s\n", dev->name, + (lp->media_status & 0x0010 ? "ok" : "problem")); + if (dev->if_port == 0) { + if (media & 0x8000) { + if (media & 0x0800) + printk(KERN_INFO "%s: flipped to 10baseT\n", + dev->name); + else + tc589_set_xcvr(dev, 2); + } else if (media & 0x4000) { + if (media & 0x0010) + tc589_set_xcvr(dev, 1); + else + printk(KERN_INFO "%s: flipped to 10base2\n", + dev->name); + } + } + lp->media_status = media; + } + + EL3WINDOW(1); + restore_flags(flags); + +reschedule: + lp->media.expires = jiffies + HZ; + add_timer(&lp->media); +} + +static struct net_device_stats *el3_get_stats(struct net_device *dev) +{ + struct el3_private *lp = (struct el3_private *)dev->priv; + unsigned long flags; + dev_link_t *link = &lp->link; + + if (DEV_OK(link)) { + save_flags(flags); + cli(); + update_stats(dev); + restore_flags(flags); + } + return &lp->stats; +} + +/* + Update statistics. We change to register window 6, so this should be run + single-threaded if the device is active. This is expected to be a rare + operation, and it's simpler for the rest of the driver to assume that + window 1 is always valid rather than use a special window-state variable. +*/ +static void update_stats(struct net_device *dev) +{ + struct el3_private *lp = (struct el3_private *)dev->priv; + ioaddr_t ioaddr = dev->base_addr; + + DEBUG(2, "%s: updating the statistics.\n", dev->name); + /* Turn off statistics updates while reading. */ + outw(StatsDisable, ioaddr + EL3_CMD); + /* Switch to the stats window, and read everything. */ + EL3WINDOW(6); + lp->stats.tx_carrier_errors += inb(ioaddr + 0); + lp->stats.tx_heartbeat_errors += inb(ioaddr + 1); + /* Multiple collisions. */ inb(ioaddr + 2); + lp->stats.collisions += inb(ioaddr + 3); + lp->stats.tx_window_errors += inb(ioaddr + 4); + lp->stats.rx_fifo_errors += inb(ioaddr + 5); + lp->stats.tx_packets += inb(ioaddr + 6); + /* Rx packets */ inb(ioaddr + 7); + /* Tx deferrals */ inb(ioaddr + 8); + /* Rx octets */ inw(ioaddr + 10); + /* Tx octets */ inw(ioaddr + 12); + + /* Back to window 1, and turn statistics back on. */ + EL3WINDOW(1); + outw(StatsEnable, ioaddr + EL3_CMD); +} + +static int el3_rx(struct net_device *dev) +{ + struct el3_private *lp = (struct el3_private *)dev->priv; + ioaddr_t ioaddr = dev->base_addr; + int worklimit = 32; + short rx_status; + + DEBUG(3, "%s: in rx_packet(), status %4.4x, rx_status %4.4x.\n", + dev->name, inw(ioaddr+EL3_STATUS), inw(ioaddr+RX_STATUS)); + while (!((rx_status = inw(ioaddr + RX_STATUS)) & 0x8000) && + (--worklimit >= 0)) { + if (rx_status & 0x4000) { /* Error, update stats. */ + short error = rx_status & 0x3800; + lp->stats.rx_errors++; + switch (error) { + case 0x0000: lp->stats.rx_over_errors++; break; + case 0x0800: lp->stats.rx_length_errors++; break; + case 0x1000: lp->stats.rx_frame_errors++; break; + case 0x1800: lp->stats.rx_length_errors++; break; + case 0x2000: lp->stats.rx_frame_errors++; break; + case 0x2800: lp->stats.rx_crc_errors++; break; + } + } else { + short pkt_len = rx_status & 0x7ff; + struct sk_buff *skb; + + skb = dev_alloc_skb(pkt_len+5); + + DEBUG(3, " Receiving packet size %d status %4.4x.\n", + pkt_len, rx_status); + if (skb != NULL) { + skb->dev = dev; + skb_reserve(skb, 2); + insl(ioaddr+RX_FIFO, skb_put(skb, pkt_len), + (pkt_len+3)>>2); + skb->protocol = eth_type_trans(skb, dev); + netif_rx(skb); + dev->last_rx = jiffies; + lp->stats.rx_packets++; + add_rx_bytes(&lp->stats, pkt_len); + } else { + DEBUG(1, "%s: couldn't allocate a sk_buff of" + " size %d.\n", dev->name, pkt_len); + lp->stats.rx_dropped++; + } + } + /* Pop the top of the Rx FIFO */ + tc589_wait_for_completion(dev, RxDiscard); + } + if (worklimit == 0) + printk(KERN_NOTICE "%s: too much work in el3_rx!\n", dev->name); + return 0; +} + +static void set_multicast_list(struct net_device *dev) +{ + struct el3_private *lp = dev->priv; + dev_link_t *link = &lp->link; + ioaddr_t ioaddr = dev->base_addr; + u_short opts = SetRxFilter | RxStation | RxBroadcast; + + if (!(DEV_OK(link))) return; + if (dev->flags & IFF_PROMISC) + opts |= RxMulticast | RxProm; + else if (dev->mc_count || (dev->flags & IFF_ALLMULTI)) + opts |= RxMulticast; + outw(opts, ioaddr + EL3_CMD); +} + +static int el3_close(struct net_device *dev) +{ + struct el3_private *lp = dev->priv; + dev_link_t *link = &lp->link; + ioaddr_t ioaddr = dev->base_addr; + + DEBUG(1, "%s: shutting down ethercard.\n", dev->name); + + if (DEV_OK(link)) { + /* Turn off statistics ASAP. We update lp->stats below. */ + outw(StatsDisable, ioaddr + EL3_CMD); + + /* Disable the receiver and transmitter. */ + outw(RxDisable, ioaddr + EL3_CMD); + outw(TxDisable, ioaddr + EL3_CMD); + + if (dev->if_port == 2) + /* Turn off thinnet power. Green! */ + outw(StopCoax, ioaddr + EL3_CMD); + else if (dev->if_port == 1) { + /* Disable link beat and jabber */ + EL3WINDOW(4); + outw(0, ioaddr + WN4_MEDIA); + } + + /* Switching back to window 0 disables the IRQ. */ + EL3WINDOW(0); + /* But we explicitly zero the IRQ line select anyway. */ + outw(0x0f00, ioaddr + WN0_IRQ); + + /* Check if the card still exists */ + if ((inw(ioaddr+EL3_STATUS) & 0xe000) == 0x2000) + update_stats(dev); + } + + link->open--; + netif_stop_queue(dev); + netif_mark_down(dev); + del_timer(&lp->media); + if (link->state & DEV_STALE_CONFIG) + mod_timer(&link->release, jiffies + HZ/20); + + MOD_DEC_USE_COUNT; + + return 0; +} + +/*====================================================================*/ + +static int __init init_3c589_cs(void) +{ + servinfo_t serv; + DEBUG(0, "%s\n", version); + CardServices(GetCardServicesInfo, &serv); + if (serv.Revision != CS_RELEASE_CODE) { + printk(KERN_NOTICE "3c589_cs: Card Services release " + "does not match!\n"); + return -EINVAL; + } + register_pccard_driver(&dev_info, &tc589_attach, &tc589_detach); + return 0; +} + +static void __exit exit_3c589_cs(void) +{ + DEBUG(0, "3c589_cs: unloading\n"); + unregister_pccard_driver(&dev_info); + while (dev_list != NULL) + tc589_detach(dev_list); +} + +module_init(init_3c589_cs); +module_exit(exit_3c589_cs); diff --git a/linux/pcmcia-cs/clients/ax8390.h b/linux/pcmcia-cs/clients/ax8390.h new file mode 100644 index 0000000..8be1197 --- /dev/null +++ b/linux/pcmcia-cs/clients/ax8390.h @@ -0,0 +1,165 @@ +/* Generic NS8390 register definitions. */ +/* This file is part of Donald Becker's 8390 drivers, and is distributed + under the same license. Auto-loading of 8390.o only in v2.2 - Paul G. + Some of these names and comments originated from the Crynwr + packet drivers, which are distributed under the GPL. */ + +#ifndef _8390_h +#define _8390_h + +#include <linux/config.h> +#include <linux/if_ether.h> +#include <linux/ioport.h> +#include <linux/skbuff.h> + +#define TX_2X_PAGES 12 +#define TX_1X_PAGES 6 + +#define TX_PAGES TX_2X_PAGES + +#define ETHER_ADDR_LEN 6 + +/* The 8390 specific per-packet-header format. */ +struct e8390_pkt_hdr { + unsigned char status; /* status */ + unsigned char next; /* pointer to next packet. */ + unsigned short count; /* header + packet length in bytes */ +}; + +#ifdef notdef +extern int ei_debug; +#else +#define ei_debug 1 +#endif + +#ifndef HAVE_AUTOIRQ +/* From auto_irq.c */ +extern void autoirq_setup(int waittime); +extern unsigned long autoirq_report(int waittime); +#endif + +/* Most of these entries should be in 'struct net_device' (or most of the + things in there should be here!) */ +/* You have one of these per-board */ +struct ei_device { + const char *name; + void (*reset_8390)(struct net_device *); + void (*get_8390_hdr)(struct net_device *, struct e8390_pkt_hdr *, int); + void (*block_output)(struct net_device *, int, const unsigned char *, int); + void (*block_input)(struct net_device *, int, struct sk_buff *, int); + unsigned char mcfilter[8]; + unsigned open:1; + unsigned word16:1; /* We have the 16-bit (vs 8-bit) version of the card. */ + unsigned txing:1; /* Transmit Active */ + unsigned irqlock:1; /* 8390's intrs disabled when '1'. */ + unsigned dmaing:1; /* Remote DMA Active */ + unsigned char tx_start_page, rx_start_page, stop_page; + unsigned char current_page; /* Read pointer in buffer */ + unsigned char interface_num; /* Net port (AUI, 10bT.) to use. */ + unsigned char txqueue; /* Tx Packet buffer queue length. */ + short tx1, tx2; /* Packet lengths for ping-pong tx. */ + short lasttx; /* Alpha version consistency check. */ + unsigned char saved_irq; /* Original dev->irq value. */ + struct net_device_stats stat; /* The new statistics table. */ + spinlock_t page_lock; /* Page register locks */ + unsigned long priv; /* Private field to store bus IDs etc. */ +}; + +/* The maximum number of 8390 interrupt service routines called per IRQ. */ +#define MAX_SERVICE 12 + +/* The maximum time waited (in jiffies) before assuming a Tx failed. (20ms) */ +#define TX_TIMEOUT (20*HZ/100) + +#define ei_status (*(struct ei_device *)(dev->priv)) + +/* Some generic ethernet register configurations. */ +#define E8390_TX_IRQ_MASK 0xa /* For register EN0_ISR */ +#define E8390_RX_IRQ_MASK 0x5 +#define E8390_RXCONFIG 0x4 /* EN0_RXCR: broadcasts, no multicast,errors */ +#define E8390_RXOFF 0x20 /* EN0_RXCR: Accept no packets */ +#define E8390_TXCONFIG 0x00 /* EN0_TXCR: Normal transmit mode */ +#define E8390_TXOFF 0x02 /* EN0_TXCR: Transmitter off */ + +/* Register accessed at EN_CMD, the 8390 base addr. */ +#define E8390_STOP 0x01 /* Stop and reset the chip */ +#define E8390_START 0x02 /* Start the chip, clear reset */ +#define E8390_TRANS 0x04 /* Transmit a frame */ +#define E8390_RREAD 0x08 /* Remote read */ +#define E8390_RWRITE 0x10 /* Remote write */ +#define E8390_NODMA 0x20 /* Remote DMA */ +#define E8390_PAGE0 0x00 /* Select page chip registers */ +#define E8390_PAGE1 0x40 /* using the two high-order bits */ +#define E8390_PAGE2 0x80 /* Page 3 is invalid. */ + +#define E8390_CMD 0x00 /* The command register (for all pages) */ +/* Page 0 register offsets. */ +#define EN0_CLDALO 0x01 /* Low byte of current local dma addr RD */ +#define EN0_STARTPG 0x01 /* Starting page of ring bfr WR */ +#define EN0_CLDAHI 0x02 /* High byte of current local dma addr RD */ +#define EN0_STOPPG 0x02 /* Ending page +1 of ring bfr WR */ +#define EN0_BOUNDARY 0x03 /* Boundary page of ring bfr RD WR */ +#define EN0_TSR 0x04 /* Transmit status reg RD */ +#define EN0_TPSR 0x04 /* Transmit starting page WR */ +#define EN0_NCR 0x05 /* Number of collision reg RD */ +#define EN0_TCNTLO 0x05 /* Low byte of tx byte count WR */ +#define EN0_FIFO 0x06 /* FIFO RD */ +#define EN0_TCNTHI 0x06 /* High byte of tx byte count WR */ +#define EN0_ISR 0x07 /* Interrupt status reg RD WR */ +#define EN0_CRDALO 0x08 /* low byte of current remote dma address RD */ +#define EN0_RSARLO 0x08 /* Remote start address reg 0 */ +#define EN0_CRDAHI 0x09 /* high byte, current remote dma address RD */ +#define EN0_RSARHI 0x09 /* Remote start address reg 1 */ +#define EN0_RCNTLO 0x0a /* Remote byte count reg WR */ +#define EN0_RCNTHI 0x0b /* Remote byte count reg WR */ +#define EN0_RSR 0x0c /* rx status reg RD */ +#define EN0_RXCR 0x0c /* RX configuration reg WR */ +#define EN0_TXCR 0x0d /* TX configuration reg WR */ +#define EN0_COUNTER0 0x0d /* Rcv alignment error counter RD */ +#define EN0_DCFG 0x0e /* Data configuration reg WR */ +#define EN0_COUNTER1 0x0e /* Rcv CRC error counter RD */ +#define EN0_IMR 0x0f /* Interrupt mask reg WR */ +#define EN0_COUNTER2 0x0f /* Rcv missed frame error counter RD */ + +/* Bits in EN0_ISR - Interrupt status register */ +#define ENISR_RX 0x01 /* Receiver, no error */ +#define ENISR_TX 0x02 /* Transmitter, no error */ +#define ENISR_RX_ERR 0x04 /* Receiver, with error */ +#define ENISR_TX_ERR 0x08 /* Transmitter, with error */ +#define ENISR_OVER 0x10 /* Receiver overwrote the ring */ +#define ENISR_COUNTERS 0x20 /* Counters need emptying */ +#define ENISR_RDC 0x40 /* remote dma complete */ +#define ENISR_RESET 0x80 /* Reset completed */ +#define ENISR_ALL 0x3f /* Interrupts we will enable */ + +/* Bits in EN0_DCFG - Data config register */ +#define ENDCFG_WTS 0x01 /* word transfer mode selection */ + +/* Page 1 register offsets. */ +#define EN1_PHYS 0x01 /* This board's physical enet addr RD WR */ +#define EN1_PHYS_SHIFT(i) (i+1) /* Get and set mac address */ +#define EN1_CURPAG 0x07 /* Current memory page RD WR */ +#define EN1_MULT 0x08 /* Multicast filter mask array (8 bytes) RD WR */ +#define EN1_MULT_SHIFT(i) (8+i) /* Get and set multicast filter */ + +/* Bits in received packet status byte and EN0_RSR*/ +#define ENRSR_RXOK 0x01 /* Received a good packet */ +#define ENRSR_CRC 0x02 /* CRC error */ +#define ENRSR_FAE 0x04 /* frame alignment error */ +#define ENRSR_FO 0x08 /* FIFO overrun */ +#define ENRSR_MPA 0x10 /* missed pkt */ +#define ENRSR_PHY 0x20 /* physical/multicast address */ +#define ENRSR_DIS 0x40 /* receiver disable. set in monitor mode */ +#define ENRSR_DEF 0x80 /* deferring */ + +/* Transmitted packet status, EN0_TSR. */ +#define ENTSR_PTX 0x01 /* Packet transmitted without error */ +#define ENTSR_ND 0x02 /* The transmit wasn't deferred. */ +#define ENTSR_COL 0x04 /* The transmit collided at least once. */ +#define ENTSR_ABT 0x08 /* The transmit collided 16 times, and was deferred. */ +#define ENTSR_CRS 0x10 /* The carrier sense was lost. */ +#define ENTSR_FU 0x20 /* A "FIFO underrun" occurred during transmit. */ +#define ENTSR_CDH 0x40 /* The collision detect "heartbeat" signal was lost. */ +#define ENTSR_OWC 0x80 /* There was an out-of-window collision. */ + +#endif /* _8390_h */ diff --git a/linux/pcmcia-cs/clients/axnet_cs.c b/linux/pcmcia-cs/clients/axnet_cs.c new file mode 100644 index 0000000..bcd79b0 --- /dev/null +++ b/linux/pcmcia-cs/clients/axnet_cs.c @@ -0,0 +1,1936 @@ +/*====================================================================== + + A PCMCIA ethernet driver for Asix AX88190-based cards + + The Asix AX88190 is a NS8390-derived chipset with a few nasty + idiosyncracies that make it very inconvenient to support with a + standard 8390 driver. This driver is based on pcnet_cs, with the + tweaked 8390 code grafted on the end. Much of what I did was to + clean up and update a similar driver supplied by Asix, which was + adapted by William Lee, william@asix.com.tw. + + Copyright (C) 2001 David A. Hinds -- dahinds@users.sourceforge.net + + axnet_cs.c 1.31 2003/08/25 15:57:40 + + The network driver code is based on Donald Becker's NE2000 code: + + Written 1992,1993 by Donald Becker. + Copyright 1993 United States Government as represented by the + Director, National Security Agency. This software may be used and + distributed according to the terms of the GNU General Public License, + incorporated herein by reference. + Donald Becker may be reached at becker@scyld.com + +======================================================================*/ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/ptrace.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/spinlock.h> +#include <asm/io.h> +#include <asm/system.h> +#include <asm/byteorder.h> +#include <asm/uaccess.h> + +#include <linux/netdevice.h> +#include "ax8390.h" + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/ciscode.h> +#include <pcmcia/ds.h> +#include <pcmcia/cisreg.h> + +#define AXNET_CMD 0x00 +#define AXNET_DATAPORT 0x10 /* NatSemi-defined port window offset. */ +#define AXNET_RESET 0x1f /* Issue a read to reset, a write to clear. */ +#define AXNET_MII_EEP 0x14 /* Offset of MII access port */ +#define AXNET_TEST 0x15 /* Offset of TEST Register port */ +#define AXNET_GPIO 0x17 /* Offset of General Purpose Register Port */ + +#define AXNET_START_PG 0x40 /* First page of TX buffer */ +#define AXNET_STOP_PG 0x80 /* Last page +1 of RX ring */ + +#define AXNET_RDC_TIMEOUT 0x02 /* Max wait in jiffies for Tx RDC */ + +#define IS_AX88190 0x0001 +#define IS_AX88790 0x0002 + +/*====================================================================*/ + +/* Module parameters */ + +MODULE_AUTHOR("David Hinds <dahinds@users.sourceforge.net>"); +MODULE_DESCRIPTION("Asix AX88190 PCMCIA ethernet driver"); +MODULE_LICENSE("GPL"); + +#define INT_MODULE_PARM(n, v) static int n = v; MODULE_PARM(n, "i") + +/* Bit map of interrupts to choose from */ +INT_MODULE_PARM(irq_mask, 0xdeb8); +static int irq_list[4] = { -1 }; +MODULE_PARM(irq_list, "1-4i"); + +#ifdef PCMCIA_DEBUG +INT_MODULE_PARM(pc_debug, PCMCIA_DEBUG); +#define DEBUG(n, args...) if (pc_debug>(n)) printk(KERN_DEBUG args) +static char *version = +"axnet_cs.c 1.31 2003/08/25 15:57:40 (David Hinds)"; +#else +#define DEBUG(n, args...) +#endif + +/*====================================================================*/ + +static void axnet_config(dev_link_t *link); +static void axnet_release(u_long arg); +static int axnet_event(event_t event, int priority, + event_callback_args_t *args); +static int axnet_open(struct net_device *dev); +static int axnet_close(struct net_device *dev); +static int axnet_ioctl(struct net_device *dev, struct ifreq *rq, int cmd); +static void ei_irq_wrapper(int irq, void *dev_id, struct pt_regs *regs); +static void ei_watchdog(u_long arg); +static void axnet_reset_8390(struct net_device *dev); + +static int mdio_read(ioaddr_t addr, int phy_id, int loc); +static void mdio_write(ioaddr_t addr, int phy_id, int loc, int value); + +static void get_8390_hdr(struct net_device *, + struct e8390_pkt_hdr *, int); +static void block_input(struct net_device *dev, int count, + struct sk_buff *skb, int ring_offset); +static void block_output(struct net_device *dev, int count, + const u_char *buf, const int start_page); + +static dev_link_t *axnet_attach(void); +static void axnet_detach(dev_link_t *); + +static dev_info_t dev_info = "axnet_cs"; +static dev_link_t *dev_list; + +static int axdev_init(struct net_device *dev); +static void AX88190_init(struct net_device *dev, int startp); +static int ax_open(struct net_device *dev); +static int ax_close(struct net_device *dev); +static void ax_interrupt(int irq, void *dev_id, struct pt_regs *regs); + +/*====================================================================*/ + +typedef struct axnet_dev_t { + struct net_device dev; /* so &dev == &axnet_dev_t */ + dev_link_t link; + dev_node_t node; + caddr_t base; + struct timer_list watchdog; + int stale, fast_poll; + u_short link_status; + u_char duplex_flag; + int phy_id; + int flags; +} axnet_dev_t; + +/*====================================================================== + + This bit of code is used to avoid unregistering network devices + at inappropriate times. 2.2 and later kernels are fairly picky + about when this can happen. + +======================================================================*/ + +static void flush_stale_links(void) +{ + dev_link_t *link, *next; + for (link = dev_list; link; link = next) { + next = link->next; + if (link->state & DEV_STALE_LINK) + axnet_detach(link); + } +} + +/*====================================================================*/ + +static void cs_error(client_handle_t handle, int func, int ret) +{ + error_info_t err = { func, ret }; + CardServices(ReportError, handle, &err); +} + +/*====================================================================== + + We never need to do anything when a axnet device is "initialized" + by the net software, because we only register already-found cards. + +======================================================================*/ + +static int axnet_init(struct net_device *dev) +{ + return 0; +} + +/*====================================================================== + + axnet_attach() creates an "instance" of the driver, allocating + local data structures for one device. The device is registered + with Card Services. + +======================================================================*/ + +static dev_link_t *axnet_attach(void) +{ + axnet_dev_t *info; + dev_link_t *link; + struct net_device *dev; + client_reg_t client_reg; + int i, ret; + + DEBUG(0, "axnet_attach()\n"); + flush_stale_links(); + + /* Create new ethernet device */ + info = kmalloc(sizeof(*info), GFP_KERNEL); + if (!info) return NULL; + memset(info, 0, sizeof(*info)); + link = &info->link; dev = &info->dev; + link->priv = info; + + init_timer(&link->release); + link->release.function = &axnet_release; + link->release.data = (u_long)link; + link->irq.Attributes = IRQ_TYPE_EXCLUSIVE; + link->irq.IRQInfo1 = IRQ_INFO2_VALID|IRQ_LEVEL_ID; + if (irq_list[0] == -1) + link->irq.IRQInfo2 = irq_mask; + else + for (i = 0; i < 4; i++) + link->irq.IRQInfo2 |= 1 << irq_list[i]; + link->conf.Attributes = CONF_ENABLE_IRQ; + link->conf.IntType = INT_MEMORY_AND_IO; + + axdev_init(dev); + init_dev_name(dev, info->node); + dev->init = &axnet_init; + dev->open = &axnet_open; + dev->stop = &axnet_close; + dev->do_ioctl = &axnet_ioctl; + + /* 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 = &axnet_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(link->handle, RegisterClient, ret); + axnet_detach(link); + return NULL; + } + + return link; +} /* axnet_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 axnet_detach(dev_link_t *link) +{ + axnet_dev_t *info = link->priv; + dev_link_t **linkp; + + DEBUG(0, "axnet_detach(0x%p)\n", link); + + /* Locate device structure */ + for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next) + if (*linkp == link) break; + if (*linkp == NULL) + return; + + del_timer(&link->release); + if (link->state & DEV_CONFIG) { + axnet_release((u_long)link); + if (link->state & DEV_STALE_CONFIG) { + link->state |= DEV_STALE_LINK; + return; + } + } + + if (link->handle) + CardServices(DeregisterClient, link->handle); + + /* Unlink device structure, free bits */ + *linkp = link->next; + if (link->dev) + unregister_netdev(&info->dev); + kfree(info); + +} /* axnet_detach */ + +/*====================================================================== + + This probes for a card's hardware address by reading the PROM. + +======================================================================*/ + +static int get_prom(dev_link_t *link) +{ + struct net_device *dev = link->priv; + ioaddr_t ioaddr = dev->base_addr; + int i, j; + + /* This is based on drivers/net/ne.c */ + struct { + u_char value, offset; + } program_seq[] = { + {E8390_NODMA+E8390_PAGE0+E8390_STOP, E8390_CMD}, /* Select page 0*/ + {0x01, EN0_DCFG}, /* Set word-wide access. */ + {0x00, EN0_RCNTLO}, /* Clear the count regs. */ + {0x00, EN0_RCNTHI}, + {0x00, EN0_IMR}, /* Mask completion irq. */ + {0xFF, EN0_ISR}, + {E8390_RXOFF|0x40, EN0_RXCR}, /* 0x60 Set to monitor */ + {E8390_TXOFF, EN0_TXCR}, /* 0x02 and loopback mode. */ + {0x10, EN0_RCNTLO}, + {0x00, EN0_RCNTHI}, + {0x00, EN0_RSARLO}, /* DMA starting at 0x0400. */ + {0x04, EN0_RSARHI}, + {E8390_RREAD+E8390_START, E8390_CMD}, + }; + + /* Not much of a test, but the alternatives are messy */ + if (link->conf.ConfigBase != 0x03c0) + return 0; + + axnet_reset_8390(dev); + mdelay(10); + + for (i = 0; i < sizeof(program_seq)/sizeof(program_seq[0]); i++) + outb_p(program_seq[i].value, ioaddr + program_seq[i].offset); + + for (i = 0; i < 6; i += 2) { + j = inw(ioaddr + AXNET_DATAPORT); + dev->dev_addr[i] = j & 0xff; + dev->dev_addr[i+1] = j >> 8; + } + return 1; +} /* get_prom */ + +/*====================================================================== + + axnet_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. + +======================================================================*/ + +#define CS_CHECK(fn, args...) \ +while ((last_ret=CardServices(last_fn=(fn), args))!=0) goto cs_failed + +#define CFG_CHECK(fn, args...) \ +if (CardServices(fn, args) != 0) goto next_entry + +static int try_io_port(dev_link_t *link) +{ + int j, ret; + if (link->io.NumPorts1 == 32) { + link->io.Attributes1 = IO_DATA_PATH_WIDTH_AUTO; + if (link->io.NumPorts2 > 0) { + /* for master/slave multifunction cards */ + link->io.Attributes2 = IO_DATA_PATH_WIDTH_8; + link->irq.Attributes = + IRQ_TYPE_DYNAMIC_SHARING|IRQ_FIRST_SHARED; + } + } else { + /* This should be two 16-port windows */ + link->io.Attributes1 = IO_DATA_PATH_WIDTH_8; + link->io.Attributes2 = IO_DATA_PATH_WIDTH_16; + } + if (link->io.BasePort1 == 0) { + link->io.IOAddrLines = 16; + for (j = 0; j < 0x400; j += 0x20) { + link->io.BasePort1 = j ^ 0x300; + link->io.BasePort2 = (j ^ 0x300) + 0x10; + ret = CardServices(RequestIO, link->handle, &link->io); + if (ret == CS_SUCCESS) return ret; + } + return ret; + } else { + return CardServices(RequestIO, link->handle, &link->io); + } +} + +static void axnet_config(dev_link_t *link) +{ + client_handle_t handle = link->handle; + axnet_dev_t *info = link->priv; + struct net_device *dev = &info->dev; + tuple_t tuple; + cisparse_t parse; + int i, j, last_ret, last_fn; + u_short buf[64]; + config_info_t conf; + + DEBUG(0, "axnet_config(0x%p)\n", link); + + tuple.Attributes = 0; + tuple.TupleData = (cisdata_t *)buf; + tuple.TupleDataMax = sizeof(buf); + tuple.TupleOffset = 0; + tuple.DesiredTuple = CISTPL_CONFIG; + CS_CHECK(GetFirstTuple, handle, &tuple); + CS_CHECK(GetTupleData, handle, &tuple); + CS_CHECK(ParseTuple, handle, &tuple, &parse); + link->conf.ConfigBase = parse.config.base; + /* don't trust the CIS on this; Linksys got it wrong */ + link->conf.Present = 0x63; + + /* Configure card */ + link->state |= DEV_CONFIG; + + /* Look up current Vcc */ + CS_CHECK(GetConfigurationInfo, handle, &conf); + link->conf.Vcc = conf.Vcc; + + tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY; + tuple.Attributes = 0; + CS_CHECK(GetFirstTuple, handle, &tuple); + while (last_ret == CS_SUCCESS) { + cistpl_cftable_entry_t *cfg = &(parse.cftable_entry); + cistpl_io_t *io = &(parse.cftable_entry.io); + + CFG_CHECK(GetTupleData, handle, &tuple); + CFG_CHECK(ParseTuple, handle, &tuple, &parse); + if ((cfg->index == 0) || (cfg->io.nwin == 0)) + goto next_entry; + + link->conf.ConfigIndex = 0x05; + /* For multifunction cards, by convention, we configure the + network function with window 0, and serial with window 1 */ + if (io->nwin > 1) { + i = (io->win[1].len > io->win[0].len); + link->io.BasePort2 = io->win[1-i].base; + link->io.NumPorts2 = io->win[1-i].len; + } else { + i = link->io.NumPorts2 = 0; + } + link->io.BasePort1 = io->win[i].base; + link->io.NumPorts1 = io->win[i].len; + link->io.IOAddrLines = io->flags & CISTPL_IO_LINES_MASK; + if (link->io.NumPorts1 + link->io.NumPorts2 >= 32) { + last_ret = try_io_port(link); + if (last_ret == CS_SUCCESS) break; + } + next_entry: + last_ret = CardServices(GetNextTuple, handle, &tuple); + } + if (last_ret != CS_SUCCESS) { + cs_error(handle, RequestIO, last_ret); + goto failed; + } + + CS_CHECK(RequestIRQ, handle, &link->irq); + + if (link->io.NumPorts2 == 8) { + link->conf.Attributes |= CONF_ENABLE_SPKR; + link->conf.Status = CCSR_AUDIO_ENA; + } + + CS_CHECK(RequestConfiguration, handle, &link->conf); + dev->irq = link->irq.AssignedIRQ; + dev->base_addr = link->io.BasePort1; + if (register_netdev(dev) != 0) { + printk(KERN_NOTICE "axnet_cs: register_netdev() failed\n"); + goto failed; + } + + if (!get_prom(link)) { + printk(KERN_NOTICE "axnet_cs: this is not an AX88190 card!\n"); + printk(KERN_NOTICE "axnet_cs: use pcnet_cs instead.\n"); + unregister_netdev(dev); + goto failed; + } + + ei_status.name = "AX88190"; + ei_status.word16 = 1; + ei_status.tx_start_page = AXNET_START_PG; + ei_status.rx_start_page = AXNET_START_PG + TX_PAGES; + ei_status.stop_page = AXNET_STOP_PG; + ei_status.reset_8390 = &axnet_reset_8390; + ei_status.get_8390_hdr = &get_8390_hdr; + ei_status.block_input = &block_input; + ei_status.block_output = &block_output; + + copy_dev_name(info->node, dev); + link->dev = &info->node; + + if (inb(dev->base_addr + AXNET_TEST) != 0) + info->flags |= IS_AX88790; + else + info->flags |= IS_AX88190; + + printk(KERN_INFO "%s: Asix AX88%d90: io %#3lx, irq %d, hw_addr ", + dev->name, ((info->flags & IS_AX88790) ? 7 : 1), + dev->base_addr, dev->irq); + for (i = 0; i < 6; i++) + printk("%02X%s", dev->dev_addr[i], ((i<5) ? ":" : "\n")); + + if (info->flags & IS_AX88790) + outb(0x10, dev->base_addr + AXNET_GPIO); /* select Internal PHY */ + + for (i = 0; i < 32; i++) { + j = mdio_read(dev->base_addr + AXNET_MII_EEP, i, 1); + if ((j != 0) && (j != 0xffff)) break; + } + + + /* Maybe PHY is in power down mode. (PPD_SET = 1) + Bit 2 of CCSR is active low. */ + if (i == 32) { + conf_reg_t reg = { 0, CS_WRITE, CISREG_CCSR, 0x04 }; + CardServices(AccessConfigurationRegister, link->handle, ®); + for (i = 0; i < 32; i++) { + j = mdio_read(dev->base_addr + AXNET_MII_EEP, i, 1); + if ((j != 0) && (j != 0xffff)) break; + } + } + + info->phy_id = (i < 32) ? i : -1; + if (i < 32) { + DEBUG(0, " MII transceiver at index %d, status %x.\n", i, j); + } else { + printk(KERN_NOTICE " No MII transceivers found!\n"); + } + + link->state &= ~DEV_CONFIG_PENDING; + return; + +cs_failed: + cs_error(link->handle, last_fn, last_ret); +failed: + axnet_release((u_long)link); + link->state &= ~DEV_CONFIG_PENDING; + return; +} /* axnet_config */ + +/*====================================================================== + + After a card is removed, axnet_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 axnet_release(u_long arg) +{ + dev_link_t *link = (dev_link_t *)arg; + + DEBUG(0, "axnet_release(0x%p)\n", link); + + if (link->open) { + DEBUG(1, "axnet_cs: release postponed, '%s' still open\n", + ((axnet_dev_t *)(link->priv))->node.dev_name); + link->state |= DEV_STALE_CONFIG; + return; + } + + CardServices(ReleaseConfiguration, link->handle); + CardServices(ReleaseIO, link->handle, &link->io); + CardServices(ReleaseIRQ, link->handle, &link->irq); + + link->state &= ~DEV_CONFIG; + +} /* axnet_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. + +======================================================================*/ + +static int axnet_event(event_t event, int priority, + event_callback_args_t *args) +{ + dev_link_t *link = args->client_data; + axnet_dev_t *info = link->priv; + + DEBUG(2, "axnet_event(0x%06x)\n", event); + + switch (event) { + case CS_EVENT_CARD_REMOVAL: + link->state &= ~DEV_PRESENT; + if (link->state & DEV_CONFIG) { + netif_device_detach(&info->dev); + mod_timer(&link->release, jiffies + HZ/20); + } + break; + case CS_EVENT_CARD_INSERTION: + link->state |= DEV_PRESENT | DEV_CONFIG_PENDING; + axnet_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) + netif_device_detach(&info->dev); + 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) { + axnet_reset_8390(&info->dev); + AX88190_init(&info->dev, 1); + netif_device_attach(&info->dev); + } + } + break; + } + return 0; +} /* axnet_event */ + +/*====================================================================== + + MII interface support + +======================================================================*/ + +#define MDIO_SHIFT_CLK 0x01 +#define MDIO_DATA_WRITE0 0x00 +#define MDIO_DATA_WRITE1 0x08 +#define MDIO_DATA_READ 0x04 +#define MDIO_MASK 0x0f +#define MDIO_ENB_IN 0x02 + +static void mdio_sync(ioaddr_t addr) +{ + int bits; + for (bits = 0; bits < 32; bits++) { + outb_p(MDIO_DATA_WRITE1, addr); + outb_p(MDIO_DATA_WRITE1 | MDIO_SHIFT_CLK, addr); + } +} + +static int mdio_read(ioaddr_t addr, int phy_id, int loc) +{ + u_int cmd = (0xf6<<10)|(phy_id<<5)|loc; + int i, retval = 0; + + mdio_sync(addr); + for (i = 14; i >= 0; i--) { + int dat = (cmd&(1<<i)) ? MDIO_DATA_WRITE1 : MDIO_DATA_WRITE0; + outb_p(dat, addr); + outb_p(dat | MDIO_SHIFT_CLK, addr); + } + for (i = 19; i > 0; i--) { + outb_p(MDIO_ENB_IN, addr); + retval = (retval << 1) | ((inb_p(addr) & MDIO_DATA_READ) != 0); + outb_p(MDIO_ENB_IN | MDIO_SHIFT_CLK, addr); + } + return (retval>>1) & 0xffff; +} + +static void mdio_write(ioaddr_t addr, int phy_id, int loc, int value) +{ + 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_p(dat, addr); + outb_p(dat | MDIO_SHIFT_CLK, addr); + } + for (i = 1; i >= 0; i--) { + outb_p(MDIO_ENB_IN, addr); + outb_p(MDIO_ENB_IN | MDIO_SHIFT_CLK, addr); + } +} + +/*====================================================================*/ + +static int axnet_open(struct net_device *dev) +{ + axnet_dev_t *info = (axnet_dev_t *)dev; + dev_link_t *link = &info->link; + + DEBUG(2, "axnet_open('%s')\n", dev->name); + + if (!DEV_OK(link)) + return -ENODEV; + + link->open++; + MOD_INC_USE_COUNT; + + request_irq(dev->irq, ei_irq_wrapper, SA_SHIRQ, dev_info, dev); + + info->link_status = 0x00; + info->watchdog.function = &ei_watchdog; + info->watchdog.data = (u_long)info; + info->watchdog.expires = jiffies + HZ; + add_timer(&info->watchdog); + + return ax_open(dev); +} /* axnet_open */ + +/*====================================================================*/ + +static int axnet_close(struct net_device *dev) +{ + axnet_dev_t *info = (axnet_dev_t *)dev; + dev_link_t *link = &info->link; + + DEBUG(2, "axnet_close('%s')\n", dev->name); + + ax_close(dev); + free_irq(dev->irq, dev); + + link->open--; + netif_stop_queue(dev); + netif_mark_down(dev); + del_timer(&info->watchdog); + if (link->state & DEV_STALE_CONFIG) + mod_timer(&link->release, jiffies + HZ/20); + + MOD_DEC_USE_COUNT; + + return 0; +} /* axnet_close */ + +/*====================================================================== + + Hard reset the card. This used to pause for the same period that + a 8390 reset command required, but that shouldn't be necessary. + +======================================================================*/ + +static void axnet_reset_8390(struct net_device *dev) +{ + ioaddr_t nic_base = dev->base_addr; + int i; + + ei_status.txing = ei_status.dmaing = 0; + + outb_p(E8390_NODMA+E8390_PAGE0+E8390_STOP, nic_base + E8390_CMD); + + outb(inb(nic_base + AXNET_RESET), nic_base + AXNET_RESET); + + for (i = 0; i < 100; i++) { + if ((inb_p(nic_base+EN0_ISR) & ENISR_RESET) != 0) + break; + udelay(100); + } + outb_p(ENISR_RESET, nic_base + EN0_ISR); /* Ack intr. */ + + if (i == 100) + printk(KERN_ERR "%s: axnet_reset_8390() did not complete.\n", + dev->name); + +} /* axnet_reset_8390 */ + +/*====================================================================*/ + +static void ei_irq_wrapper(int irq, void *dev_id, struct pt_regs *regs) +{ + axnet_dev_t *info = dev_id; + info->stale = 0; + ax_interrupt(irq, dev_id, regs); +} + +static void ei_watchdog(u_long arg) +{ + axnet_dev_t *info = (axnet_dev_t *)(arg); + struct net_device *dev = &info->dev; + ioaddr_t nic_base = dev->base_addr; + ioaddr_t mii_addr = nic_base + AXNET_MII_EEP; + u_short link; + + if (!netif_device_present(dev)) goto reschedule; + + /* Check for pending interrupt with expired latency timer: with + this, we can limp along even if the interrupt is blocked */ + if (info->stale++ && (inb_p(nic_base + EN0_ISR) & ENISR_ALL)) { + if (!info->fast_poll) + printk(KERN_INFO "%s: interrupt(s) dropped!\n", dev->name); + ei_irq_wrapper(dev->irq, dev, NULL); + info->fast_poll = HZ; + } + if (info->fast_poll) { + info->fast_poll--; + info->watchdog.expires = jiffies + 1; + add_timer(&info->watchdog); + return; + } + + if (info->phy_id < 0) + goto reschedule; + link = mdio_read(mii_addr, info->phy_id, 1); + if (!link || (link == 0xffff)) { + printk(KERN_INFO "%s: MII is missing!\n", dev->name); + info->phy_id = -1; + goto reschedule; + } + + link &= 0x0004; + if (link != info->link_status) { + u_short p = mdio_read(mii_addr, info->phy_id, 5); + printk(KERN_INFO "%s: %s link beat\n", dev->name, + (link) ? "found" : "lost"); + if (link) { + info->duplex_flag = (p & 0x0140) ? 0x80 : 0x00; + if (p) + printk(KERN_INFO "%s: autonegotiation complete: " + "%sbaseT-%cD selected\n", dev->name, + ((p & 0x0180) ? "100" : "10"), + ((p & 0x0140) ? 'F' : 'H')); + else + printk(KERN_INFO "%s: link partner did not autonegotiate\n", + dev->name); + AX88190_init(dev, 1); + } + info->link_status = link; + } + +reschedule: + info->watchdog.expires = jiffies + HZ; + add_timer(&info->watchdog); +} + +/*====================================================================*/ + +static int axnet_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) +{ + axnet_dev_t *info = (axnet_dev_t *)dev; + u16 *data = (u16 *)&rq->ifr_data; + ioaddr_t mii_addr = dev->base_addr + AXNET_MII_EEP; + switch (cmd) { + case SIOCDEVPRIVATE: + data[0] = info->phy_id; + case SIOCDEVPRIVATE+1: + data[3] = mdio_read(mii_addr, data[0], data[1] & 0x1f); + return 0; + case SIOCDEVPRIVATE+2: + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + mdio_write(mii_addr, data[0], data[1] & 0x1f, data[2]); + return 0; + } + return -EOPNOTSUPP; +} + +/*====================================================================*/ + +static void get_8390_hdr(struct net_device *dev, + struct e8390_pkt_hdr *hdr, + int ring_page) +{ + ioaddr_t nic_base = dev->base_addr; + + outb_p(0, nic_base + EN0_RSARLO); /* On page boundary */ + outb_p(ring_page, nic_base + EN0_RSARHI); + outb_p(E8390_RREAD+E8390_START, nic_base + AXNET_CMD); + + insw(nic_base + AXNET_DATAPORT, hdr, + sizeof(struct e8390_pkt_hdr)>>1); + /* Fix for big endian systems */ + hdr->count = le16_to_cpu(hdr->count); + +} + +/*====================================================================*/ + +static void block_input(struct net_device *dev, int count, + struct sk_buff *skb, int ring_offset) +{ + ioaddr_t nic_base = dev->base_addr; + int xfer_count = count; + char *buf = skb->data; + +#ifdef PCMCIA_DEBUG + if ((ei_debug > 4) && (count != 4)) + printk(KERN_DEBUG "%s: [bi=%d]\n", dev->name, count+4); +#endif + outb_p(ring_offset & 0xff, nic_base + EN0_RSARLO); + outb_p(ring_offset >> 8, nic_base + EN0_RSARHI); + outb_p(E8390_RREAD+E8390_START, nic_base + AXNET_CMD); + + insw(nic_base + AXNET_DATAPORT,buf,count>>1); + if (count & 0x01) + buf[count-1] = inb(nic_base + AXNET_DATAPORT), xfer_count++; + +} + +/*====================================================================*/ + +static void block_output(struct net_device *dev, int count, + const u_char *buf, const int start_page) +{ + ioaddr_t nic_base = dev->base_addr; + +#ifdef PCMCIA_DEBUG + if (ei_debug > 4) + printk(KERN_DEBUG "%s: [bo=%d]\n", dev->name, count); +#endif + + /* Round the count up for word writes. Do we need to do this? + What effect will an odd byte count have on the 8390? + I should check someday. */ + if (count & 0x01) + count++; + + outb_p(0x00, nic_base + EN0_RSARLO); + outb_p(start_page, nic_base + EN0_RSARHI); + outb_p(E8390_RWRITE+E8390_START, nic_base + AXNET_CMD); + outsw(nic_base + AXNET_DATAPORT, buf, count>>1); +} + +/*====================================================================*/ + +static int __init init_axnet_cs(void) +{ + servinfo_t serv; + DEBUG(0, "%s\n", version); + CardServices(GetCardServicesInfo, &serv); + if (serv.Revision != CS_RELEASE_CODE) { + printk(KERN_NOTICE "axnet_cs: Card Services release " + "does not match!\n"); + return -EINVAL; + } + register_pccard_driver(&dev_info, &axnet_attach, &axnet_detach); + return 0; +} + +static void __exit exit_axnet_cs(void) +{ + DEBUG(0, "axnet_cs: unloading\n"); + unregister_pccard_driver(&dev_info); + while (dev_list != NULL) + axnet_detach(dev_list); +} + +module_init(init_axnet_cs); +module_exit(exit_axnet_cs); + +/*====================================================================*/ + +/* 8390.c: A general NS8390 ethernet driver core for linux. */ +/* + Written 1992-94 by Donald Becker. + + Copyright 1993 United States Government as represented by the + Director, National Security Agency. + + This software may be used and distributed according to the terms + of the GNU General Public License, incorporated herein by reference. + + The author may be reached as becker@scyld.com, or C/O + Scyld Computing Corporation + 410 Severn Ave., Suite 210 + Annapolis MD 21403 + + This is the chip-specific code for many 8390-based ethernet adaptors. + This is not a complete driver, it must be combined with board-specific + code such as ne.c, wd.c, 3c503.c, etc. + + Seeing how at least eight drivers use this code, (not counting the + PCMCIA ones either) it is easy to break some card by what seems like + a simple innocent change. Please contact me or Donald if you think + you have found something that needs changing. -- PG + + Changelog: + + Paul Gortmaker : remove set_bit lock, other cleanups. + Paul Gortmaker : add ei_get_8390_hdr() so we can pass skb's to + ei_block_input() for eth_io_copy_and_sum(). + Paul Gortmaker : exchange static int ei_pingpong for a #define, + also add better Tx error handling. + Paul Gortmaker : rewrite Rx overrun handling as per NS specs. + Alexey Kuznetsov : use the 8390's six bit hash multicast filter. + Paul Gortmaker : tweak ANK's above multicast changes a bit. + Paul Gortmaker : update packet statistics for v2.1.x + Alan Cox : support arbitary stupid port mappings on the + 68K Macintosh. Support >16bit I/O spaces + Paul Gortmaker : add kmod support for auto-loading of the 8390 + module by all drivers that require it. + Alan Cox : Spinlocking work, added 'BUG_83C690' + Paul Gortmaker : Separate out Tx timeout code from Tx path. + + Sources: + The National Semiconductor LAN Databook, and the 3Com 3c503 databook. + + */ + +static const char *version_8390 = + "8390.c:v1.10cvs 9/23/94 Donald Becker (becker@scyld.com)\n"; + +#include <asm/uaccess.h> +#include <asm/bitops.h> +#include <asm/irq.h> +#include <linux/fcntl.h> +#include <linux/in.h> +#include <linux/interrupt.h> + +#include <linux/etherdevice.h> + +#define BUG_83C690 + +/* These are the operational function interfaces to board-specific + routines. + void reset_8390(struct net_device *dev) + Resets the board associated with DEV, including a hardware reset of + the 8390. This is only called when there is a transmit timeout, and + it is always followed by 8390_init(). + void block_output(struct net_device *dev, int count, const unsigned char *buf, + int start_page) + Write the COUNT bytes of BUF to the packet buffer at START_PAGE. The + "page" value uses the 8390's 256-byte pages. + void get_8390_hdr(struct net_device *dev, struct e8390_hdr *hdr, int ring_page) + Read the 4 byte, page aligned 8390 header. *If* there is a + subsequent read, it will be of the rest of the packet. + void block_input(struct net_device *dev, int count, struct sk_buff *skb, int ring_offset) + Read COUNT bytes from the packet buffer into the skb data area. Start + reading from RING_OFFSET, the address as the 8390 sees it. This will always + follow the read of the 8390 header. +*/ +#define ei_reset_8390 (ei_local->reset_8390) +#define ei_block_output (ei_local->block_output) +#define ei_block_input (ei_local->block_input) +#define ei_get_8390_hdr (ei_local->get_8390_hdr) + +/* use 0 for production, 1 for verification, >2 for debug */ +#ifndef ei_debug +int ei_debug = 1; +#endif + +/* Index to functions. */ +static void ei_tx_intr(struct net_device *dev); +static void ei_tx_err(struct net_device *dev); +static void ei_tx_timeout(struct net_device *dev); +static void ei_receive(struct net_device *dev); +static void ei_rx_overrun(struct net_device *dev); + +/* Routines generic to NS8390-based boards. */ +static void NS8390_trigger_send(struct net_device *dev, unsigned int length, + int start_page); +static void set_multicast_list(struct net_device *dev); +static void do_set_multicast_list(struct net_device *dev); + +/* + * SMP and the 8390 setup. + * + * The 8390 isnt exactly designed to be multithreaded on RX/TX. There is + * a page register that controls bank and packet buffer access. We guard + * this with ei_local->page_lock. Nobody should assume or set the page other + * than zero when the lock is not held. Lock holders must restore page 0 + * before unlocking. Even pure readers must take the lock to protect in + * page 0. + * + * To make life difficult the chip can also be very slow. We therefore can't + * just use spinlocks. For the longer lockups we disable the irq the device + * sits on and hold the lock. We must hold the lock because there is a dual + * processor case other than interrupts (get stats/set multicast list in + * parallel with each other and transmit). + * + * Note: in theory we can just disable the irq on the card _but_ there is + * a latency on SMP irq delivery. So we can easily go "disable irq" "sync irqs" + * enter lock, take the queued irq. So we waddle instead of flying. + * + * Finally by special arrangement for the purpose of being generally + * annoying the transmit function is called bh atomic. That places + * restrictions on the user context callers as disable_irq won't save + * them. + */ + +/** + * ax_open - Open/initialize the board. + * @dev: network device to initialize + * + * This routine goes all-out, setting everything + * up anew at each open, even though many of these registers should only + * need to be set once at boot. + */ +static int ax_open(struct net_device *dev) +{ + unsigned long flags; + struct ei_device *ei_local = (struct ei_device *) dev->priv; + + /* This can't happen unless somebody forgot to call axdev_init(). */ + if (ei_local == NULL) + { + printk(KERN_EMERG "%s: ax_open passed a non-existent device!\n", dev->name); + return -ENXIO; + } + +#ifdef HAVE_TX_TIMEOUT + /* The card I/O part of the driver (e.g. 3c503) can hook a Tx timeout + wrapper that does e.g. media check & then calls ei_tx_timeout. */ + if (dev->tx_timeout == NULL) + dev->tx_timeout = ei_tx_timeout; + if (dev->watchdog_timeo <= 0) + dev->watchdog_timeo = TX_TIMEOUT; +#endif + + /* + * Grab the page lock so we own the register set, then call + * the init function. + */ + + spin_lock_irqsave(&ei_local->page_lock, flags); + AX88190_init(dev, 1); + /* Set the flag before we drop the lock, That way the IRQ arrives + after its set and we get no silly warnings */ + netif_mark_up(dev); + netif_start_queue(dev); + spin_unlock_irqrestore(&ei_local->page_lock, flags); + ei_local->irqlock = 0; + return 0; +} + +#define dev_lock(dev) (((struct ei_device *)(dev)->priv)->page_lock) + +/** + * ax_close - shut down network device + * @dev: network device to close + * + * Opposite of ax_open(). Only used when "ifconfig <devname> down" is done. + */ +int ax_close(struct net_device *dev) +{ + unsigned long flags; + + /* + * Hold the page lock during close + */ + + spin_lock_irqsave(&dev_lock(dev), flags); + AX88190_init(dev, 0); + spin_unlock_irqrestore(&dev_lock(dev), flags); + netif_stop_queue(dev); + return 0; +} + +/** + * ei_tx_timeout - handle transmit time out condition + * @dev: network device which has apparently fallen asleep + * + * Called by kernel when device never acknowledges a transmit has + * completed (or failed) - i.e. never posted a Tx related interrupt. + */ + +void ei_tx_timeout(struct net_device *dev) +{ + long e8390_base = dev->base_addr; + struct ei_device *ei_local = (struct ei_device *) dev->priv; + int txsr, isr, tickssofar = jiffies - dev->trans_start; + unsigned long flags; + + ei_local->stat.tx_errors++; + + spin_lock_irqsave(&ei_local->page_lock, flags); + txsr = inb(e8390_base+EN0_TSR); + isr = inb(e8390_base+EN0_ISR); + spin_unlock_irqrestore(&ei_local->page_lock, flags); + + printk(KERN_DEBUG "%s: Tx timed out, %s TSR=%#2x, ISR=%#2x, t=%d.\n", + dev->name, (txsr & ENTSR_ABT) ? "excess collisions." : + (isr) ? "lost interrupt?" : "cable problem?", txsr, isr, tickssofar); + + if (!isr && !ei_local->stat.tx_packets) + { + /* The 8390 probably hasn't gotten on the cable yet. */ + ei_local->interface_num ^= 1; /* Try a different xcvr. */ + } + + /* Ugly but a reset can be slow, yet must be protected */ + + disable_irq_nosync(dev->irq); + spin_lock(&ei_local->page_lock); + + /* Try to restart the card. Perhaps the user has fixed something. */ + ei_reset_8390(dev); + AX88190_init(dev, 1); + + spin_unlock(&ei_local->page_lock); + enable_irq(dev->irq); + netif_wake_queue(dev); +} + +/** + * ei_start_xmit - begin packet transmission + * @skb: packet to be sent + * @dev: network device to which packet is sent + * + * Sends a packet to an 8390 network device. + */ + +static int ei_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + long e8390_base = dev->base_addr; + struct ei_device *ei_local = (struct ei_device *) dev->priv; + int length, send_length, output_page; + unsigned long flags; + + tx_timeout_check(dev, ei_tx_timeout); + skb_tx_check(dev, skb); + + length = skb->len; + + /* Mask interrupts from the ethercard. + SMP: We have to grab the lock here otherwise the IRQ handler + on another CPU can flip window and race the IRQ mask set. We end + up trashing the mcast filter not disabling irqs if we dont lock */ + + spin_lock_irqsave(&ei_local->page_lock, flags); + outb_p(0x00, e8390_base + EN0_IMR); + spin_unlock_irqrestore(&ei_local->page_lock, flags); + + /* + * Slow phase with lock held. + */ + + disable_irq_nosync(dev->irq); + + spin_lock(&ei_local->page_lock); + + ei_local->irqlock = 1; + + send_length = ETH_ZLEN < length ? length : ETH_ZLEN; + + /* + * We have two Tx slots available for use. Find the first free + * slot, and then perform some sanity checks. With two Tx bufs, + * you get very close to transmitting back-to-back packets. With + * only one Tx buf, the transmitter sits idle while you reload the + * card, leaving a substantial gap between each transmitted packet. + */ + + if (ei_local->tx1 == 0) + { + output_page = ei_local->tx_start_page; + ei_local->tx1 = send_length; + if (ei_debug && ei_local->tx2 > 0) + printk(KERN_DEBUG "%s: idle transmitter tx2=%d, lasttx=%d, txing=%d.\n", + dev->name, ei_local->tx2, ei_local->lasttx, ei_local->txing); + } + else if (ei_local->tx2 == 0) + { + output_page = ei_local->tx_start_page + TX_1X_PAGES; + ei_local->tx2 = send_length; + if (ei_debug && ei_local->tx1 > 0) + printk(KERN_DEBUG "%s: idle transmitter, tx1=%d, lasttx=%d, txing=%d.\n", + dev->name, ei_local->tx1, ei_local->lasttx, ei_local->txing); + } + else + { /* We should never get here. */ + if (ei_debug) + printk(KERN_DEBUG "%s: No Tx buffers free! tx1=%d tx2=%d last=%d\n", + dev->name, ei_local->tx1, ei_local->tx2, ei_local->lasttx); + ei_local->irqlock = 0; + netif_stop_queue(dev); + outb_p(ENISR_ALL, e8390_base + EN0_IMR); + spin_unlock(&ei_local->page_lock); + enable_irq(dev->irq); + ei_local->stat.tx_errors++; + return 1; + } + + /* + * Okay, now upload the packet and trigger a send if the transmitter + * isn't already sending. If it is busy, the interrupt handler will + * trigger the send later, upon receiving a Tx done interrupt. + */ + + ei_block_output(dev, length, skb->data, output_page); + if (! ei_local->txing) + { + ei_local->txing = 1; + NS8390_trigger_send(dev, send_length, output_page); + dev->trans_start = jiffies; + if (output_page == ei_local->tx_start_page) + { + ei_local->tx1 = -1; + ei_local->lasttx = -1; + } + else + { + ei_local->tx2 = -1; + ei_local->lasttx = -2; + } + } + else ei_local->txqueue++; + + if (ei_local->tx1 && ei_local->tx2) + netif_stop_queue(dev); + else + netif_start_queue(dev); + + /* Turn 8390 interrupts back on. */ + ei_local->irqlock = 0; + outb_p(ENISR_ALL, e8390_base + EN0_IMR); + + spin_unlock(&ei_local->page_lock); + enable_irq(dev->irq); + + DEV_KFREE_SKB (skb); + add_tx_bytes(&ei_local->stat, send_length); + + return 0; +} + +/** + * ax_interrupt - handle the interrupts from an 8390 + * @irq: interrupt number + * @dev_id: a pointer to the net_device + * @regs: unused + * + * Handle the ether interface interrupts. We pull packets from + * the 8390 via the card specific functions and fire them at the networking + * stack. We also handle transmit completions and wake the transmit path if + * neccessary. We also update the counters and do other housekeeping as + * needed. + */ + +static void ax_interrupt(int irq, void *dev_id, struct pt_regs * regs) +{ + struct net_device *dev = dev_id; + long e8390_base; + int interrupts, nr_serviced = 0, i; + struct ei_device *ei_local; + + if (dev == NULL) + { + printk ("net_interrupt(): irq %d for unknown device.\n", irq); + return; + } + + e8390_base = dev->base_addr; + ei_local = (struct ei_device *) dev->priv; + + /* + * Protect the irq test too. + */ + + spin_lock(&ei_local->page_lock); + + if (ei_local->irqlock) + { +#if 1 /* This might just be an interrupt for a PCI device sharing this line */ + /* The "irqlock" check is only for testing. */ + printk(ei_local->irqlock + ? "%s: Interrupted while interrupts are masked! isr=%#2x imr=%#2x.\n" + : "%s: Reentering the interrupt handler! isr=%#2x imr=%#2x.\n", + dev->name, inb_p(e8390_base + EN0_ISR), + inb_p(e8390_base + EN0_IMR)); +#endif + spin_unlock(&ei_local->page_lock); + return; + } + + if (ei_debug > 3) + printk(KERN_DEBUG "%s: interrupt(isr=%#2.2x).\n", dev->name, + inb_p(e8390_base + EN0_ISR)); + + outb_p(0x00, e8390_base + EN0_ISR); + ei_local->irqlock = 1; + + /* !!Assumption!! -- we stay in page 0. Don't break this. */ + while ((interrupts = inb_p(e8390_base + EN0_ISR)) != 0 + && ++nr_serviced < MAX_SERVICE) + { + if (!netif_running(dev) || (interrupts == 0xff)) { + if (ei_debug > 1) + printk(KERN_WARNING "%s: interrupt from stopped card\n", dev->name); + outb_p(interrupts, e8390_base + EN0_ISR); + interrupts = 0; + break; + } + /* AX88190 bug fix. */ + outb_p(interrupts, e8390_base + EN0_ISR); + for (i = 0; i < 10; i++) { + if (!(inb(e8390_base + EN0_ISR) & interrupts)) + break; + outb_p(0, e8390_base + EN0_ISR); + outb_p(interrupts, e8390_base + EN0_ISR); + } + if (interrupts & ENISR_OVER) + ei_rx_overrun(dev); + else if (interrupts & (ENISR_RX+ENISR_RX_ERR)) + { + /* Got a good (?) packet. */ + ei_receive(dev); + } + /* Push the next to-transmit packet through. */ + if (interrupts & ENISR_TX) + ei_tx_intr(dev); + else if (interrupts & ENISR_TX_ERR) + ei_tx_err(dev); + + if (interrupts & ENISR_COUNTERS) + { + ei_local->stat.rx_frame_errors += inb_p(e8390_base + EN0_COUNTER0); + ei_local->stat.rx_crc_errors += inb_p(e8390_base + EN0_COUNTER1); + ei_local->stat.rx_missed_errors+= inb_p(e8390_base + EN0_COUNTER2); + } + } + + if (interrupts && ei_debug) + { + if (nr_serviced >= MAX_SERVICE) + { + /* 0xFF is valid for a card removal */ + if(interrupts!=0xFF) + printk(KERN_WARNING "%s: Too much work at interrupt, status %#2.2x\n", + dev->name, interrupts); + outb_p(ENISR_ALL, e8390_base + EN0_ISR); /* Ack. most intrs. */ + } else { + printk(KERN_WARNING "%s: unknown interrupt %#2x\n", dev->name, interrupts); + outb_p(0xff, e8390_base + EN0_ISR); /* Ack. all intrs. */ + } + } + + /* Turn 8390 interrupts back on. */ + ei_local->irqlock = 0; + outb_p(ENISR_ALL, e8390_base + EN0_IMR); + + spin_unlock(&ei_local->page_lock); + return; +} + +/** + * ei_tx_err - handle transmitter error + * @dev: network device which threw the exception + * + * A transmitter error has happened. Most likely excess collisions (which + * is a fairly normal condition). If the error is one where the Tx will + * have been aborted, we try and send another one right away, instead of + * letting the failed packet sit and collect dust in the Tx buffer. This + * is a much better solution as it avoids kernel based Tx timeouts, and + * an unnecessary card reset. + * + * Called with lock held. + */ + +static void ei_tx_err(struct net_device *dev) +{ + long e8390_base = dev->base_addr; + struct ei_device *ei_local = (struct ei_device *) dev->priv; + unsigned char txsr = inb_p(e8390_base+EN0_TSR); + unsigned char tx_was_aborted = txsr & (ENTSR_ABT+ENTSR_FU); + +#ifdef VERBOSE_ERROR_DUMP + printk(KERN_DEBUG "%s: transmitter error (%#2x): ", dev->name, txsr); + if (txsr & ENTSR_ABT) + printk("excess-collisions "); + if (txsr & ENTSR_ND) + printk("non-deferral "); + if (txsr & ENTSR_CRS) + printk("lost-carrier "); + if (txsr & ENTSR_FU) + printk("FIFO-underrun "); + if (txsr & ENTSR_CDH) + printk("lost-heartbeat "); + printk("\n"); +#endif + + if (tx_was_aborted) + ei_tx_intr(dev); + else + { + ei_local->stat.tx_errors++; + if (txsr & ENTSR_CRS) ei_local->stat.tx_carrier_errors++; + if (txsr & ENTSR_CDH) ei_local->stat.tx_heartbeat_errors++; + if (txsr & ENTSR_OWC) ei_local->stat.tx_window_errors++; + } +} + +/** + * ei_tx_intr - transmit interrupt handler + * @dev: network device for which tx intr is handled + * + * We have finished a transmit: check for errors and then trigger the next + * packet to be sent. Called with lock held. + */ + +static void ei_tx_intr(struct net_device *dev) +{ + long e8390_base = dev->base_addr; + struct ei_device *ei_local = (struct ei_device *) dev->priv; + int status = inb(e8390_base + EN0_TSR); + + /* + * There are two Tx buffers, see which one finished, and trigger + * the send of another one if it exists. + */ + ei_local->txqueue--; + + if (ei_local->tx1 < 0) + { + if (ei_local->lasttx != 1 && ei_local->lasttx != -1) + printk(KERN_ERR "%s: bogus last_tx_buffer %d, tx1=%d.\n", + ei_local->name, ei_local->lasttx, ei_local->tx1); + ei_local->tx1 = 0; + if (ei_local->tx2 > 0) + { + ei_local->txing = 1; + NS8390_trigger_send(dev, ei_local->tx2, ei_local->tx_start_page + 6); + dev->trans_start = jiffies; + ei_local->tx2 = -1, + ei_local->lasttx = 2; + } + else ei_local->lasttx = 20, ei_local->txing = 0; + } + else if (ei_local->tx2 < 0) + { + if (ei_local->lasttx != 2 && ei_local->lasttx != -2) + printk("%s: bogus last_tx_buffer %d, tx2=%d.\n", + ei_local->name, ei_local->lasttx, ei_local->tx2); + ei_local->tx2 = 0; + if (ei_local->tx1 > 0) + { + ei_local->txing = 1; + NS8390_trigger_send(dev, ei_local->tx1, ei_local->tx_start_page); + dev->trans_start = jiffies; + ei_local->tx1 = -1; + ei_local->lasttx = 1; + } + else + ei_local->lasttx = 10, ei_local->txing = 0; + } +// else printk(KERN_WARNING "%s: unexpected TX-done interrupt, lasttx=%d.\n", +// dev->name, ei_local->lasttx); + + /* Minimize Tx latency: update the statistics after we restart TXing. */ + if (status & ENTSR_COL) + ei_local->stat.collisions++; + if (status & ENTSR_PTX) + ei_local->stat.tx_packets++; + else + { + ei_local->stat.tx_errors++; + if (status & ENTSR_ABT) + { + ei_local->stat.tx_aborted_errors++; + ei_local->stat.collisions += 16; + } + if (status & ENTSR_CRS) + ei_local->stat.tx_carrier_errors++; + if (status & ENTSR_FU) + ei_local->stat.tx_fifo_errors++; + if (status & ENTSR_CDH) + ei_local->stat.tx_heartbeat_errors++; + if (status & ENTSR_OWC) + ei_local->stat.tx_window_errors++; + } + netif_wake_queue(dev); +} + +/** + * ei_receive - receive some packets + * @dev: network device with which receive will be run + * + * We have a good packet(s), get it/them out of the buffers. + * Called with lock held. + */ + +static void ei_receive(struct net_device *dev) +{ + long e8390_base = dev->base_addr; + struct ei_device *ei_local = (struct ei_device *) dev->priv; + unsigned char rxing_page, this_frame, next_frame; + unsigned short current_offset; + int rx_pkt_count = 0; + struct e8390_pkt_hdr rx_frame; + + while (++rx_pkt_count < 10) + { + int pkt_len, pkt_stat; + + /* Get the rx page (incoming packet pointer). */ + rxing_page = inb_p(e8390_base + EN1_CURPAG -1); + + /* Remove one frame from the ring. Boundary is always a page behind. */ + this_frame = inb_p(e8390_base + EN0_BOUNDARY) + 1; + if (this_frame >= ei_local->stop_page) + this_frame = ei_local->rx_start_page; + + /* Someday we'll omit the previous, iff we never get this message. + (There is at least one clone claimed to have a problem.) + + Keep quiet if it looks like a card removal. One problem here + is that some clones crash in roughly the same way. + */ + if (ei_debug > 0 && this_frame != ei_local->current_page && (this_frame!=0x0 || rxing_page!=0xFF)) + printk(KERN_ERR "%s: mismatched read page pointers %2x vs %2x.\n", + dev->name, this_frame, ei_local->current_page); + + if (this_frame == rxing_page) /* Read all the frames? */ + break; /* Done for now */ + + current_offset = this_frame << 8; + ei_get_8390_hdr(dev, &rx_frame, this_frame); + + pkt_len = rx_frame.count - sizeof(struct e8390_pkt_hdr); + pkt_stat = rx_frame.status; + + next_frame = this_frame + 1 + ((pkt_len+4)>>8); + + if (pkt_len < 60 || pkt_len > 1518) + { + if (ei_debug) + printk(KERN_DEBUG "%s: bogus packet size: %d, status=%#2x nxpg=%#2x.\n", + dev->name, rx_frame.count, rx_frame.status, + rx_frame.next); + ei_local->stat.rx_errors++; + ei_local->stat.rx_length_errors++; + } + else if ((pkt_stat & 0x0F) == ENRSR_RXOK) + { + struct sk_buff *skb; + + skb = dev_alloc_skb(pkt_len+2); + if (skb == NULL) + { + if (ei_debug > 1) + printk(KERN_DEBUG "%s: Couldn't allocate a sk_buff of size %d.\n", + dev->name, pkt_len); + ei_local->stat.rx_dropped++; + break; + } + else + { + skb_reserve(skb,2); /* IP headers on 16 byte boundaries */ + skb->dev = dev; + skb_put(skb, pkt_len); /* Make room */ + ei_block_input(dev, pkt_len, skb, current_offset + sizeof(rx_frame)); + skb->protocol=eth_type_trans(skb,dev); + netif_rx(skb); + dev->last_rx = jiffies; + ei_local->stat.rx_packets++; + add_rx_bytes(&ei_local->stat, pkt_len); + if (pkt_stat & ENRSR_PHY) + ei_local->stat.multicast++; + } + } + else + { + if (ei_debug) + printk(KERN_DEBUG "%s: bogus packet: status=%#2x nxpg=%#2x size=%d\n", + dev->name, rx_frame.status, rx_frame.next, + rx_frame.count); + ei_local->stat.rx_errors++; + /* NB: The NIC counts CRC, frame and missed errors. */ + if (pkt_stat & ENRSR_FO) + ei_local->stat.rx_fifo_errors++; + } + next_frame = rx_frame.next; + + /* This _should_ never happen: it's here for avoiding bad clones. */ + if (next_frame >= ei_local->stop_page) { + printk("%s: next frame inconsistency, %#2x\n", dev->name, + next_frame); + next_frame = ei_local->rx_start_page; + } + ei_local->current_page = next_frame; + outb_p(next_frame-1, e8390_base+EN0_BOUNDARY); + } + + return; +} + +/** + * ei_rx_overrun - handle receiver overrun + * @dev: network device which threw exception + * + * We have a receiver overrun: we have to kick the 8390 to get it started + * again. Problem is that you have to kick it exactly as NS prescribes in + * the updated datasheets, or "the NIC may act in an unpredictable manner." + * This includes causing "the NIC to defer indefinitely when it is stopped + * on a busy network." Ugh. + * Called with lock held. Don't call this with the interrupts off or your + * computer will hate you - it takes 10ms or so. + */ + +static void ei_rx_overrun(struct net_device *dev) +{ + axnet_dev_t *info = (axnet_dev_t *)dev; + long e8390_base = dev->base_addr; + unsigned char was_txing, must_resend = 0; + struct ei_device *ei_local = (struct ei_device *) dev->priv; + + /* + * Record whether a Tx was in progress and then issue the + * stop command. + */ + was_txing = inb_p(e8390_base+E8390_CMD) & E8390_TRANS; + outb_p(E8390_NODMA+E8390_PAGE0+E8390_STOP, e8390_base+E8390_CMD); + + if (ei_debug > 1) + printk(KERN_DEBUG "%s: Receiver overrun.\n", dev->name); + ei_local->stat.rx_over_errors++; + + /* + * Wait a full Tx time (1.2ms) + some guard time, NS says 1.6ms total. + * Early datasheets said to poll the reset bit, but now they say that + * it "is not a reliable indicator and subsequently should be ignored." + * We wait at least 10ms. + */ + + mdelay(10); + + /* + * Reset RBCR[01] back to zero as per magic incantation. + */ + outb_p(0x00, e8390_base+EN0_RCNTLO); + outb_p(0x00, e8390_base+EN0_RCNTHI); + + /* + * See if any Tx was interrupted or not. According to NS, this + * step is vital, and skipping it will cause no end of havoc. + */ + + if (was_txing) + { + unsigned char tx_completed = inb_p(e8390_base+EN0_ISR) & (ENISR_TX+ENISR_TX_ERR); + if (!tx_completed) + must_resend = 1; + } + + /* + * Have to enter loopback mode and then restart the NIC before + * you are allowed to slurp packets up off the ring. + */ + outb_p(E8390_TXOFF, e8390_base + EN0_TXCR); + outb_p(E8390_NODMA + E8390_PAGE0 + E8390_START, e8390_base + E8390_CMD); + + /* + * Clear the Rx ring of all the debris, and ack the interrupt. + */ + ei_receive(dev); + + /* + * Leave loopback mode, and resend any packet that got stopped. + */ + outb_p(E8390_TXCONFIG | info->duplex_flag, e8390_base + EN0_TXCR); + if (must_resend) + outb_p(E8390_NODMA + E8390_PAGE0 + E8390_START + E8390_TRANS, e8390_base + E8390_CMD); +} + +/* + * Collect the stats. This is called unlocked and from several contexts. + */ + +static struct net_device_stats *get_stats(struct net_device *dev) +{ + long ioaddr = dev->base_addr; + struct ei_device *ei_local = (struct ei_device *) dev->priv; + unsigned long flags; + + /* If the card is stopped, just return the present stats. */ + if (!netif_running(dev)) + return &ei_local->stat; + + spin_lock_irqsave(&ei_local->page_lock,flags); + /* Read the counter registers, assuming we are in page 0. */ + ei_local->stat.rx_frame_errors += inb_p(ioaddr + EN0_COUNTER0); + ei_local->stat.rx_crc_errors += inb_p(ioaddr + EN0_COUNTER1); + ei_local->stat.rx_missed_errors+= inb_p(ioaddr + EN0_COUNTER2); + spin_unlock_irqrestore(&ei_local->page_lock, flags); + + return &ei_local->stat; +} + +/** + * do_set_multicast_list - set/clear multicast filter + * @dev: net device for which multicast filter is adjusted + * + * Set or clear the multicast filter for this adaptor. May be called + * from a BH in 2.1.x. Must be called with lock held. + */ + +static void do_set_multicast_list(struct net_device *dev) +{ + long e8390_base = dev->base_addr; + + if(dev->flags&IFF_PROMISC) + outb_p(E8390_RXCONFIG | 0x58, e8390_base + EN0_RXCR); + else if(dev->flags&IFF_ALLMULTI || dev->mc_list) + outb_p(E8390_RXCONFIG | 0x48, e8390_base + EN0_RXCR); + else + outb_p(E8390_RXCONFIG | 0x40, e8390_base + EN0_RXCR); +} + +/* + * Called without lock held. This is invoked from user context and may + * be parallel to just about everything else. Its also fairly quick and + * not called too often. Must protect against both bh and irq users + */ + +static void set_multicast_list(struct net_device *dev) +{ + unsigned long flags; + + spin_lock_irqsave(&dev_lock(dev), flags); + do_set_multicast_list(dev); + spin_unlock_irqrestore(&dev_lock(dev), flags); +} + +/** + * axdev_init - init rest of 8390 device struct + * @dev: network device structure to init + * + * Initialize the rest of the 8390 device structure. Do NOT __init + * this, as it is used by 8390 based modular drivers too. + */ + +static int axdev_init(struct net_device *dev) +{ + if (ei_debug > 1) + printk(version_8390); + + if (dev->priv == NULL) + { + struct ei_device *ei_local; + + dev->priv = kmalloc(sizeof(struct ei_device), GFP_KERNEL); + if (dev->priv == NULL) + return -ENOMEM; + memset(dev->priv, 0, sizeof(struct ei_device)); + ei_local = (struct ei_device *)dev->priv; + spin_lock_init(&ei_local->page_lock); + } + + dev->hard_start_xmit = &ei_start_xmit; + dev->get_stats = get_stats; + dev->set_multicast_list = &set_multicast_list; + + ether_setup(dev); + + return 0; +} + +/* This page of functions should be 8390 generic */ +/* Follow National Semi's recommendations for initializing the "NIC". */ + +/** + * AX88190_init - initialize 8390 hardware + * @dev: network device to initialize + * @startp: boolean. non-zero value to initiate chip processing + * + * Must be called with lock held. + */ + +static void AX88190_init(struct net_device *dev, int startp) +{ + axnet_dev_t *info = (axnet_dev_t *)dev; + long e8390_base = dev->base_addr; + struct ei_device *ei_local = (struct ei_device *) dev->priv; + int i; + int endcfg = ei_local->word16 ? (0x48 | ENDCFG_WTS) : 0x48; + + if(sizeof(struct e8390_pkt_hdr)!=4) + panic("8390.c: header struct mispacked\n"); + /* Follow National Semi's recommendations for initing the DP83902. */ + outb_p(E8390_NODMA+E8390_PAGE0+E8390_STOP, e8390_base+E8390_CMD); /* 0x21 */ + outb_p(endcfg, e8390_base + EN0_DCFG); /* 0x48 or 0x49 */ + /* Clear the remote byte count registers. */ + outb_p(0x00, e8390_base + EN0_RCNTLO); + outb_p(0x00, e8390_base + EN0_RCNTHI); + /* Set to monitor and loopback mode -- this is vital!. */ + outb_p(E8390_RXOFF|0x40, e8390_base + EN0_RXCR); /* 0x60 */ + outb_p(E8390_TXOFF, e8390_base + EN0_TXCR); /* 0x02 */ + /* Set the transmit page and receive ring. */ + outb_p(ei_local->tx_start_page, e8390_base + EN0_TPSR); + ei_local->tx1 = ei_local->tx2 = 0; + outb_p(ei_local->rx_start_page, e8390_base + EN0_STARTPG); + outb_p(ei_local->stop_page-1, e8390_base + EN0_BOUNDARY); /* 3c503 says 0x3f,NS0x26*/ + ei_local->current_page = ei_local->rx_start_page; /* assert boundary+1 */ + outb_p(ei_local->stop_page, e8390_base + EN0_STOPPG); + /* Clear the pending interrupts and mask. */ + outb_p(0xFF, e8390_base + EN0_ISR); + outb_p(0x00, e8390_base + EN0_IMR); + + /* Copy the station address into the DS8390 registers. */ + + outb_p(E8390_NODMA + E8390_PAGE1 + E8390_STOP, e8390_base+E8390_CMD); /* 0x61 */ + for(i = 0; i < 6; i++) + { + outb_p(dev->dev_addr[i], e8390_base + EN1_PHYS_SHIFT(i)); + if(inb_p(e8390_base + EN1_PHYS_SHIFT(i))!=dev->dev_addr[i]) + printk(KERN_ERR "Hw. address read/write mismap %d\n",i); + } + /* + * Initialize the multicast list to accept-all. If we enable multicast + * the higher levels can do the filtering. + */ + for (i = 0; i < 8; i++) + outb_p(0xff, e8390_base + EN1_MULT + i); + + outb_p(ei_local->rx_start_page, e8390_base + EN1_CURPAG); + outb_p(E8390_NODMA+E8390_PAGE0+E8390_STOP, e8390_base+E8390_CMD); + + netif_start_queue(dev); + ei_local->tx1 = ei_local->tx2 = 0; + ei_local->txing = 0; + + if (startp) + { + outb_p(0xff, e8390_base + EN0_ISR); + outb_p(ENISR_ALL, e8390_base + EN0_IMR); + outb_p(E8390_NODMA+E8390_PAGE0+E8390_START, e8390_base+E8390_CMD); + outb_p(E8390_TXCONFIG | info->duplex_flag, + e8390_base + EN0_TXCR); /* xmit on. */ + /* 3c503 TechMan says rxconfig only after the NIC is started. */ + outb_p(E8390_RXCONFIG | 0x40, e8390_base + EN0_RXCR); /* rx on, */ + do_set_multicast_list(dev); /* (re)load the mcast table */ + } +} + +/* Trigger a transmit start, assuming the length is valid. + Always called with the page lock held */ + +static void NS8390_trigger_send(struct net_device *dev, unsigned int length, + int start_page) +{ + long e8390_base = dev->base_addr; + struct ei_device *ei_local __attribute((unused)) = (struct ei_device *) dev->priv; + + if (inb_p(e8390_base) & E8390_TRANS) + { + printk(KERN_WARNING "%s: trigger_send() called with the transmitter busy.\n", + dev->name); + return; + } + outb_p(length & 0xff, e8390_base + EN0_TCNTLO); + outb_p(length >> 8, e8390_base + EN0_TCNTHI); + outb_p(start_page, e8390_base + EN0_TPSR); + outb_p(E8390_NODMA+E8390_TRANS+E8390_START, e8390_base+E8390_CMD); +} diff --git a/linux/pcmcia-cs/clients/fmvj18x_cs.c b/linux/pcmcia-cs/clients/fmvj18x_cs.c new file mode 100644 index 0000000..bd492e8 --- /dev/null +++ b/linux/pcmcia-cs/clients/fmvj18x_cs.c @@ -0,0 +1,1322 @@ +/*====================================================================== + fmvj18x_cs.c 2.8 2002/03/23 + + A fmvj18x (and its compatibles) PCMCIA client driver + + Contributed by Shingo Fujimoto, shingo@flab.fujitsu.co.jp + + TDK LAK-CD021 and CONTEC C-NET(PC)C support added by + Nobuhiro Katayama, kata-n@po.iijnet.or.jp + + The PCMCIA client code is based on code written by David Hinds. + Network code is based on the "FMV-18x driver" by Yutaka TAMIYA + but is actually largely Donald Becker's AT1700 driver, which + carries the following attribution: + + Written 1993-94 by Donald Becker. + + Copyright 1993 United States Government as represented by the + Director, National Security Agency. + + This software may be used and distributed according to the terms + of the GNU General Public License, incorporated herein by reference. + + The author may be reached as becker@scyld.com, or C/O + Scyld Computing Corporation + 410 Severn Ave., Suite 210 + Annapolis MD 21403 + +======================================================================*/ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/ptrace.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/in.h> +#include <linux/delay.h> +#include <asm/io.h> +#include <asm/system.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/if_arp.h> +#include <linux/ioport.h> +#include <linux/crc32.h> + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/ciscode.h> +#include <pcmcia/ds.h> + +/*====================================================================*/ + +/* Module parameters */ + +MODULE_DESCRIPTION("fmvj18x and compatible PCMCIA ethernet driver"); +MODULE_LICENSE("GPL"); + +#define INT_MODULE_PARM(n, v) static int n = v; MODULE_PARM(n, "i") + +/* Bit map of interrupts to choose from */ +/* This means pick from 15, 14, 12, 11, 10, 9, 7, 5, 4, and 3 */ +INT_MODULE_PARM(irq_mask, 0xdeb8); +static int irq_list[4] = { -1 }; +MODULE_PARM(irq_list, "1-4i"); + +/* SRAM configuration */ +/* 0:4KB*2 TX buffer else:8KB*2 TX buffer */ +INT_MODULE_PARM(sram_config, 0); + +#ifdef PCMCIA_DEBUG +INT_MODULE_PARM(pc_debug, PCMCIA_DEBUG); +#define DEBUG(n, args...) if (pc_debug>(n)) printk(KERN_DEBUG args) +static char *version = "fmvj18x_cs.c 2.8 2002/03/23"; +#else +#define DEBUG(n, args...) +#endif + +/*====================================================================*/ +/* + PCMCIA event handlers + */ +static void fmvj18x_config(dev_link_t *link); +static int fmvj18x_get_hwinfo(dev_link_t *link, u_char *node_id); +static int fmvj18x_setup_mfc(dev_link_t *link); +static void fmvj18x_release(u_long arg); +static int fmvj18x_event(event_t event, int priority, + event_callback_args_t *args); +static dev_link_t *fmvj18x_attach(void); +static void fmvj18x_detach(dev_link_t *); + +/* + LAN controller(MBH86960A) specific routines + */ +static int fjn_config(struct net_device *dev, struct ifmap *map); +static int fjn_open(struct net_device *dev); +static int fjn_close(struct net_device *dev); +static int fjn_start_xmit(struct sk_buff *skb, struct net_device *dev); +static void fjn_interrupt(int irq, void *dev_id, struct pt_regs *regs); +static void fjn_rx(struct net_device *dev); +static void fjn_reset(struct net_device *dev); +static struct net_device_stats *fjn_get_stats(struct net_device *dev); +static void set_rx_mode(struct net_device *dev); +static void fjn_tx_timeout(struct net_device *dev); + +static dev_info_t dev_info = "fmvj18x_cs"; +static dev_link_t *dev_list; + +/* + card type + */ +typedef enum { MBH10302, MBH10304, TDK, CONTEC, LA501, UNGERMANN, + XXX10304 +} cardtype_t; + +/* + driver specific data structure +*/ +typedef struct local_info_t { + dev_link_t link; + struct net_device dev; + dev_node_t node; + struct net_device_stats stats; + long open_time; + uint tx_started:1; + uint tx_queue; + u_short tx_queue_len; + cardtype_t cardtype; + u_short sent; + u_char mc_filter[8]; +} local_info_t; + +#define MC_FILTERBREAK 64 + +/*====================================================================*/ +/* + ioport offset from the base address + */ +#define TX_STATUS 0 /* transmit status register */ +#define RX_STATUS 1 /* receive status register */ +#define TX_INTR 2 /* transmit interrupt mask register */ +#define RX_INTR 3 /* receive interrupt mask register */ +#define TX_MODE 4 /* transmit mode register */ +#define RX_MODE 5 /* receive mode register */ +#define CONFIG_0 6 /* configuration register 0 */ +#define CONFIG_1 7 /* configuration register 1 */ + +#define NODE_ID 8 /* node ID register (bank 0) */ +#define MAR_ADR 8 /* multicast address registers (bank 1) */ + +#define DATAPORT 8 /* buffer mem port registers (bank 2) */ +#define TX_START 10 /* transmit start register */ +#define COL_CTRL 11 /* 16 collision control register */ +#define BMPR12 12 /* reserved */ +#define BMPR13 13 /* reserved */ +#define RX_SKIP 14 /* skip received packet register */ + +#define LAN_CTRL 16 /* LAN card control register */ + +#define MAC_ID 0x1a /* hardware address */ +#define UNGERMANN_MAC_ID 0x18 /* UNGERMANN-BASS hardware address */ + +/* + control bits + */ +#define ENA_TMT_OK 0x80 +#define ENA_TMT_REC 0x20 +#define ENA_COL 0x04 +#define ENA_16_COL 0x02 +#define ENA_TBUS_ERR 0x01 + +#define ENA_PKT_RDY 0x80 +#define ENA_BUS_ERR 0x40 +#define ENA_LEN_ERR 0x08 +#define ENA_ALG_ERR 0x04 +#define ENA_CRC_ERR 0x02 +#define ENA_OVR_FLO 0x01 + +/* flags */ +#define F_TMT_RDY 0x80 /* can accept new packet */ +#define F_NET_BSY 0x40 /* carrier is detected */ +#define F_TMT_OK 0x20 /* send packet successfully */ +#define F_SRT_PKT 0x10 /* short packet error */ +#define F_COL_ERR 0x04 /* collision error */ +#define F_16_COL 0x02 /* 16 collision error */ +#define F_TBUS_ERR 0x01 /* bus read error */ + +#define F_PKT_RDY 0x80 /* packet(s) in buffer */ +#define F_BUS_ERR 0x40 /* bus read error */ +#define F_LEN_ERR 0x08 /* short packet */ +#define F_ALG_ERR 0x04 /* frame error */ +#define F_CRC_ERR 0x02 /* CRC error */ +#define F_OVR_FLO 0x01 /* overflow error */ + +#define F_BUF_EMP 0x40 /* receive buffer is empty */ + +#define F_SKP_PKT 0x05 /* drop packet in buffer */ + +/* default bitmaps */ +#define D_TX_INTR ( ENA_TMT_OK ) +#define D_RX_INTR ( ENA_PKT_RDY | ENA_LEN_ERR \ + | ENA_ALG_ERR | ENA_CRC_ERR | ENA_OVR_FLO ) +#define TX_STAT_M ( F_TMT_RDY ) +#define RX_STAT_M ( F_PKT_RDY | F_LEN_ERR \ + | F_ALG_ERR | F_CRC_ERR | F_OVR_FLO ) + +/* commands */ +#define D_TX_MODE 0x06 /* no tests, detect carrier */ +#define ID_MATCHED 0x02 /* (RX_MODE) */ +#define RECV_ALL 0x03 /* (RX_MODE) */ +#define CONFIG0_DFL 0x5a /* 16bit bus, 4K x 2 Tx queues */ +#define CONFIG0_DFL_1 0x5e /* 16bit bus, 8K x 2 Tx queues */ +#define CONFIG0_RST 0xda /* Data Link Controller off (CONFIG_0) */ +#define CONFIG0_RST_1 0xde /* Data Link Controller off (CONFIG_0) */ +#define BANK_0 0xa0 /* bank 0 (CONFIG_1) */ +#define BANK_1 0xa4 /* bank 1 (CONFIG_1) */ +#define BANK_2 0xa8 /* bank 2 (CONFIG_1) */ +#define CHIP_OFF 0x80 /* contrl chip power off (CONFIG_1) */ +#define DO_TX 0x80 /* do transmit packet */ +#define SEND_PKT 0x81 /* send a packet */ +#define AUTO_MODE 0x07 /* Auto skip packet on 16 col detected */ +#define MANU_MODE 0x03 /* Stop and skip packet on 16 col */ +#define TDK_AUTO_MODE 0x47 /* Auto skip packet on 16 col detected */ +#define TDK_MANU_MODE 0x43 /* Stop and skip packet on 16 col */ +#define INTR_OFF 0x0d /* LAN controller ignores interrupts */ +#define INTR_ON 0x1d /* LAN controller will catch interrupts */ + +#define TX_TIMEOUT ((400*HZ)/1000) + +#define BANK_0U 0x20 /* bank 0 (CONFIG_1) */ +#define BANK_1U 0x24 /* bank 1 (CONFIG_1) */ +#define BANK_2U 0x28 /* bank 2 (CONFIG_1) */ + +/*====================================================================== + + This bit of code is used to avoid unregistering network devices + at inappropriate times. 2.2 and later kernels are fairly picky + about when this can happen. + +======================================================================*/ + +static void flush_stale_links(void) +{ + dev_link_t *link, *next; + for (link = dev_list; link; link = next) { + next = link->next; + if (link->state & DEV_STALE_LINK) + fmvj18x_detach(link); + } +} + +/*====================================================================*/ + +static void cs_error(client_handle_t handle, int func, int ret) +{ + error_info_t err = { func, ret }; + CardServices(ReportError, handle, &err); +} + +/*====================================================================*/ + +static dev_link_t *fmvj18x_attach(void) +{ + local_info_t *lp; + dev_link_t *link; + struct net_device *dev; + client_reg_t client_reg; + int i, ret; + + DEBUG(0, "fmvj18x_attach()\n"); + flush_stale_links(); + + /* Make up a FMVJ18x specific data structure */ + lp = kmalloc(sizeof(*lp), GFP_KERNEL); + if (!lp) return NULL; + memset(lp, 0, sizeof(*lp)); + link = &lp->link; dev = &lp->dev; + link->priv = dev->priv = link->irq.Instance = lp; + + init_timer(&link->release); + link->release.function = &fmvj18x_release; + link->release.data = (u_long)link; + + /* The io structure describes IO port mapping */ + link->io.NumPorts1 = 32; + link->io.Attributes1 = IO_DATA_PATH_WIDTH_AUTO; + link->io.IOAddrLines = 5; + + /* Interrupt setup */ + link->irq.Attributes = IRQ_TYPE_EXCLUSIVE | IRQ_HANDLE_PRESENT; + link->irq.IRQInfo1 = IRQ_INFO2_VALID|IRQ_LEVEL_ID; + if (irq_list[0] == -1) + link->irq.IRQInfo2 = irq_mask; + else + for (i = 0; i < 4; i++) + link->irq.IRQInfo2 |= 1 << irq_list[i]; + link->irq.Handler = &fjn_interrupt; + + /* General socket configuration */ + link->conf.Attributes = CONF_ENABLE_IRQ; + link->conf.Vcc = 50; + link->conf.IntType = INT_MEMORY_AND_IO; + + /* The FMVJ18x specific entries in the device structure. */ + dev->hard_start_xmit = &fjn_start_xmit; + dev->set_config = &fjn_config; + dev->get_stats = &fjn_get_stats; + dev->set_multicast_list = &set_rx_mode; + ether_setup(dev); + init_dev_name(dev, lp->node); + dev->open = &fjn_open; + dev->stop = &fjn_close; +#ifdef HAVE_TX_TIMEOUT + dev->tx_timeout = fjn_tx_timeout; + dev->watchdog_timeo = TX_TIMEOUT; +#endif + + /* 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 = &fmvj18x_event; + client_reg.Version = 0x0210; + client_reg.event_callback_args.client_data = link; + ret = CardServices(RegisterClient, &link->handle, &client_reg); + if (ret != 0) { + cs_error(link->handle, RegisterClient, ret); + fmvj18x_detach(link); + return NULL; + } + + return link; +} /* fmvj18x_attach */ + +/*====================================================================*/ + +static void fmvj18x_detach(dev_link_t *link) +{ + local_info_t *lp = link->priv; + dev_link_t **linkp; + + DEBUG(0, "fmvj18x_detach(0x%p)\n", link); + + /* Locate device structure */ + for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next) + if (*linkp == link) break; + if (*linkp == NULL) + return; + + del_timer(&link->release); + if (link->state & DEV_CONFIG) { + fmvj18x_release((u_long)link); + if (link->state & DEV_STALE_CONFIG) { + 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->dev) + unregister_netdev(&lp->dev); + kfree(lp); + +} /* fmvj18x_detach */ + +/*====================================================================*/ + +#define CS_CHECK(fn, args...) \ +while ((last_ret=CardServices(last_fn=(fn), args))!=0) goto cs_failed + +static int mfc_try_io_port(dev_link_t *link) +{ + int i, ret; + static ioaddr_t serial_base[5] = { 0x3f8, 0x2f8, 0x3e8, 0x2e8, 0x0 }; + + for (i = 0; i < 5; i++) { + link->io.BasePort2 = serial_base[i]; + link->io.Attributes2 = IO_DATA_PATH_WIDTH_8; + if (link->io.BasePort2 == 0) { + link->io.NumPorts2 = 0; + printk(KERN_NOTICE "fmvj18x_cs: out of resource for serial\n"); + } + ret = CardServices(RequestIO, link->handle, &link->io); + if (ret == CS_SUCCESS) return ret; + } + return ret; +} + +static int ungermann_try_io_port(dev_link_t *link) +{ + int ret; + ioaddr_t ioaddr; + /* + Ungermann-Bass Access/CARD accepts 0x300,0x320,0x340,0x360 + 0x380,0x3c0 only for ioport. + */ + for (ioaddr = 0x300; ioaddr < 0x3e0; ioaddr += 0x20) { + link->io.BasePort1 = ioaddr; + ret = CardServices(RequestIO, link->handle, &link->io); + if (ret == CS_SUCCESS) { + /* calculate ConfigIndex value */ + link->conf.ConfigIndex = + ((link->io.BasePort1 & 0x0f0) >> 3) | 0x22; + return ret; + } + } + return ret; /* RequestIO failed */ +} + +static void fmvj18x_config(dev_link_t *link) +{ + client_handle_t handle = link->handle; + local_info_t *lp = link->priv; + struct net_device *dev = &lp->dev; + tuple_t tuple; + cisparse_t parse; + u_short buf[32]; + int i, last_fn, last_ret, ret; + ioaddr_t ioaddr; + cardtype_t cardtype; + char *card_name = "unknown"; + u_char *node_id; + + DEBUG(0, "fmvj18x_config(0x%p)\n", link); + + /* + This reads the card's CONFIG tuple to find its configuration + registers. + */ + tuple.DesiredTuple = CISTPL_CONFIG; + CS_CHECK(GetFirstTuple, handle, &tuple); + tuple.TupleData = (u_char *)buf; + tuple.TupleDataMax = 64; + tuple.TupleOffset = 0; + CS_CHECK(GetTupleData, handle, &tuple); + CS_CHECK(ParseTuple, handle, &tuple, &parse); + + /* Configure card */ + link->state |= DEV_CONFIG; + + link->conf.ConfigBase = parse.config.base; + link->conf.Present = parse.config.rmask[0]; + + tuple.DesiredTuple = CISTPL_FUNCE; + tuple.TupleOffset = 0; + if (CardServices(GetFirstTuple, handle, &tuple) == CS_SUCCESS) { + /* Yes, I have CISTPL_FUNCE. Let's check CISTPL_MANFID */ + tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY; + CS_CHECK(GetFirstTuple, handle, &tuple); + CS_CHECK(GetTupleData, handle, &tuple); + CS_CHECK(ParseTuple, handle, &tuple, &parse); + link->conf.ConfigIndex = parse.cftable_entry.index; + tuple.DesiredTuple = CISTPL_MANFID; + if (CardServices(GetFirstTuple, handle, &tuple) == CS_SUCCESS) + CS_CHECK(GetTupleData, handle, &tuple); + else + buf[0] = 0xffff; + switch (le16_to_cpu(buf[0])) { + case MANFID_TDK: + cardtype = TDK; + if (le16_to_cpu(buf[1]) == PRODID_TDK_CF010) { + cs_status_t status; + CardServices(GetStatus, handle, &status); + if (status.CardState & CS_EVENT_3VCARD) + link->conf.Vcc = 33; /* inserted in 3.3V slot */ + } else if (le16_to_cpu(buf[1]) == PRODID_TDK_GN3410) { + /* MultiFunction Card */ + link->conf.ConfigBase = 0x800; + link->conf.ConfigIndex = 0x47; + link->io.NumPorts2 = 8; + } + break; + case MANFID_CONTEC: + cardtype = CONTEC; + break; + case MANFID_FUJITSU: + if (le16_to_cpu(buf[1]) == PRODID_FUJITSU_MBH10302) + /* RATOC REX-5588/9822/4886's PRODID are 0004(=MBH10302), + but these are MBH10304 based card. */ + cardtype = MBH10304; + else if (le16_to_cpu(buf[1]) == PRODID_FUJITSU_MBH10304) + cardtype = MBH10304; + else + cardtype = LA501; + break; + default: + cardtype = MBH10304; + } + } else { + /* old type card */ + tuple.DesiredTuple = CISTPL_MANFID; + if (CardServices(GetFirstTuple, handle, &tuple) == CS_SUCCESS) + CS_CHECK(GetTupleData, handle, &tuple); + else + buf[0] = 0xffff; + switch (le16_to_cpu(buf[0])) { + case MANFID_FUJITSU: + if (le16_to_cpu(buf[1]) == PRODID_FUJITSU_MBH10304) { + cardtype = XXX10304; /* MBH10304 with buggy CIS */ + link->conf.ConfigIndex = 0x20; + } else { + cardtype = MBH10302; /* NextCom NC5310, etc. */ + link->conf.ConfigIndex = 1; + } + break; + case MANFID_UNGERMANN: + cardtype = UNGERMANN; + break; + default: + cardtype = MBH10302; + link->conf.ConfigIndex = 1; + } + } + + if (link->io.NumPorts2 != 0) { + link->irq.Attributes = + IRQ_TYPE_DYNAMIC_SHARING|IRQ_FIRST_SHARED|IRQ_HANDLE_PRESENT; + ret = mfc_try_io_port(link); + if (ret != CS_SUCCESS) goto cs_failed; + } else if (cardtype == UNGERMANN) { + ret = ungermann_try_io_port(link); + if (ret != CS_SUCCESS) goto cs_failed; + } else { + CS_CHECK(RequestIO, link->handle, &link->io); + } + CS_CHECK(RequestIRQ, link->handle, &link->irq); + CS_CHECK(RequestConfiguration, link->handle, &link->conf); + dev->irq = link->irq.AssignedIRQ; + dev->base_addr = link->io.BasePort1; + if (register_netdev(dev) != 0) { + printk(KERN_NOTICE "fmvj18x_cs: register_netdev() failed\n"); + goto failed; + } + + if (link->io.BasePort2 != 0) + fmvj18x_setup_mfc(link); + + ioaddr = dev->base_addr; + + /* Reset controller */ + if (sram_config == 0) + outb(CONFIG0_RST, ioaddr + CONFIG_0); + else + outb(CONFIG0_RST_1, ioaddr + CONFIG_0); + + /* Power On chip and select bank 0 */ + if (cardtype == MBH10302) + outb(BANK_0, ioaddr + CONFIG_1); + else + outb(BANK_0U, ioaddr + CONFIG_1); + + /* Set hardware address */ + switch (cardtype) { + case MBH10304: + case TDK: + case LA501: + case CONTEC: + tuple.DesiredTuple = CISTPL_FUNCE; + tuple.TupleOffset = 0; + CS_CHECK(GetFirstTuple, handle, &tuple); + tuple.TupleOffset = 0; + CS_CHECK(GetTupleData, handle, &tuple); + if (cardtype == MBH10304) { + /* MBH10304's CIS_FUNCE is corrupted */ + node_id = &(tuple.TupleData[5]); + card_name = "FMV-J182"; + } else { + while (tuple.TupleData[0] != CISTPL_FUNCE_LAN_NODE_ID ) { + CS_CHECK(GetNextTuple, handle, &tuple) ; + CS_CHECK(GetTupleData, handle, &tuple) ; + } + node_id = &(tuple.TupleData[2]); + if( cardtype == TDK ) { + card_name = "TDK LAK-CD021"; + } else if( cardtype == LA501 ) { + card_name = "LA501"; + } else { + card_name = "C-NET(PC)C"; + } + } + /* Read MACID from CIS */ + for (i = 0; i < 6; i++) + dev->dev_addr[i] = node_id[i]; + break; + case UNGERMANN: + /* Read MACID from register */ + for (i = 0; i < 6; i++) + dev->dev_addr[i] = inb(ioaddr + UNGERMANN_MAC_ID + i); + card_name = "Access/CARD"; + break; + case XXX10304: + /* Read MACID from Buggy CIS */ + if (fmvj18x_get_hwinfo(link, tuple.TupleData) == -1) { + printk(KERN_NOTICE "fmvj18x_cs: unable to read hardware net address."); + unregister_netdev(dev); + goto failed; + } + for (i = 0 ; i < 6; i++) { + dev->dev_addr[i] = tuple.TupleData[i]; + } + card_name = "FMV-J182"; + break; + case MBH10302: + default: + /* Read MACID from register */ + for (i = 0; i < 6; i++) + dev->dev_addr[i] = inb(ioaddr + MAC_ID + i); + card_name = "FMV-J181"; + break; + } + + copy_dev_name(lp->node, dev); + link->dev = &lp->node; + + lp->cardtype = cardtype; + /* print current configuration */ + printk(KERN_INFO "%s: %s, sram %s, port %#3lx, irq %d, hw_addr ", + dev->name, card_name, sram_config == 0 ? "4K TX*2" : "8K TX*2", + dev->base_addr, dev->irq); + for (i = 0; i < 6; i++) + printk("%02X%s", dev->dev_addr[i], ((i<5) ? ":" : "\n")); + + link->state &= ~DEV_CONFIG_PENDING; + return; + +cs_failed: + /* All Card Services errors end up here */ + cs_error(link->handle, last_fn, last_ret); +failed: + fmvj18x_release((u_long)link); + link->state &= ~DEV_CONFIG_PENDING; + +} /* fmvj18x_config */ +/*====================================================================*/ + +static int fmvj18x_get_hwinfo(dev_link_t *link, u_char *node_id) +{ + win_req_t req; + memreq_t mem; + u_char *base; + int i, j; + + /* Allocate a small memory window */ + req.Attributes = WIN_DATA_WIDTH_8|WIN_MEMORY_TYPE_AM|WIN_ENABLE; + req.Base = 0; req.Size = 0; + req.AccessSpeed = 0; + link->win = (window_handle_t)link->handle; + i = CardServices(RequestWindow, &link->win, &req); + if (i != CS_SUCCESS) { + cs_error(link->handle, RequestWindow, i); + return -1; + } + + base = ioremap(req.Base, req.Size); + mem.Page = 0; + mem.CardOffset = 0; + CardServices(MapMemPage, link->win, &mem); + + /* + * MBH10304 CISTPL_FUNCE_LAN_NODE_ID format + * 22 0d xx xx xx 04 06 yy yy yy yy yy yy ff + * 'xx' is garbage. + * 'yy' is MAC address. + */ + for (i = 0; i < 0x200; i++) { + if (readb(base+i*2) == 0x22) { + if (readb(base+(i-1)*2) == 0xff + && readb(base+(i+5)*2) == 0x04 + && readb(base+(i+6)*2) == 0x06 + && readb(base+(i+13)*2) == 0xff) + break; + } + } + + if (i != 0x200) { + for (j = 0 ; j < 6; j++,i++) { + node_id[j] = readb(base+(i+7)*2); + } + } + + iounmap(base); + j = CardServices(ReleaseWindow, link->win); + if (j != CS_SUCCESS) + cs_error(link->handle, ReleaseWindow, j); + return (i != 0x200) ? 0 : -1; + +} /* fmvj18x_get_hwinfo */ +/*====================================================================*/ + +static int fmvj18x_setup_mfc(dev_link_t *link) +{ + win_req_t req; + memreq_t mem; + u_char *base; + int i, j; + local_info_t *lp = link->priv; + struct net_device *dev = &lp->dev; + ioaddr_t ioaddr; + + /* Allocate a small memory window */ + req.Attributes = WIN_DATA_WIDTH_8|WIN_MEMORY_TYPE_AM|WIN_ENABLE; + req.Base = 0; req.Size = 0; + req.AccessSpeed = 0; + link->win = (window_handle_t)link->handle; + i = CardServices(RequestWindow, &link->win, &req); + if (i != CS_SUCCESS) { + cs_error(link->handle, RequestWindow, i); + return -1; + } + + base = ioremap(req.Base, req.Size); + mem.Page = 0; + mem.CardOffset = 0; + CardServices(MapMemPage, link->win, &mem); + + ioaddr = dev->base_addr; + writeb(0x47, base+0x800); /* Config Option Register of LAN */ + writeb(0x0, base+0x802); /* Config and Status Register */ + + writeb(ioaddr & 0xff, base+0x80a); /* I/O Base(Low) of LAN */ + writeb((ioaddr >> 8) & 0xff, base+0x80c); /* I/O Base(High) of LAN */ + + writeb(0x45, base+0x820); /* Config Option Register of Modem */ + writeb(0x8, base+0x822); /* Config and Status Register */ + + iounmap(base); + j = CardServices(ReleaseWindow, link->win); + if (j != CS_SUCCESS) + cs_error(link->handle, ReleaseWindow, j); + return 0; + +} +/*====================================================================*/ + +static void fmvj18x_release(u_long arg) +{ + dev_link_t *link = (dev_link_t *)arg; + + DEBUG(0, "fmvj18x_release(0x%p)\n", link); + + /* + If the device is currently in use, we won't release until it + is actually closed. + */ + if (link->open) { + DEBUG(1, "fmvj18x_cs: release postponed, '%s' " + "still open\n", link->dev->dev_name); + link->state |= DEV_STALE_CONFIG; + return; + } + + /* 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); + + link->state &= ~DEV_CONFIG; + +} /* fmvj18x_release */ + +/*====================================================================*/ + +static int fmvj18x_event(event_t event, int priority, + event_callback_args_t *args) +{ + dev_link_t *link = args->client_data; + local_info_t *lp = link->priv; + struct net_device *dev = &lp->dev; + + DEBUG(1, "fmvj18x_event(0x%06x)\n", event); + + switch (event) { + case CS_EVENT_CARD_REMOVAL: + link->state &= ~DEV_PRESENT; + if (link->state & DEV_CONFIG) { + netif_device_detach(dev); + mod_timer(&link->release, jiffies + HZ/20); + } + break; + case CS_EVENT_CARD_INSERTION: + link->state |= DEV_PRESENT | DEV_CONFIG_PENDING; + fmvj18x_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) + netif_device_detach(dev); + 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) { + fjn_reset(dev); + netif_device_attach(dev); + } + } + break; + } + return 0; +} /* fmvj18x_event */ + +/*====================================================================*/ + +static int __init init_fmvj18x_cs(void) +{ + servinfo_t serv; + DEBUG(0, "%s\n", version); + CardServices(GetCardServicesInfo, &serv); + if (serv.Revision != CS_RELEASE_CODE) { + printk(KERN_NOTICE "fmvj18x: Card Services release " + "does not match!\n"); + return -EINVAL; + } + register_pccard_driver(&dev_info, &fmvj18x_attach, &fmvj18x_detach); + return 0; +} + +static void __exit exit_fmvj18x_cs(void) +{ + DEBUG(0, "fmvj18x_cs: unloading\n"); + unregister_pccard_driver(&dev_info); + while (dev_list != NULL) + fmvj18x_detach(dev_list); +} + +module_init(init_fmvj18x_cs); +module_exit(exit_fmvj18x_cs); + +/*====================================================================*/ + +static void fjn_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + local_info_t *lp = dev_id; + struct net_device *dev = &lp->dev; + ioaddr_t ioaddr; + unsigned short tx_stat, rx_stat; + + if (lp == NULL) { + printk(KERN_NOTICE "fjn_interrupt(): irq %d for " + "unknown device.\n", irq); + return; + } + ioaddr = dev->base_addr; + + /* avoid multiple interrupts */ + outw(0x0000, ioaddr + TX_INTR); + + /* wait for a while */ + udelay(1); + + /* get status */ + tx_stat = inb(ioaddr + TX_STATUS); + rx_stat = inb(ioaddr + RX_STATUS); + + /* clear status */ + outb(tx_stat, ioaddr + TX_STATUS); + outb(rx_stat, ioaddr + RX_STATUS); + + DEBUG(4, "%s: interrupt, rx_status %02x.\n", dev->name, rx_stat); + DEBUG(4, " tx_status %02x.\n", tx_stat); + + if (rx_stat || (inb(ioaddr + RX_MODE) & F_BUF_EMP) == 0) { + /* there is packet(s) in rx buffer */ + fjn_rx(dev); + } + if (tx_stat & F_TMT_RDY) { + lp->stats.tx_packets += lp->sent ; + lp->sent = 0 ; + if (lp->tx_queue) { + outb(DO_TX | lp->tx_queue, ioaddr + TX_START); + lp->sent = lp->tx_queue ; + lp->tx_queue = 0; + lp->tx_queue_len = 0; + dev->trans_start = jiffies; + } else { + lp->tx_started = 0; + } + netif_wake_queue(dev); + } + DEBUG(4, "%s: exiting interrupt,\n", dev->name); + DEBUG(4, " tx_status %02x, rx_status %02x.\n", tx_stat, rx_stat); + + outb(D_TX_INTR, ioaddr + TX_INTR); + outb(D_RX_INTR, ioaddr + RX_INTR); + +} /* fjn_interrupt */ + +/*====================================================================*/ + +static void fjn_tx_timeout(struct net_device *dev) +{ + struct local_info_t *lp = (struct local_info_t *)dev->priv; + ioaddr_t ioaddr = dev->base_addr; + + printk(KERN_NOTICE "%s: transmit timed out with status %04x, %s?\n", + dev->name, htons(inw(ioaddr + TX_STATUS)), + inb(ioaddr + TX_STATUS) & F_TMT_RDY + ? "IRQ conflict" : "network cable problem"); + printk(KERN_NOTICE "%s: timeout registers: %04x %04x %04x " + "%04x %04x %04x %04x %04x.\n", + dev->name, htons(inw(ioaddr + 0)), + htons(inw(ioaddr + 2)), htons(inw(ioaddr + 4)), + htons(inw(ioaddr + 6)), htons(inw(ioaddr + 8)), + htons(inw(ioaddr +10)), htons(inw(ioaddr +12)), + htons(inw(ioaddr +14))); + lp->stats.tx_errors++; + /* ToDo: We should try to restart the adaptor... */ + cli(); + + fjn_reset(dev); + + lp->tx_started = 0; + lp->tx_queue = 0; + lp->tx_queue_len = 0; + lp->sent = 0; + lp->open_time = jiffies; + sti(); + netif_wake_queue(dev); +} + +static int fjn_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct local_info_t *lp = (struct local_info_t *)dev->priv; + ioaddr_t ioaddr = dev->base_addr; + + tx_timeout_check(dev, fjn_tx_timeout); + skb_tx_check(dev, skb); + + { + short length = ETH_ZLEN < skb->len ? skb->len : ETH_ZLEN; + unsigned char *buf = skb->data; + + if (length > ETH_FRAME_LEN) { + printk(KERN_NOTICE "%s: Attempting to send a large packet" + " (%d bytes).\n", dev->name, length); + return 1; + } + + DEBUG(4, "%s: Transmitting a packet of length %lu.\n", + dev->name, (unsigned long)skb->len); + add_tx_bytes(&lp->stats, skb->len); + + /* Disable both interrupts. */ + outw(0x0000, ioaddr + TX_INTR); + + /* wait for a while */ + udelay(1); + + outw(length, ioaddr + DATAPORT); + outsw(ioaddr + DATAPORT, buf, (length + 1) >> 1); + + lp->tx_queue++; + lp->tx_queue_len += ((length+3) & ~1); + + if (lp->tx_started == 0) { + /* If the Tx is idle, always trigger a transmit. */ + outb(DO_TX | lp->tx_queue, ioaddr + TX_START); + lp->sent = lp->tx_queue ; + lp->tx_queue = 0; + lp->tx_queue_len = 0; + dev->trans_start = jiffies; + lp->tx_started = 1; + netif_start_queue(dev); + } else { + if( sram_config == 0 ) { + if (lp->tx_queue_len < (4096 - (ETH_FRAME_LEN +2)) ) + /* Yes, there is room for one more packet. */ + netif_start_queue(dev); + } else { + if (lp->tx_queue_len < (8192 - (ETH_FRAME_LEN +2)) && + lp->tx_queue < 127 ) + /* Yes, there is room for one more packet. */ + netif_start_queue(dev); + } + } + + /* Re-enable interrupts */ + outb(D_TX_INTR, ioaddr + TX_INTR); + outb(D_RX_INTR, ioaddr + RX_INTR); + } + DEV_KFREE_SKB (skb); + + return 0; +} /* fjn_start_xmit */ + +/*====================================================================*/ + +static void fjn_reset(struct net_device *dev) +{ + struct local_info_t *lp = (struct local_info_t *)dev->priv; + ioaddr_t ioaddr = dev->base_addr; + int i; + + DEBUG(4, "fjn_reset(%s) called.\n",dev->name); + + /* Reset controller */ + if( sram_config == 0 ) + outb(CONFIG0_RST, ioaddr + CONFIG_0); + else + outb(CONFIG0_RST_1, ioaddr + CONFIG_0); + + /* Power On chip and select bank 0 */ + if (lp->cardtype == MBH10302) + outb(BANK_0, ioaddr + CONFIG_1); + else + outb(BANK_0U, ioaddr + CONFIG_1); + + /* Set Tx modes */ + outb(D_TX_MODE, ioaddr + TX_MODE); + /* set Rx modes */ + outb(ID_MATCHED, ioaddr + RX_MODE); + + /* Set hardware address */ + for (i = 0; i < 6; i++) + outb(dev->dev_addr[i], ioaddr + NODE_ID + i); + + /* Switch to bank 1 */ + if (lp->cardtype == MBH10302) + outb(BANK_1, ioaddr + CONFIG_1); + else + outb(BANK_1U, ioaddr + CONFIG_1); + + /* set the multicast table to accept none. */ + for (i = 0; i < 6; i++) + outb(0x00, ioaddr + MAR_ADR + i); + + /* Switch to bank 2 (runtime mode) */ + if (lp->cardtype == MBH10302) + outb(BANK_2, ioaddr + CONFIG_1); + else + outb(BANK_2U, ioaddr + CONFIG_1); + + /* set 16col ctrl bits */ + if( lp->cardtype == TDK || lp->cardtype == CONTEC) + outb(TDK_AUTO_MODE, ioaddr + COL_CTRL); + else + outb(AUTO_MODE, ioaddr + COL_CTRL); + + /* clear Reserved Regs */ + outb(0x00, ioaddr + BMPR12); + outb(0x00, ioaddr + BMPR13); + + /* reset Skip packet reg. */ + outb(0x01, ioaddr + RX_SKIP); + + /* Enable Tx and Rx */ + if( sram_config == 0 ) + outb(CONFIG0_DFL, ioaddr + CONFIG_0); + else + outb(CONFIG0_DFL_1, ioaddr + CONFIG_0); + + /* Init receive pointer ? */ + inw(ioaddr + DATAPORT); + inw(ioaddr + DATAPORT); + + /* Clear all status */ + outb(0xff, ioaddr + TX_STATUS); + outb(0xff, ioaddr + RX_STATUS); + + if (lp->cardtype == MBH10302) + outb(INTR_OFF, ioaddr + LAN_CTRL); + + /* Turn on Rx interrupts */ + outb(D_TX_INTR, ioaddr + TX_INTR); + outb(D_RX_INTR, ioaddr + RX_INTR); + + /* Turn on interrupts from LAN card controller */ + if (lp->cardtype == MBH10302) + outb(INTR_ON, ioaddr + LAN_CTRL); +} /* fjn_reset */ + +/*====================================================================*/ + +static void fjn_rx(struct net_device *dev) +{ + struct local_info_t *lp = (struct local_info_t *)dev->priv; + ioaddr_t ioaddr = dev->base_addr; + int boguscount = 10; /* 5 -> 10: by agy 19940922 */ + + DEBUG(4, "%s: in rx_packet(), rx_status %02x.\n", + dev->name, inb(ioaddr + RX_STATUS)); + + while ((inb(ioaddr + RX_MODE) & F_BUF_EMP) == 0) { + u_short status = inw(ioaddr + DATAPORT); + + DEBUG(4, "%s: Rxing packet mode %02x status %04x.\n", + dev->name, inb(ioaddr + RX_MODE), status); +#ifndef final_version + if (status == 0) { + outb(F_SKP_PKT, ioaddr + RX_SKIP); + break; + } +#endif + if ((status & 0xF0) != 0x20) { /* There was an error. */ + lp->stats.rx_errors++; + if (status & F_LEN_ERR) lp->stats.rx_length_errors++; + if (status & F_ALG_ERR) lp->stats.rx_frame_errors++; + if (status & F_CRC_ERR) lp->stats.rx_crc_errors++; + if (status & F_OVR_FLO) lp->stats.rx_over_errors++; + } else { + u_short pkt_len = inw(ioaddr + DATAPORT); + /* Malloc up new buffer. */ + struct sk_buff *skb; + + if (pkt_len > 1550) { + printk(KERN_NOTICE "%s: The FMV-18x claimed a very " + "large packet, size %d.\n", dev->name, pkt_len); + outb(F_SKP_PKT, ioaddr + RX_SKIP); + lp->stats.rx_errors++; + break; + } + skb = dev_alloc_skb(pkt_len+2); + if (skb == NULL) { + printk(KERN_NOTICE "%s: Memory squeeze, dropping " + "packet (len %d).\n", dev->name, pkt_len); + outb(F_SKP_PKT, ioaddr + RX_SKIP); + lp->stats.rx_dropped++; + break; + } + skb->dev = dev; + + skb_reserve(skb, 2); + insw(ioaddr + DATAPORT, skb_put(skb, pkt_len), + (pkt_len + 1) >> 1); + skb->protocol = eth_type_trans(skb, dev); + +#ifdef PCMCIA_DEBUG + if (pc_debug > 5) { + int i; + printk(KERN_DEBUG "%s: Rxed packet of length %d: ", + dev->name, pkt_len); + for (i = 0; i < 14; i++) + printk(" %02x", skb->data[i]); + printk(".\n"); + } +#endif + + netif_rx(skb); + dev->last_rx = jiffies; + lp->stats.rx_packets++; + add_rx_bytes(&lp->stats, pkt_len); + } + if (--boguscount <= 0) + break; + } + + /* If any worth-while packets have been received, dev_rint() + has done a netif_wake_queue() for us and will work on them + when we get to the bottom-half routine. */ +/* + if (lp->cardtype != TDK) { + int i; + for (i = 0; i < 20; i++) { + if ((inb(ioaddr + RX_MODE) & F_BUF_EMP) == F_BUF_EMP) + break; + (void)inw(ioaddr + DATAPORT); /+ dummy status read +/ + outb(F_SKP_PKT, ioaddr + RX_SKIP); + } + + if (i > 0) + DEBUG(5, "%s: Exint Rx packet with mode %02x after " + "%d ticks.\n", dev->name, inb(ioaddr + RX_MODE), i); + } +*/ + + return; +} /* fjn_rx */ + +/*====================================================================*/ + +static int fjn_config(struct net_device *dev, struct ifmap *map){ + return 0; +} + +static int fjn_open(struct net_device *dev) +{ + struct local_info_t *lp = (struct local_info_t *)dev->priv; + dev_link_t *link = &lp->link; + + DEBUG(4, "fjn_open('%s').\n", dev->name); + + if (!DEV_OK(link)) + return -ENODEV; + + link->open++; + + fjn_reset(dev); + + lp->tx_started = 0; + lp->tx_queue = 0; + lp->tx_queue_len = 0; + lp->open_time = jiffies; + netif_mark_up(dev); + netif_start_queue(dev); + + MOD_INC_USE_COUNT; + + return 0; +} /* fjn_open */ + +/*====================================================================*/ + +static int fjn_close(struct net_device *dev) +{ + struct local_info_t *lp = (struct local_info_t *)dev->priv; + dev_link_t *link = &lp->link; + ioaddr_t ioaddr = dev->base_addr; + + DEBUG(4, "fjn_close('%s').\n", dev->name); + + lp->open_time = 0; + netif_stop_queue(dev); + netif_mark_down(dev); + + /* Set configuration register 0 to disable Tx and Rx. */ + if( sram_config == 0 ) + outb(CONFIG0_RST ,ioaddr + CONFIG_0); + else + outb(CONFIG0_RST_1 ,ioaddr + CONFIG_0); + + /* Update the statistics -- ToDo. */ + + /* Power-down the chip. Green, green, green! */ + outb(CHIP_OFF ,ioaddr + CONFIG_1); + + /* Set the ethernet adaptor disable IRQ */ + if (lp->cardtype == MBH10302) + outb(INTR_OFF, ioaddr + LAN_CTRL); + + link->open--; + if (link->state & DEV_STALE_CONFIG) + mod_timer(&link->release, jiffies + HZ/20); + MOD_DEC_USE_COUNT; + + return 0; +} /* fjn_close */ + +/*====================================================================*/ + +static struct net_device_stats *fjn_get_stats(struct net_device *dev) +{ + local_info_t *lp = (local_info_t *)dev->priv; + return &lp->stats; +} /* fjn_get_stats */ + +/*====================================================================*/ + +/* + Set the multicast/promiscuous mode for this adaptor. +*/ + +static void set_rx_mode(struct net_device *dev) +{ + ioaddr_t ioaddr = dev->base_addr; + struct local_info_t *lp = (struct local_info_t *)dev->priv; + u_char mc_filter[8]; /* Multicast hash filter */ + u_long flags; + int i; + + if (dev->flags & IFF_PROMISC) { + /* Unconditionally log net taps. */ + printk("%s: Promiscuous mode enabled.\n", dev->name); + memset(mc_filter, 0xff, sizeof(mc_filter)); + outb(3, ioaddr + RX_MODE); /* Enable promiscuous mode */ + } else if (dev->mc_count > MC_FILTERBREAK + || (dev->flags & IFF_ALLMULTI)) { + /* Too many to filter perfectly -- accept all multicasts. */ + memset(mc_filter, 0xff, sizeof(mc_filter)); + outb(2, ioaddr + RX_MODE); /* Use normal mode. */ + } else if (dev->mc_count == 0) { + memset(mc_filter, 0x00, sizeof(mc_filter)); + outb(1, ioaddr + RX_MODE); /* Ignore almost all multicasts. */ + } else { + struct dev_mc_list *mclist; + int i; + + memset(mc_filter, 0, sizeof(mc_filter)); + for (i = 0, mclist = dev->mc_list; mclist && i < dev->mc_count; + i++, mclist = mclist->next) + set_bit(ether_crc_le(ETH_ALEN, mclist->dmi_addr) & 0x3f, + mc_filter); + } + + save_flags(flags); + cli(); + if (memcmp(mc_filter, lp->mc_filter, sizeof(mc_filter))) { + int saved_bank = inb(ioaddr + CONFIG_1); + /* Switch to bank 1 and set the multicast table. */ + outb(0xe4, ioaddr + CONFIG_1); + for (i = 0; i < 8; i++) + outb(mc_filter[i], ioaddr + 8 + i); + memcpy(lp->mc_filter, mc_filter, sizeof(mc_filter)); + outb(saved_bank, ioaddr + CONFIG_1); + } + restore_flags(flags); +} diff --git a/linux/pcmcia-cs/clients/nmclan_cs.c b/linux/pcmcia-cs/clients/nmclan_cs.c new file mode 100644 index 0000000..2f6fb08 --- /dev/null +++ b/linux/pcmcia-cs/clients/nmclan_cs.c @@ -0,0 +1,1744 @@ +/* ---------------------------------------------------------------------------- +Linux PCMCIA ethernet adapter driver for the New Media Ethernet LAN. + nmclan_cs.c,v 0.16 1995/07/01 06:42:17 rpao Exp rpao + + The Ethernet LAN uses the Advanced Micro Devices (AMD) Am79C940 Media + Access Controller for Ethernet (MACE). It is essentially the Am2150 + PCMCIA Ethernet card contained in the Am2150 Demo Kit. + +Written by Roger C. Pao <rpao@paonet.org> + Copyright 1995 Roger C. Pao + + This software may be used and distributed according to the terms of + the GNU General Public License. + +Ported to Linux 1.3.* network driver environment by + Matti Aarnio <mea@utu.fi> + +References + + Am2150 Technical Reference Manual, Revision 1.0, August 17, 1993 + Am79C940 (MACE) Data Sheet, 1994 + Am79C90 (C-LANCE) Data Sheet, 1994 + Linux PCMCIA Programmer's Guide v1.17 + /usr/src/linux/net/inet/dev.c, Linux kernel 1.2.8 + + Eric Mears, New Media Corporation + Tom Pollard, New Media Corporation + Dean Siasoyco, New Media Corporation + Ken Lesniak, Silicon Graphics, Inc. <lesniak@boston.sgi.com> + Donald Becker <becker@scyld.com> + David Hinds <dahinds@users.sourceforge.net> + + The Linux client driver is based on the 3c589_cs.c client driver by + David Hinds. + + The Linux network driver outline is based on the 3c589_cs.c driver, + the 8390.c driver, and the example skeleton.c kernel code, which are + by Donald Becker. + + The Am2150 network driver hardware interface code is based on the + OS/9000 driver for the New Media Ethernet LAN by Eric Mears. + + Special thanks for testing and help in debugging this driver goes + to Ken Lesniak. + +------------------------------------------------------------------------------- +Driver Notes and Issues +------------------------------------------------------------------------------- + +1. Developed on a Dell 320SLi + PCMCIA Card Services 2.6.2 + Linux dell 1.2.10 #1 Thu Jun 29 20:23:41 PDT 1995 i386 + +2. rc.pcmcia may require loading pcmcia_core with io_speed=300: + 'insmod pcmcia_core.o io_speed=300'. + This will avoid problems with fast systems which causes rx_framecnt + to return random values. + +3. If hot extraction does not work for you, use 'ifconfig eth0 down' + before extraction. + +4. There is a bad slow-down problem in this driver. + +5. Future: Multicast processing. In the meantime, do _not_ compile your + kernel with multicast ip enabled. + +------------------------------------------------------------------------------- +History +------------------------------------------------------------------------------- +Log: nmclan_cs.c,v + * Revision 0.16 1995/07/01 06:42:17 rpao + * Bug fix: nmclan_reset() called CardServices incorrectly. + * + * Revision 0.15 1995/05/24 08:09:47 rpao + * Re-implement MULTI_TX dev->tbusy handling. + * + * Revision 0.14 1995/05/23 03:19:30 rpao + * Added, in nmclan_config(), "tuple.Attributes = 0;". + * Modified MACE ID check to ignore chip revision level. + * Avoid tx_free_frames race condition between _start_xmit and _interrupt. + * + * Revision 0.13 1995/05/18 05:56:34 rpao + * Statistics changes. + * Bug fix: nmclan_reset did not enable TX and RX: call restore_multicast_list. + * Bug fix: mace_interrupt checks ~MACE_IMR_DEFAULT. Fixes driver lockup. + * + * Revision 0.12 1995/05/14 00:12:23 rpao + * Statistics overhaul. + * + +95/05/13 rpao V0.10a + Bug fix: MACE statistics counters used wrong I/O ports. + Bug fix: mace_interrupt() needed to allow statistics to be + processed without RX or TX interrupts pending. +95/05/11 rpao V0.10 + Multiple transmit request processing. + Modified statistics to use MACE counters where possible. +95/05/10 rpao V0.09 Bug fix: Must use IO_DATA_PATH_WIDTH_AUTO. + *Released +95/05/10 rpao V0.08 + Bug fix: Make all non-exported functions private by using + static keyword. + Bug fix: Test IntrCnt _before_ reading MACE_IR. +95/05/10 rpao V0.07 Statistics. +95/05/09 rpao V0.06 Fix rx_framecnt problem by addition of PCIC wait states. + +---------------------------------------------------------------------------- */ + +/* ---------------------------------------------------------------------------- +Conditional Compilation Options +---------------------------------------------------------------------------- */ + +#define MULTI_TX 0 +#define RESET_ON_TIMEOUT 1 +#define TX_INTERRUPTABLE 1 +#define RESET_XILINX 0 + +/* ---------------------------------------------------------------------------- +Include Files +---------------------------------------------------------------------------- */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/ptrace.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/in.h> +#include <linux/delay.h> +#include <asm/io.h> +#include <asm/system.h> +#include <asm/bitops.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/if_arp.h> +#include <linux/ioport.h> + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/cisreg.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/ds.h> + +/* ---------------------------------------------------------------------------- +Defines +---------------------------------------------------------------------------- */ + +#define ETHER_ADDR_LEN ETH_ALEN + /* 6 bytes in an Ethernet Address */ +#define MACE_LADRF_LEN 8 + /* 8 bytes in Logical Address Filter */ + +/* Loop Control Defines */ +#define MACE_MAX_IR_ITERATIONS 10 +#define MACE_MAX_RX_ITERATIONS 12 + /* + TBD: Dean brought this up, and I assumed the hardware would + handle it: + + If MACE_MAX_RX_ITERATIONS is > 1, rx_framecnt may still be + non-zero when the isr exits. We may not get another interrupt + to process the remaining packets for some time. + */ + +/* +The Am2150 has a Xilinx XC3042 field programmable gate array (FPGA) +which manages the interface between the MACE and the PCMCIA bus. It +also includes buffer management for the 32K x 8 SRAM to control up to +four transmit and 12 receive frames at a time. +*/ +#define AM2150_MAX_TX_FRAMES 4 +#define AM2150_MAX_RX_FRAMES 12 + +/* Am2150 Ethernet Card I/O Mapping */ +#define AM2150_RCV 0x00 +#define AM2150_XMT 0x04 +#define AM2150_XMT_SKIP 0x09 +#define AM2150_RCV_NEXT 0x0A +#define AM2150_RCV_FRAME_COUNT 0x0B +#define AM2150_MACE_BANK 0x0C +#define AM2150_MACE_BASE 0x10 + +/* MACE Registers */ +#define MACE_RCVFIFO 0 +#define MACE_XMTFIFO 1 +#define MACE_XMTFC 2 +#define MACE_XMTFS 3 +#define MACE_XMTRC 4 +#define MACE_RCVFC 5 +#define MACE_RCVFS 6 +#define MACE_FIFOFC 7 +#define MACE_IR 8 +#define MACE_IMR 9 +#define MACE_PR 10 +#define MACE_BIUCC 11 +#define MACE_FIFOCC 12 +#define MACE_MACCC 13 +#define MACE_PLSCC 14 +#define MACE_PHYCC 15 +#define MACE_CHIPIDL 16 +#define MACE_CHIPIDH 17 +#define MACE_IAC 18 +/* Reserved */ +#define MACE_LADRF 20 +#define MACE_PADR 21 +/* Reserved */ +/* Reserved */ +#define MACE_MPC 24 +/* Reserved */ +#define MACE_RNTPC 26 +#define MACE_RCVCC 27 +/* Reserved */ +#define MACE_UTR 29 +#define MACE_RTR1 30 +#define MACE_RTR2 31 + +/* MACE Bit Masks */ +#define MACE_XMTRC_EXDEF 0x80 +#define MACE_XMTRC_XMTRC 0x0F + +#define MACE_XMTFS_XMTSV 0x80 +#define MACE_XMTFS_UFLO 0x40 +#define MACE_XMTFS_LCOL 0x20 +#define MACE_XMTFS_MORE 0x10 +#define MACE_XMTFS_ONE 0x08 +#define MACE_XMTFS_DEFER 0x04 +#define MACE_XMTFS_LCAR 0x02 +#define MACE_XMTFS_RTRY 0x01 + +#define MACE_RCVFS_RCVSTS 0xF000 +#define MACE_RCVFS_OFLO 0x8000 +#define MACE_RCVFS_CLSN 0x4000 +#define MACE_RCVFS_FRAM 0x2000 +#define MACE_RCVFS_FCS 0x1000 + +#define MACE_FIFOFC_RCVFC 0xF0 +#define MACE_FIFOFC_XMTFC 0x0F + +#define MACE_IR_JAB 0x80 +#define MACE_IR_BABL 0x40 +#define MACE_IR_CERR 0x20 +#define MACE_IR_RCVCCO 0x10 +#define MACE_IR_RNTPCO 0x08 +#define MACE_IR_MPCO 0x04 +#define MACE_IR_RCVINT 0x02 +#define MACE_IR_XMTINT 0x01 + +#define MACE_MACCC_PROM 0x80 +#define MACE_MACCC_DXMT2PD 0x40 +#define MACE_MACCC_EMBA 0x20 +#define MACE_MACCC_RESERVED 0x10 +#define MACE_MACCC_DRCVPA 0x08 +#define MACE_MACCC_DRCVBC 0x04 +#define MACE_MACCC_ENXMT 0x02 +#define MACE_MACCC_ENRCV 0x01 + +#define MACE_PHYCC_LNKFL 0x80 +#define MACE_PHYCC_DLNKTST 0x40 +#define MACE_PHYCC_REVPOL 0x20 +#define MACE_PHYCC_DAPC 0x10 +#define MACE_PHYCC_LRT 0x08 +#define MACE_PHYCC_ASEL 0x04 +#define MACE_PHYCC_RWAKE 0x02 +#define MACE_PHYCC_AWAKE 0x01 + +#define MACE_IAC_ADDRCHG 0x80 +#define MACE_IAC_PHYADDR 0x04 +#define MACE_IAC_LOGADDR 0x02 + +#define MACE_UTR_RTRE 0x80 +#define MACE_UTR_RTRD 0x40 +#define MACE_UTR_RPA 0x20 +#define MACE_UTR_FCOLL 0x10 +#define MACE_UTR_RCVFCSE 0x08 +#define MACE_UTR_LOOP_INCL_MENDEC 0x06 +#define MACE_UTR_LOOP_NO_MENDEC 0x04 +#define MACE_UTR_LOOP_EXTERNAL 0x02 +#define MACE_UTR_LOOP_NONE 0x00 +#define MACE_UTR_RESERVED 0x01 + +/* Switch MACE register bank (only 0 and 1 are valid) */ +#define MACEBANK(win_num) outb((win_num), ioaddr + AM2150_MACE_BANK) + +#define MACE_IMR_DEFAULT \ + (0xFF - \ + ( \ + MACE_IR_CERR | \ + MACE_IR_RCVCCO | \ + MACE_IR_RNTPCO | \ + MACE_IR_MPCO | \ + MACE_IR_RCVINT | \ + MACE_IR_XMTINT \ + ) \ + ) +#undef MACE_IMR_DEFAULT +#define MACE_IMR_DEFAULT 0x00 /* New statistics handling: grab everything */ + +#define TX_TIMEOUT ((400*HZ)/1000) + +/* ---------------------------------------------------------------------------- +Type Definitions +---------------------------------------------------------------------------- */ + +typedef struct _mace_statistics { + /* MACE_XMTFS */ + int xmtsv; + int uflo; + int lcol; + int more; + int one; + int defer; + int lcar; + int rtry; + + /* MACE_XMTRC */ + int exdef; + int xmtrc; + + /* RFS1--Receive Status (RCVSTS) */ + int oflo; + int clsn; + int fram; + int fcs; + + /* RFS2--Runt Packet Count (RNTPC) */ + int rfs_rntpc; + + /* RFS3--Receive Collision Count (RCVCC) */ + int rfs_rcvcc; + + /* MACE_IR */ + int jab; + int babl; + int cerr; + int rcvcco; + int rntpco; + int mpco; + + /* MACE_MPC */ + int mpc; + + /* MACE_RNTPC */ + int rntpc; + + /* MACE_RCVCC */ + int rcvcc; +} mace_statistics; + +typedef struct _mace_private { + dev_link_t link; + struct net_device dev; + dev_node_t node; + struct net_device_stats linux_stats; /* Linux statistics counters */ + mace_statistics mace_stats; /* MACE chip statistics counters */ + + /* restore_multicast_list() state variables */ + int multicast_ladrf[MACE_LADRF_LEN]; /* Logical address filter */ + int multicast_num_addrs; + + char tx_free_frames; /* Number of free transmit frame buffers */ + char tx_irq_disabled; /* MACE TX interrupt disabled */ +} mace_private; + +/* ---------------------------------------------------------------------------- +Private Global Variables +---------------------------------------------------------------------------- */ + +#ifdef PCMCIA_DEBUG +static char rcsid[] = +"nmclan_cs.c,v 0.16 1995/07/01 06:42:17 rpao Exp rpao"; +static char *version = +"nmclan_cs 0.16 (Roger C. Pao)"; +#endif + +static dev_info_t dev_info="nmclan_cs"; +static dev_link_t *dev_list=NULL; + +static char *if_names[]={ + "Auto", "10baseT", "BNC", +}; + +/* ---------------------------------------------------------------------------- +Parameters + These are the parameters that can be set during loading with + 'insmod'. +---------------------------------------------------------------------------- */ + +MODULE_DESCRIPTION("New Media PCMCIA ethernet driver"); +MODULE_LICENSE("GPL"); + +#define INT_MODULE_PARM(n, v) static int n = v; MODULE_PARM(n, "i") + +static int irq_list[4] = { -1 }; +MODULE_PARM(irq_list, "1-4i"); + +/* 0=auto, 1=10baseT, 2 = 10base2, default=auto */ +INT_MODULE_PARM(if_port, 0); +/* Bit map of interrupts to choose from */ +INT_MODULE_PARM(irq_mask, 0xdeb8); + +#ifdef PCMCIA_DEBUG +INT_MODULE_PARM(pc_debug, PCMCIA_DEBUG); +#define DEBUG(n, args...) if (pc_debug>(n)) printk(KERN_DEBUG args) +#else +#define DEBUG(n, args...) +#endif + +/* ---------------------------------------------------------------------------- +Function Prototypes +---------------------------------------------------------------------------- */ + +static void nmclan_config(dev_link_t *link); +static void nmclan_release(u_long arg); +static int nmclan_event(event_t event, int priority, + event_callback_args_t *args); + +static void nmclan_reset(struct net_device *dev); +static int mace_config(struct net_device *dev, struct ifmap *map); +static int mace_open(struct net_device *dev); +static int mace_close(struct net_device *dev); +static int mace_start_xmit(struct sk_buff *skb, struct net_device *dev); +static void mace_tx_timeout(struct net_device *dev); +static void mace_interrupt(int irq, void *dev_id, struct pt_regs *regs); +static struct net_device_stats *mace_get_stats(struct net_device *dev); +static int mace_rx(struct net_device *dev, unsigned char RxCnt); +static void restore_multicast_list(struct net_device *dev); + +static void set_multicast_list(struct net_device *dev); + +static dev_link_t *nmclan_attach(void); +static void nmclan_detach(dev_link_t *); + +/* ---------------------------------------------------------------------------- +flush_stale_links + Clean up stale device structures +---------------------------------------------------------------------------- */ + +static void flush_stale_links(void) +{ + dev_link_t *link, *next; + for (link = dev_list; link; link = next) { + next = link->next; + if (link->state & DEV_STALE_LINK) + nmclan_detach(link); + } +} + +/* ---------------------------------------------------------------------------- +cs_error + Report a Card Services related error. +---------------------------------------------------------------------------- */ + +static void cs_error(client_handle_t handle, int func, int ret) +{ + error_info_t err = { func, ret }; + CardServices(ReportError, handle, &err); +} + +/* ---------------------------------------------------------------------------- +nmclan_attach + Creates an "instance" of the driver, allocating local data + structures for one device. The device is registered with Card + Services. +---------------------------------------------------------------------------- */ + +static dev_link_t *nmclan_attach(void) +{ + mace_private *lp; + dev_link_t *link; + struct net_device *dev; + client_reg_t client_reg; + int i, ret; + + DEBUG(0, "nmclan_attach()\n"); + DEBUG(1, "%s\n", rcsid); + flush_stale_links(); + + /* Create new ethernet device */ + lp = kmalloc(sizeof(*lp), GFP_KERNEL); + if (!lp) return NULL; + memset(lp, 0, sizeof(*lp)); + link = &lp->link; dev = &lp->dev; + link->priv = dev->priv = link->irq.Instance = lp; + + init_timer(&link->release); + link->release.function = &nmclan_release; + link->release.data = (u_long)link; + link->io.NumPorts1 = 32; + link->io.Attributes1 = IO_DATA_PATH_WIDTH_AUTO; + link->io.IOAddrLines = 5; + link->irq.Attributes = IRQ_TYPE_EXCLUSIVE | IRQ_HANDLE_PRESENT; + link->irq.IRQInfo1 = IRQ_INFO2_VALID|IRQ_LEVEL_ID; + if (irq_list[0] == -1) + link->irq.IRQInfo2 = irq_mask; + else + for (i = 0; i < 4; i++) + link->irq.IRQInfo2 |= 1 << irq_list[i]; + link->irq.Handler = &mace_interrupt; + 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; + + lp->tx_free_frames=AM2150_MAX_TX_FRAMES; + + dev->hard_start_xmit = &mace_start_xmit; + dev->set_config = &mace_config; + dev->get_stats = &mace_get_stats; + dev->set_multicast_list = &set_multicast_list; + ether_setup(dev); + init_dev_name(dev, lp->node); + dev->open = &mace_open; + dev->stop = &mace_close; +#ifdef HAVE_TX_TIMEOUT + dev->tx_timeout = mace_tx_timeout; + dev->watchdog_timeo = TX_TIMEOUT; +#endif + + /* 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 = &nmclan_event; + client_reg.Version = 0x0210; + client_reg.event_callback_args.client_data = link; + ret = CardServices(RegisterClient, &link->handle, &client_reg); + if (ret != 0) { + cs_error(link->handle, RegisterClient, ret); + nmclan_detach(link); + return NULL; + } + + return link; +} /* nmclan_attach */ + +/* ---------------------------------------------------------------------------- +nmclan_detach + 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 nmclan_detach(dev_link_t *link) +{ + mace_private *lp = link->priv; + dev_link_t **linkp; + + DEBUG(0, "nmclan_detach(0x%p)\n", link); + + /* Locate device structure */ + for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next) + if (*linkp == link) break; + if (*linkp == NULL) + return; + + del_timer(&link->release); + if (link->state & DEV_CONFIG) { + nmclan_release((u_long)link); + if (link->state & DEV_STALE_CONFIG) { + link->state |= DEV_STALE_LINK; + return; + } + } + + if (link->handle) + CardServices(DeregisterClient, link->handle); + + /* Unlink device structure, free bits */ + *linkp = link->next; + if (link->dev) + unregister_netdev(&lp->dev); + kfree(lp); + +} /* nmclan_detach */ + +/* ---------------------------------------------------------------------------- +mace_read + Reads a MACE register. This is bank independent; however, the + caller must ensure that this call is not interruptable. We are + assuming that during normal operation, the MACE is always in + bank 0. +---------------------------------------------------------------------------- */ +static int mace_read(ioaddr_t ioaddr, int reg) +{ + int data = 0xFF; + unsigned long flags; + + switch (reg >> 4) { + case 0: /* register 0-15 */ + data = inb(ioaddr + AM2150_MACE_BASE + reg); + break; + case 1: /* register 16-31 */ + save_flags(flags); + cli(); + MACEBANK(1); + data = inb(ioaddr + AM2150_MACE_BASE + (reg & 0x0F)); + MACEBANK(0); + restore_flags(flags); + break; + } + return (data & 0xFF); +} /* mace_read */ + +/* ---------------------------------------------------------------------------- +mace_write + Writes to a MACE register. This is bank independent; however, + the caller must ensure that this call is not interruptable. We + are assuming that during normal operation, the MACE is always in + bank 0. +---------------------------------------------------------------------------- */ +static void mace_write(ioaddr_t ioaddr, int reg, int data) +{ + unsigned long flags; + + switch (reg >> 4) { + case 0: /* register 0-15 */ + outb(data & 0xFF, ioaddr + AM2150_MACE_BASE + reg); + break; + case 1: /* register 16-31 */ + save_flags(flags); + cli(); + MACEBANK(1); + outb(data & 0xFF, ioaddr + AM2150_MACE_BASE + (reg & 0x0F)); + MACEBANK(0); + restore_flags(flags); + break; + } +} /* mace_write */ + +/* ---------------------------------------------------------------------------- +mace_init + Resets the MACE chip. +---------------------------------------------------------------------------- */ +static void mace_init(ioaddr_t ioaddr, char *enet_addr) +{ + int i; + + /* MACE Software reset */ + mace_write(ioaddr, MACE_BIUCC, 1); + while (mace_read(ioaddr, MACE_BIUCC) & 0x01) { + /* Wait for reset bit to be cleared automatically after <= 200ns */; + } + mace_write(ioaddr, MACE_BIUCC, 0); + + /* The Am2150 requires that the MACE FIFOs operate in burst mode. */ + mace_write(ioaddr, MACE_FIFOCC, 0x0F); + + mace_write(ioaddr, MACE_RCVFC, 0); /* Disable Auto Strip Receive */ + mace_write(ioaddr, MACE_IMR, 0xFF); /* Disable all interrupts until _open */ + + /* + * Bit 2-1 PORTSEL[1-0] Port Select. + * 00 AUI/10Base-2 + * 01 10Base-T + * 10 DAI Port (reserved in Am2150) + * 11 GPSI + * For this card, only the first two are valid. + * So, PLSCC should be set to + * 0x00 for 10Base-2 + * 0x02 for 10Base-T + * Or just set ASEL in PHYCC below! + */ + switch (if_port) { + case 1: + mace_write(ioaddr, MACE_PLSCC, 0x02); + break; + case 2: + mace_write(ioaddr, MACE_PLSCC, 0x00); + break; + default: + mace_write(ioaddr, MACE_PHYCC, /* ASEL */ 4); + /* ASEL Auto Select. When set, the PORTSEL[1-0] bits are overridden, + and the MACE device will automatically select the operating media + interface port. */ + break; + } + + mace_write(ioaddr, MACE_IAC, MACE_IAC_ADDRCHG | MACE_IAC_PHYADDR); + /* Poll ADDRCHG bit */ + while (mace_read(ioaddr, MACE_IAC) & MACE_IAC_ADDRCHG) + ; + /* Set PADR register */ + for (i = 0; i < ETHER_ADDR_LEN; i++) + mace_write(ioaddr, MACE_PADR, enet_addr[i]); + + /* MAC Configuration Control Register should be written last */ + /* Let set_multicast_list set this. */ + /* mace_write(ioaddr, MACE_MACCC, MACE_MACCC_ENXMT | MACE_MACCC_ENRCV); */ + mace_write(ioaddr, MACE_MACCC, 0x00); +} /* mace_init */ + +/* ---------------------------------------------------------------------------- +nmclan_config + This routine 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. +---------------------------------------------------------------------------- */ + +#define CS_CHECK(fn, args...) \ +while ((last_ret=CardServices(last_fn=(fn), args))!=0) goto cs_failed + +static void nmclan_config(dev_link_t *link) +{ + client_handle_t handle = link->handle; + mace_private *lp = link->priv; + struct net_device *dev = &lp->dev; + tuple_t tuple; + cisparse_t parse; + u_char buf[64]; + int i, last_ret, last_fn; + ioaddr_t ioaddr; + + DEBUG(0, "nmclan_config(0x%p)\n", link); + + tuple.Attributes = 0; + tuple.TupleData = buf; + tuple.TupleDataMax = 64; + tuple.TupleOffset = 0; + tuple.DesiredTuple = CISTPL_CONFIG; + CS_CHECK(GetFirstTuple, handle, &tuple); + CS_CHECK(GetTupleData, handle, &tuple); + CS_CHECK(ParseTuple, handle, &tuple, &parse); + link->conf.ConfigBase = parse.config.base; + + /* Configure card */ + link->state |= DEV_CONFIG; + + CS_CHECK(RequestIO, handle, &link->io); + CS_CHECK(RequestIRQ, handle, &link->irq); + CS_CHECK(RequestConfiguration, handle, &link->conf); + dev->irq = link->irq.AssignedIRQ; + dev->base_addr = link->io.BasePort1; + i = register_netdev(dev); + if (i != 0) { + printk(KERN_NOTICE "nmclan_cs: register_netdev() failed\n"); + goto failed; + } + + ioaddr = dev->base_addr; + + /* Read the ethernet address from the CIS. */ + tuple.DesiredTuple = 0x80 /* CISTPL_CFTABLE_ENTRY_MISC */; + tuple.TupleData = buf; + tuple.TupleDataMax = 64; + tuple.TupleOffset = 0; + CS_CHECK(GetFirstTuple, handle, &tuple); + CS_CHECK(GetTupleData, handle, &tuple); + memcpy(dev->dev_addr, tuple.TupleData, ETHER_ADDR_LEN); + + /* Verify configuration by reading the MACE ID. */ + { + char sig[2]; + + sig[0] = mace_read(ioaddr, MACE_CHIPIDL); + sig[1] = mace_read(ioaddr, MACE_CHIPIDH); + if ((sig[0] == 0x40) && ((sig[1] & 0x0F) == 0x09)) { + DEBUG(0, "nmclan_cs configured: mace id=%x %x\n", + sig[0], sig[1]); + } else { + printk(KERN_NOTICE "nmclan_cs: mace id not found: %x %x should" + " be 0x40 0x?9\n", sig[0], sig[1]); + link->state &= ~DEV_CONFIG_PENDING; + return; + } + } + + mace_init(ioaddr, dev->dev_addr); + + /* The if_port symbol can be set when the module is loaded */ + if (if_port <= 2) + dev->if_port = if_port; + else + printk(KERN_NOTICE "nmclan_cs: invalid if_port requested\n"); + +#if 0 + /* Determine which port we are using if auto is selected */ + if (if_port==0) { + mace_write(ioaddr, MACE_MACCC, MACE_MACCC_ENXMT | MACE_MACCC_ENRCV); + DEBUG(2, "%s: mace_phycc 0x%X.\n", dev->name, + mace_read(ioaddr, MACE_PHYCC)); + if (mace_read(ioaddr, MACE_PHYCC) & MACE_PHYCC_LNKFL) + /* 10base-T receiver is in link fail, MACE is using AUI port. */ + dev->if_port = 2; + else + dev->if_port = 1; + mace_write(ioaddr, MACE_MACCC, 0x00); + } + /* Unfortunately, this doesn't seem to work. LNKFL is always set. + LNKFL is supposed to be opposite the green LED on the edge of the card. + It doesn't work if it is checked and printed in _open() either. + It does work if check in _start_xmit(), but that's not a good place + to printk. */ +#endif + + copy_dev_name(lp->node, dev); + link->dev = &lp->node; + link->state &= ~DEV_CONFIG_PENDING; + + printk(KERN_INFO "%s: nmclan: port %#3lx, irq %d, %s port, hw_addr ", + dev->name, dev->base_addr, dev->irq, if_names[dev->if_port]); + for (i = 0; i < 6; i++) + printk("%02X%s", dev->dev_addr[i], ((i<5) ? ":" : "\n")); + return; + +cs_failed: + cs_error(link->handle, last_fn, last_ret); +failed: + nmclan_release((u_long)link); + link->state &= ~DEV_CONFIG_PENDING; + return; + +} /* nmclan_config */ + +/* ---------------------------------------------------------------------------- +nmclan_release + After a card is removed, nmclan_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 nmclan_release(u_long arg) +{ + dev_link_t *link = (dev_link_t *)arg; + + DEBUG(0, "nmclan_release(0x%p)\n", link); + + if (link->open) { + DEBUG(1, "nmclan_cs: release postponed, '%s' " + "still open\n", link->dev->dev_name); + link->state |= DEV_STALE_CONFIG; + return; + } + + CardServices(ReleaseConfiguration, link->handle); + CardServices(ReleaseIO, link->handle, &link->io); + CardServices(ReleaseIRQ, link->handle, &link->irq); + + link->state &= ~DEV_CONFIG; + +} /* nmclan_release */ + +/* ---------------------------------------------------------------------------- +nmclan_event + 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. +---------------------------------------------------------------------------- */ +static int nmclan_event(event_t event, int priority, + event_callback_args_t *args) +{ + dev_link_t *link = args->client_data; + mace_private *lp = link->priv; + struct net_device *dev = &lp->dev; + + DEBUG(1, "nmclan_event(0x%06x)\n", event); + + switch (event) { + case CS_EVENT_CARD_REMOVAL: + link->state &= ~DEV_PRESENT; + if (link->state & DEV_CONFIG) { + netif_device_detach(dev); + mod_timer(&link->release, jiffies + HZ/20); + } + break; + case CS_EVENT_CARD_INSERTION: + link->state |= DEV_PRESENT | DEV_CONFIG_PENDING; + nmclan_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) + netif_device_detach(dev); + 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) { + nmclan_reset(dev); + netif_device_attach(dev); + } + } + break; + case CS_EVENT_RESET_REQUEST: + return 1; + break; + } + return 0; +} /* nmclan_event */ + +/* ---------------------------------------------------------------------------- +nmclan_reset + Reset and restore all of the Xilinx and MACE registers. +---------------------------------------------------------------------------- */ +static void nmclan_reset(struct net_device *dev) +{ + mace_private *lp = dev->priv; + +#if RESET_XILINX + dev_link_t *link = &lp->link; + conf_reg_t reg; + u_long OrigCorValue; + + /* Save original COR value */ + reg.Function = 0; + reg.Action = CS_READ; + reg.Offset = CISREG_COR; + reg.Value = 0; + CardServices(AccessConfigurationRegister, link->handle, ®); + OrigCorValue = reg.Value; + + /* Reset Xilinx */ + reg.Action = CS_WRITE; + reg.Offset = CISREG_COR; + DEBUG(1, "nmclan_reset: OrigCorValue=0x%lX, resetting...\n", + OrigCorValue); + reg.Value = COR_SOFT_RESET; + CardServices(AccessConfigurationRegister, link->handle, ®); + /* Need to wait for 20 ms for PCMCIA to finish reset. */ + + /* Restore original COR configuration index */ + reg.Value = COR_LEVEL_REQ | (OrigCorValue & COR_CONFIG_MASK); + CardServices(AccessConfigurationRegister, link->handle, ®); + /* Xilinx is now completely reset along with the MACE chip. */ + lp->tx_free_frames=AM2150_MAX_TX_FRAMES; + +#endif /* #if RESET_XILINX */ + + /* Xilinx is now completely reset along with the MACE chip. */ + lp->tx_free_frames=AM2150_MAX_TX_FRAMES; + + /* Reinitialize the MACE chip for operation. */ + mace_init(dev->base_addr, dev->dev_addr); + mace_write(dev->base_addr, MACE_IMR, MACE_IMR_DEFAULT); + + /* Restore the multicast list and enable TX and RX. */ + restore_multicast_list(dev); +} /* nmclan_reset */ + +/* ---------------------------------------------------------------------------- +mace_config + [Someone tell me what this is supposed to do? Is if_port a defined + standard? If so, there should be defines to indicate 1=10Base-T, + 2=10Base-2, etc. including limited automatic detection.] +---------------------------------------------------------------------------- */ +static int mace_config(struct net_device *dev, struct ifmap *map) +{ + if ((map->port != (u_char)(-1)) && (map->port != dev->if_port)) { + if (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; +} /* mace_config */ + +/* ---------------------------------------------------------------------------- +mace_open + Open device driver. +---------------------------------------------------------------------------- */ +static int mace_open(struct net_device *dev) +{ + ioaddr_t ioaddr = dev->base_addr; + mace_private *lp = dev->priv; + dev_link_t *link = &lp->link; + + if (!DEV_OK(link)) + return -ENODEV; + + link->open++; + MOD_INC_USE_COUNT; + + MACEBANK(0); + + netif_start_queue(dev); + netif_mark_up(dev); + nmclan_reset(dev); + + return 0; /* Always succeed */ +} /* mace_open */ + +/* ---------------------------------------------------------------------------- +mace_close + Closes device driver. +---------------------------------------------------------------------------- */ +static int mace_close(struct net_device *dev) +{ + ioaddr_t ioaddr = dev->base_addr; + mace_private *lp = dev->priv; + dev_link_t *link = &lp->link; + + DEBUG(2, "%s: shutting down ethercard.\n", dev->name); + + /* Mask off all interrupts from the MACE chip. */ + outb(0xFF, ioaddr + AM2150_MACE_BASE + MACE_IMR); + + link->open--; + netif_stop_queue(dev); + netif_mark_down(dev); + if (link->state & DEV_STALE_CONFIG) + mod_timer(&link->release, jiffies + HZ/20); + + MOD_DEC_USE_COUNT; + + return 0; +} /* mace_close */ + +/* ---------------------------------------------------------------------------- +mace_start_xmit + This routine begins the packet transmit function. When completed, + it will generate a transmit interrupt. + + According to /usr/src/linux/net/inet/dev.c, if _start_xmit + returns 0, the "packet is now solely the responsibility of the + driver." If _start_xmit returns non-zero, the "transmission + failed, put skb back into a list." +---------------------------------------------------------------------------- */ + +static void mace_tx_timeout(struct net_device *dev) +{ + mace_private *lp = (mace_private *)dev->priv; + dev_link_t *link = &lp->link; + + printk(KERN_NOTICE "%s: transmit timed out -- ", dev->name); +#if RESET_ON_TIMEOUT + printk("resetting card\n"); + CardServices(ResetCard, link->handle); +#else /* #if RESET_ON_TIMEOUT */ + printk("NOT resetting card\n"); +#endif /* #if RESET_ON_TIMEOUT */ + dev->trans_start = jiffies; + netif_wake_queue(dev); +} + +static int mace_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + mace_private *lp = (mace_private *)dev->priv; + ioaddr_t ioaddr = dev->base_addr; + + tx_timeout_check(dev, mace_tx_timeout); + skb_tx_check(dev, skb); + + DEBUG(3, "%s: mace_start_xmit(length = %ld) called.\n", + dev->name, (long)skb->len); + +#if (!TX_INTERRUPTABLE) + /* Disable MACE TX interrupts. */ + outb(MACE_IMR_DEFAULT | MACE_IR_XMTINT, + ioaddr + AM2150_MACE_BASE + MACE_IMR); + lp->tx_irq_disabled=1; +#endif /* #if (!TX_INTERRUPTABLE) */ + + { + /* This block must not be interrupted by another transmit request! + mace_tx_timeout will take care of timer-based retransmissions from + the upper layers. The interrupt handler is guaranteed never to + service a transmit interrupt while we are in here. + */ + + add_tx_bytes(&lp->linux_stats, skb->len); + lp->tx_free_frames--; + + /* WARNING: Write the _exact_ number of bytes written in the header! */ + /* Put out the word header [must be an outw()] . . . */ + outw(skb->len, ioaddr + AM2150_XMT); + /* . . . and the packet [may be any combination of outw() and outb()] */ + outsw(ioaddr + AM2150_XMT, skb->data, skb->len >> 1); + if (skb->len & 1) { + /* Odd byte transfer */ + outb(skb->data[skb->len-1], ioaddr + AM2150_XMT); + } + + dev->trans_start = jiffies; + +#if MULTI_TX + if (lp->tx_free_frames > 0) + netif_start_queue(dev); +#endif /* #if MULTI_TX */ + } + +#if (!TX_INTERRUPTABLE) + /* Re-enable MACE TX interrupts. */ + lp->tx_irq_disabled=0; + outb(MACE_IMR_DEFAULT, ioaddr + AM2150_MACE_BASE + MACE_IMR); +#endif /* #if (!TX_INTERRUPTABLE) */ + + DEV_KFREE_SKB(skb); + + return 0; +} /* mace_start_xmit */ + +/* ---------------------------------------------------------------------------- +mace_interrupt + The interrupt handler. +---------------------------------------------------------------------------- */ +static void mace_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + mace_private *lp = (mace_private *)dev_id; + struct net_device *dev = &lp->dev; + ioaddr_t ioaddr = dev->base_addr; + int status; + int IntrCnt = MACE_MAX_IR_ITERATIONS; + + if (dev == NULL) { + DEBUG(2, "mace_interrupt(): irq 0x%X for unknown device.\n", + irq); + return; + } + + if (lp->tx_irq_disabled) { + printk( + (lp->tx_irq_disabled? + KERN_NOTICE "%s: Interrupt with tx_irq_disabled " + "[isr=%02X, imr=%02X]\n": + KERN_NOTICE "%s: Re-entering the interrupt handler " + "[isr=%02X, imr=%02X]\n"), + dev->name, + inb(ioaddr + AM2150_MACE_BASE + MACE_IR), + inb(ioaddr + AM2150_MACE_BASE + MACE_IMR) + ); + /* WARNING: MACE_IR has been read! */ + return; + } + + if (!netif_device_present(dev)) { + DEBUG(2, "%s: interrupt from dead card\n", dev->name); + goto exception; + } + + do { + /* WARNING: MACE_IR is a READ/CLEAR port! */ + status = inb(ioaddr + AM2150_MACE_BASE + MACE_IR); + + DEBUG(3, "mace_interrupt: irq 0x%X status 0x%X.\n", irq, status); + + if (status & MACE_IR_RCVINT) { + mace_rx(dev, MACE_MAX_RX_ITERATIONS); + } + + if (status & MACE_IR_XMTINT) { + unsigned char fifofc; + unsigned char xmtrc; + unsigned char xmtfs; + + fifofc = inb(ioaddr + AM2150_MACE_BASE + MACE_FIFOFC); + if ((fifofc & MACE_FIFOFC_XMTFC)==0) { + lp->linux_stats.tx_errors++; + outb(0xFF, ioaddr + AM2150_XMT_SKIP); + } + + /* Transmit Retry Count (XMTRC, reg 4) */ + xmtrc = inb(ioaddr + AM2150_MACE_BASE + MACE_XMTRC); + if (xmtrc & MACE_XMTRC_EXDEF) lp->mace_stats.exdef++; + lp->mace_stats.xmtrc += (xmtrc & MACE_XMTRC_XMTRC); + + if ( + (xmtfs = inb(ioaddr + AM2150_MACE_BASE + MACE_XMTFS)) & + MACE_XMTFS_XMTSV /* Transmit Status Valid */ + ) { + lp->mace_stats.xmtsv++; + + if (xmtfs & ~MACE_XMTFS_XMTSV) { + if (xmtfs & MACE_XMTFS_UFLO) { + /* Underflow. Indicates that the Transmit FIFO emptied before + the end of frame was reached. */ + lp->mace_stats.uflo++; + } + if (xmtfs & MACE_XMTFS_LCOL) { + /* Late Collision */ + lp->mace_stats.lcol++; + } + if (xmtfs & MACE_XMTFS_MORE) { + /* MORE than one retry was needed */ + lp->mace_stats.more++; + } + if (xmtfs & MACE_XMTFS_ONE) { + /* Exactly ONE retry occurred */ + lp->mace_stats.one++; + } + if (xmtfs & MACE_XMTFS_DEFER) { + /* Transmission was defered */ + lp->mace_stats.defer++; + } + if (xmtfs & MACE_XMTFS_LCAR) { + /* Loss of carrier */ + lp->mace_stats.lcar++; + } + if (xmtfs & MACE_XMTFS_RTRY) { + /* Retry error: transmit aborted after 16 attempts */ + lp->mace_stats.rtry++; + } + } /* if (xmtfs & ~MACE_XMTFS_XMTSV) */ + + } /* if (xmtfs & MACE_XMTFS_XMTSV) */ + + lp->linux_stats.tx_packets++; + lp->tx_free_frames++; + netif_wake_queue(dev); + } /* if (status & MACE_IR_XMTINT) */ + + if (status & ~MACE_IMR_DEFAULT & ~MACE_IR_RCVINT & ~MACE_IR_XMTINT) { + if (status & MACE_IR_JAB) { + /* Jabber Error. Excessive transmit duration (20-150ms). */ + lp->mace_stats.jab++; + } + if (status & MACE_IR_BABL) { + /* Babble Error. >1518 bytes transmitted. */ + lp->mace_stats.babl++; + } + if (status & MACE_IR_CERR) { + /* Collision Error. CERR indicates the absence of the + Signal Quality Error Test message after a packet + transmission. */ + lp->mace_stats.cerr++; + } + if (status & MACE_IR_RCVCCO) { + /* Receive Collision Count Overflow; */ + lp->mace_stats.rcvcco++; + } + if (status & MACE_IR_RNTPCO) { + /* Runt Packet Count Overflow */ + lp->mace_stats.rntpco++; + } + if (status & MACE_IR_MPCO) { + /* Missed Packet Count Overflow */ + lp->mace_stats.mpco++; + } + } /* if (status & ~MACE_IMR_DEFAULT & ~MACE_IR_RCVINT & ~MACE_IR_XMTINT) */ + + } while ((status & ~MACE_IMR_DEFAULT) && (--IntrCnt)); + +exception: + return; +} /* mace_interrupt */ + +/* ---------------------------------------------------------------------------- +mace_rx + Receives packets. +---------------------------------------------------------------------------- */ +static int mace_rx(struct net_device *dev, unsigned char RxCnt) +{ + mace_private *lp = (mace_private *)dev->priv; + ioaddr_t ioaddr = dev->base_addr; + unsigned char rx_framecnt; + unsigned short rx_status; + + while ( + ((rx_framecnt = inb(ioaddr + AM2150_RCV_FRAME_COUNT)) > 0) && + (rx_framecnt <= 12) && /* rx_framecnt==0xFF if card is extracted. */ + (RxCnt--) + ) { + rx_status = inw(ioaddr + AM2150_RCV); + + DEBUG(3, "%s: in mace_rx(), framecnt 0x%X, rx_status" + " 0x%X.\n", dev->name, rx_framecnt, rx_status); + + if (rx_status & MACE_RCVFS_RCVSTS) { /* Error, update stats. */ + lp->linux_stats.rx_errors++; + if (rx_status & MACE_RCVFS_OFLO) { + lp->mace_stats.oflo++; + } + if (rx_status & MACE_RCVFS_CLSN) { + lp->mace_stats.clsn++; + } + if (rx_status & MACE_RCVFS_FRAM) { + lp->mace_stats.fram++; + } + if (rx_status & MACE_RCVFS_FCS) { + lp->mace_stats.fcs++; + } + } else { + short pkt_len = (rx_status & ~MACE_RCVFS_RCVSTS) - 4; + /* Auto Strip is off, always subtract 4 */ + struct sk_buff *skb; + + lp->mace_stats.rfs_rntpc += inb(ioaddr + AM2150_RCV); + /* runt packet count */ + lp->mace_stats.rfs_rcvcc += inb(ioaddr + AM2150_RCV); + /* rcv collision count */ + + DEBUG(3, " receiving packet size 0x%X rx_status" + " 0x%X.\n", pkt_len, rx_status); + + skb = dev_alloc_skb(pkt_len+2); + + if (skb != NULL) { + skb->dev = dev; + + skb_reserve(skb, 2); + insw(ioaddr + AM2150_RCV, skb_put(skb, pkt_len), pkt_len>>1); + if (pkt_len & 1) + *(skb->tail-1) = inb(ioaddr + AM2150_RCV); + skb->protocol = eth_type_trans(skb, dev); + + netif_rx(skb); /* Send the packet to the upper (protocol) layers. */ + + dev->last_rx = jiffies; + lp->linux_stats.rx_packets++; + add_rx_bytes(&lp->linux_stats, skb->len); + outb(0xFF, ioaddr + AM2150_RCV_NEXT); /* skip to next frame */ + continue; + } else { + DEBUG(1, "%s: couldn't allocate a sk_buff of size" + " %d.\n", dev->name, pkt_len); + lp->linux_stats.rx_dropped++; + } + } + outb(0xFF, ioaddr + AM2150_RCV_NEXT); /* skip to next frame */ + } /* while */ + + return 0; +} /* mace_rx */ + +/* ---------------------------------------------------------------------------- +pr_linux_stats +---------------------------------------------------------------------------- */ +static void pr_linux_stats(struct net_device_stats *pstats) +{ + DEBUG(2, "pr_linux_stats\n"); + DEBUG(2, " rx_packets=%-7ld tx_packets=%ld\n", + (long)pstats->rx_packets, (long)pstats->tx_packets); + DEBUG(2, " rx_errors=%-7ld tx_errors=%ld\n", + (long)pstats->rx_errors, (long)pstats->tx_errors); + DEBUG(2, " rx_dropped=%-7ld tx_dropped=%ld\n", + (long)pstats->rx_dropped, (long)pstats->tx_dropped); + DEBUG(2, " multicast=%-7ld collisions=%ld\n", + (long)pstats->multicast, (long)pstats->collisions); + + DEBUG(2, " rx_length_errors=%-7ld rx_over_errors=%ld\n", + (long)pstats->rx_length_errors, (long)pstats->rx_over_errors); + DEBUG(2, " rx_crc_errors=%-7ld rx_frame_errors=%ld\n", + (long)pstats->rx_crc_errors, (long)pstats->rx_frame_errors); + DEBUG(2, " rx_fifo_errors=%-7ld rx_missed_errors=%ld\n", + (long)pstats->rx_fifo_errors, (long)pstats->rx_missed_errors); + + DEBUG(2, " tx_aborted_errors=%-7ld tx_carrier_errors=%ld\n", + (long)pstats->tx_aborted_errors, (long)pstats->tx_carrier_errors); + DEBUG(2, " tx_fifo_errors=%-7ld tx_heartbeat_errors=%ld\n", + (long)pstats->tx_fifo_errors, (long)pstats->tx_heartbeat_errors); + DEBUG(2, " tx_window_errors=%ld\n", + (long)pstats->tx_window_errors); +} /* pr_linux_stats */ + +/* ---------------------------------------------------------------------------- +pr_mace_stats +---------------------------------------------------------------------------- */ +static void pr_mace_stats(mace_statistics *pstats) +{ + DEBUG(2, "pr_mace_stats\n"); + + DEBUG(2, " xmtsv=%-7d uflo=%d\n", + pstats->xmtsv, pstats->uflo); + DEBUG(2, " lcol=%-7d more=%d\n", + pstats->lcol, pstats->more); + DEBUG(2, " one=%-7d defer=%d\n", + pstats->one, pstats->defer); + DEBUG(2, " lcar=%-7d rtry=%d\n", + pstats->lcar, pstats->rtry); + + /* MACE_XMTRC */ + DEBUG(2, " exdef=%-7d xmtrc=%d\n", + pstats->exdef, pstats->xmtrc); + + /* RFS1--Receive Status (RCVSTS) */ + DEBUG(2, " oflo=%-7d clsn=%d\n", + pstats->oflo, pstats->clsn); + DEBUG(2, " fram=%-7d fcs=%d\n", + pstats->fram, pstats->fcs); + + /* RFS2--Runt Packet Count (RNTPC) */ + /* RFS3--Receive Collision Count (RCVCC) */ + DEBUG(2, " rfs_rntpc=%-7d rfs_rcvcc=%d\n", + pstats->rfs_rntpc, pstats->rfs_rcvcc); + + /* MACE_IR */ + DEBUG(2, " jab=%-7d babl=%d\n", + pstats->jab, pstats->babl); + DEBUG(2, " cerr=%-7d rcvcco=%d\n", + pstats->cerr, pstats->rcvcco); + DEBUG(2, " rntpco=%-7d mpco=%d\n", + pstats->rntpco, pstats->mpco); + + /* MACE_MPC */ + DEBUG(2, " mpc=%d\n", pstats->mpc); + + /* MACE_RNTPC */ + DEBUG(2, " rntpc=%d\n", pstats->rntpc); + + /* MACE_RCVCC */ + DEBUG(2, " rcvcc=%d\n", pstats->rcvcc); + +} /* pr_mace_stats */ + +/* ---------------------------------------------------------------------------- +update_stats + Update statistics. We change to register window 1, so this + should be run single-threaded if the device is active. This is + expected to be a rare operation, and it's simpler for the rest + of the driver to assume that window 0 is always valid rather + than use a special window-state variable. + + oflo & uflo should _never_ occur since it would mean the Xilinx + was not able to transfer data between the MACE FIFO and the + card's SRAM fast enough. If this happens, something is + seriously wrong with the hardware. +---------------------------------------------------------------------------- */ +static void update_stats(ioaddr_t ioaddr, struct net_device *dev) +{ + mace_private *lp = (mace_private *)dev->priv; + + lp->mace_stats.rcvcc += mace_read(ioaddr, MACE_RCVCC); + lp->mace_stats.rntpc += mace_read(ioaddr, MACE_RNTPC); + lp->mace_stats.mpc += mace_read(ioaddr, MACE_MPC); + /* At this point, mace_stats is fully updated for this call. + We may now update the linux_stats. */ + + /* The MACE has no equivalent for linux_stats field which are commented + out. */ + +#if 0 + /* These must be tracked in the main body of the driver. */ + lp->linux_stats.rx_packets; + lp->linux_stats.tx_packets; + lp->linux_stats.rx_errors; + lp->linux_stats.tx_errors; + lp->linux_stats.rx_dropped; + lp->linux_stats.tx_dropped; +#endif + /* lp->linux_stats.multicast; */ + lp->linux_stats.collisions = + lp->mace_stats.rcvcco * 256 + lp->mace_stats.rcvcc; + /* Collision: The MACE may retry sending a packet 15 times + before giving up. The retry count is in XMTRC. + Does each retry constitute a collision? + If so, why doesn't the RCVCC record these collisions? */ + + /* detailed rx_errors: */ + lp->linux_stats.rx_length_errors = + lp->mace_stats.rntpco * 256 + lp->mace_stats.rntpc; + /* lp->linux_stats.rx_over_errors */ + lp->linux_stats.rx_crc_errors = lp->mace_stats.fcs; + lp->linux_stats.rx_frame_errors = lp->mace_stats.fram; + lp->linux_stats.rx_fifo_errors = lp->mace_stats.oflo; + lp->linux_stats.rx_missed_errors = + lp->mace_stats.mpco * 256 + lp->mace_stats.mpc; + + /* detailed tx_errors */ + lp->linux_stats.tx_aborted_errors = lp->mace_stats.rtry; + lp->linux_stats.tx_carrier_errors = lp->mace_stats.lcar; + /* LCAR usually results from bad cabling. */ + lp->linux_stats.tx_fifo_errors = lp->mace_stats.uflo; + lp->linux_stats.tx_heartbeat_errors = lp->mace_stats.cerr; + /* lp->linux_stats.tx_window_errors; */ + + return; +} /* update_stats */ + +/* ---------------------------------------------------------------------------- +mace_get_stats + Gathers ethernet statistics from the MACE chip. +---------------------------------------------------------------------------- */ +static struct net_device_stats *mace_get_stats(struct net_device *dev) +{ + mace_private *lp = (mace_private *)dev->priv; + + update_stats(dev->base_addr, dev); + + DEBUG(1, "%s: updating the statistics.\n", dev->name); + pr_linux_stats(&lp->linux_stats); + pr_mace_stats(&lp->mace_stats); + + return &lp->linux_stats; +} /* net_device_stats */ + +/* ---------------------------------------------------------------------------- +updateCRC + Modified from Am79C90 data sheet. +---------------------------------------------------------------------------- */ + +#if BROKEN_MULTICAST + +static void updateCRC(int *CRC, int bit) +{ + int poly[]={ + 1,1,1,0, 1,1,0,1, + 1,0,1,1, 1,0,0,0, + 1,0,0,0, 0,0,1,1, + 0,0,1,0, 0,0,0,0 + }; /* CRC polynomial. poly[n] = coefficient of the x**n term of the + CRC generator polynomial. */ + + int j; + + /* shift CRC and control bit (CRC[32]) */ + for (j = 32; j > 0; j--) + CRC[j] = CRC[j-1]; + CRC[0] = 0; + + /* If bit XOR(control bit) = 1, set CRC = CRC XOR polynomial. */ + if (bit ^ CRC[32]) + for (j = 0; j < 32; j++) + CRC[j] ^= poly[j]; +} /* updateCRC */ + +/* ---------------------------------------------------------------------------- +BuildLAF + Build logical address filter. + Modified from Am79C90 data sheet. + +Input + ladrf: logical address filter (contents initialized to 0) + adr: ethernet address +---------------------------------------------------------------------------- */ +static void BuildLAF(int *ladrf, int *adr) +{ + int CRC[33]={1}; /* CRC register, 1 word/bit + extra control bit */ + + int i, byte; /* temporary array indices */ + int hashcode; /* the output object */ + + CRC[32]=0; + + for (byte = 0; byte < 6; byte++) + for (i = 0; i < 8; i++) + updateCRC(CRC, (adr[byte] >> i) & 1); + + hashcode = 0; + for (i = 0; i < 6; i++) + hashcode = (hashcode << 1) + CRC[i]; + + byte = hashcode >> 3; + ladrf[byte] |= (1 << (hashcode & 7)); + +#ifdef PCMCIA_DEBUG + if (pc_debug > 2) { + printk(KERN_DEBUG " adr ="); + for (i = 0; i < 6; i++) + printk(" %02X", adr[i]); + printk("\n" KERN_DEBUG " hashcode = %d(decimal), ladrf[0:63]" + " =", hashcode); + for (i = 0; i < 8; i++) + printk(" %02X", ladrf[i]); + printk("\n"); + } +#endif +} /* BuildLAF */ + +/* ---------------------------------------------------------------------------- +restore_multicast_list + Restores the multicast filter for MACE chip to the last + set_multicast_list() call. + +Input + multicast_num_addrs + multicast_ladrf[] +---------------------------------------------------------------------------- */ +static void restore_multicast_list(struct net_device *dev) +{ + mace_private *lp = (mace_private *)dev->priv; + int num_addrs = lp->multicast_num_addrs; + int *ladrf = lp->multicast_ladrf; + ioaddr_t ioaddr = dev->base_addr; + int i; + + DEBUG(2, "%s: restoring Rx mode to %d addresses.\n", + dev->name, num_addrs); + + if (num_addrs > 0) { + + DEBUG(1, "Attempt to restore multicast list detected.\n"); + + mace_write(ioaddr, MACE_IAC, MACE_IAC_ADDRCHG | MACE_IAC_LOGADDR); + /* Poll ADDRCHG bit */ + while (mace_read(ioaddr, MACE_IAC) & MACE_IAC_ADDRCHG) + ; + /* Set LADRF register */ + for (i = 0; i < MACE_LADRF_LEN; i++) + mace_write(ioaddr, MACE_LADRF, ladrf[i]); + + mace_write(ioaddr, MACE_UTR, MACE_UTR_RCVFCSE | MACE_UTR_LOOP_EXTERNAL); + mace_write(ioaddr, MACE_MACCC, MACE_MACCC_ENXMT | MACE_MACCC_ENRCV); + + } else if (num_addrs < 0) { + + /* Promiscuous mode: receive all packets */ + mace_write(ioaddr, MACE_UTR, MACE_UTR_LOOP_EXTERNAL); + mace_write(ioaddr, MACE_MACCC, + MACE_MACCC_PROM | MACE_MACCC_ENXMT | MACE_MACCC_ENRCV + ); + + } else { + + /* Normal mode */ + mace_write(ioaddr, MACE_UTR, MACE_UTR_LOOP_EXTERNAL); + mace_write(ioaddr, MACE_MACCC, MACE_MACCC_ENXMT | MACE_MACCC_ENRCV); + + } +} /* restore_multicast_list */ + +/* ---------------------------------------------------------------------------- +set_multicast_list + Set or clear the multicast filter for this adaptor. + +Input + num_addrs == -1 Promiscuous mode, receive all packets + num_addrs == 0 Normal mode, clear multicast list + num_addrs > 0 Multicast mode, receive normal and MC packets, and do + best-effort filtering. +Output + multicast_num_addrs + multicast_ladrf[] +---------------------------------------------------------------------------- */ + +static void set_multicast_list(struct net_device *dev) +{ + mace_private *lp = (mace_private *)dev->priv; + int adr[ETHER_ADDR_LEN] = {0}; /* Ethernet address */ + int i; + struct dev_mc_list *dmi = dev->mc_list; + +#ifdef PCMCIA_DEBUG + if (pc_debug > 1) { + static int old = 0; + if (dev->mc_count != old) { + old = dev->mc_count; + DEBUG(0, "%s: setting Rx mode to %d addresses.\n", + dev->name, old); + } + } +#endif + + /* Set multicast_num_addrs. */ + lp->multicast_num_addrs = dev->mc_count; + + /* Set multicast_ladrf. */ + if (num_addrs > 0) { + /* Calculate multicast logical address filter */ + memset(lp->multicast_ladrf, 0, MACE_LADRF_LEN); + for (i = 0; i < dev->mc_count; i++) { + memcpy(adr, dmi->dmi_addr, ETHER_ADDR_LEN); + dmi = dmi->next; + BuildLAF(lp->multicast_ladrf, adr); + } + } + + restore_multicast_list(dev); + +} /* set_multicast_list */ + +#endif /* BROKEN_MULTICAST */ + +static void restore_multicast_list(struct net_device *dev) +{ + ioaddr_t ioaddr = dev->base_addr; + + DEBUG(2, "%s: restoring Rx mode to %d addresses.\n", dev->name, + ((mace_private *)(dev->priv))->multicast_num_addrs); + + if (dev->flags & IFF_PROMISC) { + /* Promiscuous mode: receive all packets */ + mace_write(ioaddr, MACE_UTR, MACE_UTR_LOOP_EXTERNAL); + mace_write(ioaddr, MACE_MACCC, + MACE_MACCC_PROM | MACE_MACCC_ENXMT | MACE_MACCC_ENRCV + ); + } else { + /* Normal mode */ + mace_write(ioaddr, MACE_UTR, MACE_UTR_LOOP_EXTERNAL); + mace_write(ioaddr, MACE_MACCC, MACE_MACCC_ENXMT | MACE_MACCC_ENRCV); + } +} /* restore_multicast_list */ + +static void set_multicast_list(struct net_device *dev) +{ + mace_private *lp = (mace_private *)dev->priv; + +#ifdef PCMCIA_DEBUG + if (pc_debug > 1) { + static int old = 0; + if (dev->mc_count != old) { + old = dev->mc_count; + DEBUG(0, "%s: setting Rx mode to %d addresses.\n", + dev->name, old); + } + } +#endif + + lp->multicast_num_addrs = dev->mc_count; + restore_multicast_list(dev); + +} /* set_multicast_list */ + +/* ---------------------------------------------------------------------------- +init_nmclan_cs +---------------------------------------------------------------------------- */ + +static int __init init_nmclan_cs(void) +{ + servinfo_t serv; + DEBUG(0, "%s\n", version); + CardServices(GetCardServicesInfo, &serv); + if (serv.Revision != CS_RELEASE_CODE) { + printk(KERN_NOTICE "nmclan_cs: Card Services release does not match!\n"); + return -EINVAL; + } + register_pccard_driver(&dev_info, &nmclan_attach, &nmclan_detach); + return 0; +} + +/* ---------------------------------------------------------------------------- +exit_nmclan_cs +---------------------------------------------------------------------------- */ + +static void __exit exit_nmclan_cs(void) +{ + DEBUG(0, "nmclan_cs: unloading\n"); + unregister_pccard_driver(&dev_info); + while (dev_list != NULL) + nmclan_detach(dev_list); +} + +module_init(init_nmclan_cs); +module_exit(exit_nmclan_cs); diff --git a/linux/pcmcia-cs/clients/ositech.h b/linux/pcmcia-cs/clients/ositech.h new file mode 100644 index 0000000..4126efc --- /dev/null +++ b/linux/pcmcia-cs/clients/ositech.h @@ -0,0 +1,358 @@ +/* + This file contains the firmware of Seven of Diamonds from OSITECH. + (Special thanks to Kevin MacPherson of OSITECH) + + This software may be used and distributed according to the terms of + the GNU General Public License, incorporated herein by reference. +*/ + + static const u_char __Xilinx7OD[] = { + 0xFF, 0x04, 0xA0, 0x36, 0xF3, 0xEC, 0xFF, 0xFF, 0xFF, 0xDF, 0xFB, 0xFF, + 0xF3, 0xFF, 0xFF, 0xFF, + 0xEF, 0x3F, 0xFF, 0xF7, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0x7F, 0xFE, 0xFF, + 0xCE, 0xFE, 0xFE, 0xFE, + 0xFE, 0xDE, 0xBD, 0xDD, 0xFD, 0xFF, 0xFD, 0xCF, 0xF7, 0xBF, 0x7F, 0xFF, + 0x7F, 0x3F, 0xFE, 0xBF, + 0xFF, 0xFF, 0xFF, 0xBC, 0xFF, 0xFF, 0xBD, 0xB5, 0x7F, 0x7F, 0xBF, 0xBF, + 0x7F, 0xFF, 0xEF, 0xFF, + 0xFF, 0xFF, 0xFB, 0xFF, 0xF7, 0xF7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xDE, + 0xFE, 0xFE, 0xFA, 0xDE, + 0xBD, 0xFD, 0xED, 0xFD, 0xFD, 0xCF, 0xEF, 0xEF, 0xEF, 0xEF, 0xC7, 0xDF, + 0xDF, 0xDF, 0xDF, 0xDF, + 0xFF, 0x7E, 0xFE, 0xFD, 0x7D, 0x6D, 0xEE, 0xFE, 0x7C, 0xFB, 0xF4, 0xFB, + 0xCF, 0xDB, 0xDF, 0xFF, + 0xFF, 0xBB, 0x7F, 0xFF, 0x7F, 0xFF, 0xF7, 0xFF, 0x9E, 0xBF, 0x3B, 0xBF, + 0xBF, 0x7F, 0x7F, 0x7F, + 0x7E, 0x6F, 0xDF, 0xEF, 0xF5, 0xF6, 0xFD, 0xF6, 0xF5, 0xED, 0xEB, 0xFF, + 0xEF, 0xEF, 0xEF, 0x7E, + 0x7F, 0x7F, 0x6F, 0x7F, 0xFF, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xEF, 0xBF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBC, 0x1F, 0x1F, 0xEE, 0xFF, 0xBC, + 0xB7, 0xFF, 0xDF, 0xFF, + 0xDF, 0xEF, 0x3B, 0xE3, 0xD3, 0xFF, 0xFB, 0xFF, 0xFF, 0xDF, 0xFF, 0xFF, + 0xFF, 0xBA, 0xBF, 0x2D, + 0xDB, 0xBD, 0xFD, 0xDB, 0xDF, 0xFA, 0xFB, 0xFF, 0xEF, 0xFB, 0xDB, 0xF3, + 0xFF, 0xDF, 0xFD, 0x7F, + 0xEF, 0xFB, 0xFF, 0xFF, 0xBE, 0xBF, 0x27, 0xBA, 0xFE, 0xFB, 0xDF, 0xFF, + 0xF6, 0xFF, 0xFF, 0xEF, + 0xFB, 0xDB, 0xF3, 0xD9, 0x9A, 0x3F, 0xFF, 0xAF, 0xBF, 0xFF, 0xFF, 0xBE, + 0x3F, 0x37, 0xBD, 0x96, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xAE, 0xFB, 0xF3, 0xF3, 0xEB, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF7, 0xFA, 0xBC, 0xAE, 0xFE, 0xBE, 0xFE, 0xBB, 0x7F, 0xFD, 0xFF, + 0x7F, 0xEF, 0xF7, 0xFB, + 0xBB, 0xD7, 0xF7, 0x7F, 0xFF, 0xF7, 0xFF, 0xFF, 0xF7, 0xBC, 0xED, 0xFD, + 0xBD, 0x9D, 0x7D, 0x7B, + 0xFB, 0x7B, 0x7B, 0xFB, 0xAF, 0xFF, 0xFE, 0xFD, 0xFD, 0xFE, 0xFE, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF7, + 0xAA, 0xB9, 0xBF, 0x8F, 0xBF, 0xDF, 0xFF, 0x7F, 0xFF, 0xFF, 0x7F, 0xCF, + 0xFB, 0xEB, 0xCB, 0xEB, + 0xEE, 0xFF, 0xFF, 0xD7, 0xFF, 0xFF, 0xFF, 0x3E, 0x33, 0x3F, 0x1C, 0x7C, + 0xFC, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xCF, 0xD3, 0xF3, 0xE3, 0xF3, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xEB, 0xFE, 0x35, + 0x3F, 0x3D, 0xFD, 0xFD, 0xFF, 0xFF, 0xFF, 0xBF, 0xFF, 0xEF, 0x6F, 0xE3, + 0xE3, 0xE3, 0xEF, 0xFF, + 0xFF, 0xDF, 0xFF, 0xFF, 0xF7, 0xFE, 0x3E, 0x5E, 0xFE, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFD, 0xFF, 0xFF, + 0xAF, 0xCF, 0xF2, 0xCB, 0xCF, 0x8E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, + 0xFC, 0x3E, 0x1F, 0x9E, + 0xAD, 0xFD, 0xFF, 0xFF, 0xBF, 0xFF, 0xFF, 0xEF, 0xFF, 0xB3, 0xF7, 0xE7, + 0xF7, 0xFA, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xEE, 0xEB, 0xAB, 0xAF, 0x9F, 0xE3, 0x7F, 0xFF, 0xDE, + 0xFF, 0x7F, 0xEE, 0xFF, + 0xFF, 0xFB, 0x3A, 0xFA, 0xFF, 0xF2, 0x77, 0xFF, 0xFF, 0xF7, 0xFE, 0xFF, + 0xFE, 0xBD, 0xAE, 0xDE, + 0x7D, 0x7D, 0xFD, 0xFF, 0xBF, 0xEE, 0xFF, 0xFD, 0xFF, 0xDB, 0xFB, 0xFF, + 0xF7, 0xEF, 0xFB, 0xFF, + 0xFF, 0xFE, 0xFF, 0x2D, 0xAF, 0xB9, 0xFD, 0x79, 0xFB, 0xFA, 0xFF, 0xBF, + 0xEF, 0xFF, 0xFF, 0x91, + 0xFA, 0xFB, 0xDF, 0xF7, 0xF7, 0xFF, 0xFF, 0xFF, 0xFC, 0xCF, 0x37, 0xBF, + 0xBF, 0xFF, 0x7F, 0x7F, + 0xFF, 0xFF, 0xFF, 0xAF, 0xFF, 0xFF, 0xF3, 0xFB, 0xFB, 0xFF, 0xF5, 0xEF, + 0xFF, 0xFF, 0xF7, 0xFA, + 0xFF, 0xFF, 0xEE, 0xFA, 0xFE, 0xFB, 0x55, 0xDD, 0xFF, 0x7F, 0xAF, 0xFE, + 0xFF, 0xFB, 0xFB, 0xF5, + 0xFF, 0xF7, 0xEF, 0xFF, 0xFF, 0xFF, 0xBE, 0xBD, 0xBD, 0xBD, 0xBD, 0x7D, + 0x7B, 0x7B, 0x7B, 0x7B, + 0xFB, 0xAE, 0xFF, 0xFD, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF7, 0xDA, 0xB7, 0x61, + 0xFF, 0xB9, 0x59, 0xF3, 0x73, 0xF3, 0xDF, 0x7F, 0x6F, 0xDF, 0xEF, 0xF7, + 0xEB, 0xEB, 0xD7, 0xFF, + 0xD7, 0xFF, 0xFF, 0xF7, 0xFE, 0x7F, 0xFB, 0x3E, 0x38, 0x73, 0xF6, 0x7F, + 0xFC, 0xFF, 0xFF, 0xCF, + 0xFF, 0xB7, 0xFB, 0xB3, 0xB3, 0x67, 0xFF, 0xE7, 0xFD, 0xFF, 0xEF, 0xF6, + 0x7F, 0xB7, 0xBC, 0xF5, + 0x7B, 0xF6, 0xF7, 0xF5, 0xFF, 0xFF, 0xEF, 0xFF, 0xF7, 0xFF, 0xF7, 0xCE, + 0xE7, 0xFF, 0x9F, 0xFF, + 0xFF, 0xF5, 0xFE, 0x7D, 0xFF, 0x5F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xEF, 0xFF, 0xF6, + 0xCB, 0xDB, 0xEE, 0xFE, 0xFF, 0xDF, 0xFF, 0xFF, 0xFF, 0xFE, 0x7F, 0xBE, + 0x1E, 0x3E, 0xFE, 0xFF, + 0x7D, 0xFE, 0xFF, 0xFF, 0xEF, 0xBF, 0xE7, 0xFF, 0xE3, 0xE3, 0xFF, 0xDF, + 0xE7, 0xFF, 0xFF, 0xFF, + 0xB8, 0xEF, 0xB7, 0x2F, 0xEE, 0xFF, 0xDF, 0xFF, 0xBF, 0xFF, 0x7F, 0xEF, + 0xEB, 0xBF, 0xA3, 0xD3, + 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0xBE, 0xFD, 0x3F, 0xCF, 0xFD, + 0xFB, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xAF, 0xFB, 0xBF, 0xBB, 0xBF, 0xDB, 0xFD, 0xFB, 0xFF, 0xFF, + 0xFF, 0xFF, 0x3E, 0xFE, + 0x3F, 0xBA, 0xBA, 0xFE, 0xFF, 0xFF, 0xFF, 0xEF, 0xFF, 0xEF, 0xC3, 0x7F, + 0xB2, 0x9B, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0x3C, 0xFF, 0x3F, 0x3C, 0xFF, 0xFE, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xAF, 0xF3, 0xFE, 0xF3, 0xE3, 0xEB, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, 0xF7, + 0x9A, 0xFE, 0xAF, 0x9E, + 0xBE, 0xFE, 0xFF, 0xDF, 0xFF, 0xFF, 0x7B, 0xEF, 0xF7, 0xBF, 0xFB, 0xFB, + 0xFB, 0xFF, 0xFF, 0x7F, + 0xFF, 0xFF, 0xFF, 0xBC, 0xBD, 0xFD, 0xBD, 0xDD, 0x7D, 0x7B, 0x7B, 0x7B, + 0x7B, 0xFB, 0xAE, 0xFF, + 0xFF, 0xFF, 0xFE, 0xFE, 0xFF, 0xFD, 0xFF, 0xFF, 0xFF, 0xF7, 0x9A, 0xFF, + 0x9F, 0xFF, 0xAF, 0xEF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xCF, 0xF3, 0xFF, 0xEB, 0xFF, 0xEB, 0xFF, + 0xFF, 0xBF, 0xFF, 0xFF, + 0xEF, 0xFE, 0xFF, 0x37, 0xFC, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xCF, 0xEF, 0xFD, 0xF3, + 0xFF, 0xEE, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x6E, 0xFD, 0x2F, 0xFD, + 0xFF, 0xFD, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xEF, 0xCF, 0xFF, 0xF3, 0xBF, 0x69, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFE, + 0xFB, 0x9F, 0xFF, 0xBF, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0x87, + 0xFE, 0xDA, 0xEF, 0xCF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xEF, 0xBF, 0xEF, 0xEF, 0xFD, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xEF, 0xFD, 0xFF, 0x7B, 0xFF, 0xEB, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, + 0xEB, 0xF8, 0xFF, 0xEF, + 0xAF, 0xFF, 0xFF, 0xBD, 0xFF, 0xFF, 0xFF, 0x7F, 0xEE, 0x7F, 0xEF, 0xFF, + 0xBB, 0xFF, 0xBF, 0xFB, + 0xFF, 0xFF, 0xFF, 0xF7, 0xF6, 0xFB, 0xBD, 0xFD, 0xDD, 0xF5, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xAF, + 0xFF, 0x5F, 0xF5, 0xDF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF6, + 0xF3, 0xFF, 0xDE, 0xFE, + 0xEF, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0xFF, 0xDE, 0xDF, 0x5F, 0xDF, + 0xFD, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFE, 0xFE, 0xFF, 0xFD, 0xFF, 0xFF, 0xFF, + 0xFF, 0xAF, 0xFF, 0xFF, + 0xEF, 0xED, 0xFF, 0xDF, 0xFF, 0xFF, 0xFB, 0xFF, 0xFF, 0xDA, 0xBD, 0xBE, + 0xAE, 0xFE, 0x7F, 0xFD, + 0xDF, 0xFF, 0xFF, 0x7F, 0xEF, 0xFF, 0xFB, 0xFB, 0xFB, 0x7F, 0xF7, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF7, + 0xBC, 0xFD, 0xBD, 0xBD, 0xBD, 0xFD, 0x7B, 0x7B, 0x7B, 0x7B, 0xFB, 0xAE, + 0xFF, 0xFF, 0xFD, 0xFF, + 0xFF, 0xFF, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFA, 0x9F, 0xBF, 0xBF, 0xCF, + 0x7F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xAF, 0xFF, 0xEB, 0xEB, 0xEB, 0xFF, 0xD7, 0xFE, 0xFF, 0xFF, + 0xBF, 0xE7, 0xFE, 0xBF, + 0x7F, 0xFC, 0xFF, 0xFF, 0xED, 0xFF, 0xFF, 0xFF, 0xFF, 0x4F, 0xFF, 0xFB, + 0xFB, 0xFF, 0xFF, 0xDD, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xBD, 0xDF, 0x9D, 0xFD, 0xDF, 0xB9, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xEF, 0xFF, 0xFB, 0xEF, 0xEB, 0xFF, 0xDE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF6, 0x9F, 0xFF, 0xFC, + 0xFE, 0xFB, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0xDF, 0xFA, 0xCD, 0xCF, + 0xBF, 0x9F, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF7, 0xFE, 0xBF, 0xFF, 0xDF, 0xEF, 0x5F, 0xFF, 0xFF, 0xFF, + 0xFF, 0x7F, 0x6F, 0xFF, + 0xBB, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7E, 0xFF, + 0x5F, 0xFF, 0xBF, 0xBF, + 0xF9, 0xFF, 0xFF, 0xFF, 0x7F, 0x6E, 0x7B, 0xFF, 0xEF, 0xFD, 0xEB, 0xDF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xF7, 0xB6, 0x3E, 0xFC, 0xFD, 0xBF, 0x7E, 0xFB, 0xFF, 0xFF, 0xFF, 0xF7, + 0xEF, 0xF7, 0xF3, 0xF7, + 0xFF, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x6E, 0x35, 0x79, 0xFF, + 0xBF, 0xFC, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xEF, 0xFB, 0x53, 0xDF, 0xFF, 0xEB, 0xBF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xBC, + 0xFF, 0xFF, 0xFF, 0xBF, 0xFF, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xAF, 0xF5, + 0xFF, 0xF7, 0xFF, 0xFB, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBA, 0xAA, 0xEE, 0xFE, 0x3F, 0x7D, + 0xFD, 0xFF, 0xFF, 0xFF, + 0x7F, 0xAF, 0x77, 0xFB, 0xFB, 0xFF, 0xFB, 0xF7, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF7, 0xBE, 0xBD, 0xBD, + 0xBD, 0xBD, 0xFD, 0x7B, 0x7B, 0x7B, 0x7B, 0xFB, 0xAE, 0xFF, 0xEF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFC, + 0xFF, 0xFF, 0xFF, 0xFF, 0x9A, 0xD9, 0xB8, 0xFF, 0xFF, 0x79, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xCF, + 0xFB, 0xFF, 0xEB, 0xFF, 0xEB, 0xD7, 0xFF, 0xFF, 0xFF, 0xFF, 0xE7, 0xDE, + 0xF8, 0xFB, 0xFE, 0x3F, + 0xFB, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xCF, 0xAD, 0xBF, 0xFA, 0xFF, 0x73, + 0xDF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x3A, 0xF5, 0xB7, 0xFC, 0x3F, 0xF9, 0xFD, 0xFF, 0xFF, 0xFF, + 0x7F, 0xEF, 0xF3, 0xFF, + 0xBF, 0xFE, 0xF3, 0x9F, 0xFE, 0xFF, 0xFF, 0xFF, 0xF7, 0x3E, 0xFF, 0xFF, + 0xFF, 0xBF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xAF, 0xD3, 0xFE, 0xDB, 0xFF, 0xDB, 0xDF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0x3E, 0xFF, 0xBF, 0xFF, 0x7F, 0xFF, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0x8F, + 0xF3, 0xFF, 0xED, 0xFF, + 0xF7, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0xF6, 0x3C, 0xFE, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x9F, 0xEF, 0xEF, 0xD1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x7E, 0xBF, + 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBB, 0xEF, 0xDF, 0xF1, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEE, 0x3E, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xBF, + 0xEF, 0xFD, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBF, 0xFF, + 0xFC, 0x3E, 0xFE, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x2E, 0xEF, 0xF3, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF7, 0xBA, 0xBE, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x7F, 0xAF, 0xFB, + 0xFB, 0xFD, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xF2, 0xD6, 0xED, + 0xBD, 0xBD, 0xBD, 0x7D, + 0x7B, 0x7B, 0x7B, 0x7B, 0xFB, 0xAF, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x92, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, + 0xAF, 0xEB, 0xEB, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE7, 0xFE, 0x2E, 0xFE, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x4F, 0xEF, 0xF3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFE, + 0x3C, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0xCE, + 0xC3, 0xFD, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x5D, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xEF, 0xCF, 0xEB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF7, 0xEE, 0x3E, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xEF, 0xDF, 0xE2, 0xFF, + 0xFF, 0xFF, 0xFB, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xF6, 0xBE, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x7F, 0xEE, + 0x5F, 0xE6, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3E, + 0x7D, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0xF3, 0xFB, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xBF, 0xF7, 0x36, 0xBE, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xEF, 0xD3, 0xF6, + 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x7F, 0xEE, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xAF, 0xEF, 0xEB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xBA, 0xBE, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEE, + 0xFB, 0xFA, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0xD6, 0xFD, 0xBD, 0xBD, 0xBD, + 0x7D, 0x7B, 0x7B, 0x7B, + 0x7B, 0xFB, 0xAE, 0xFF, 0x7E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF7, 0xBA, 0xBF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xEF, 0xEB, 0x6B, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0xFE, 0xBE, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0x4F, 0xEF, 0xF7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, + 0x3E, 0x6E, 0xFC, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0xC3, 0xC9, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x3E, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xEF, 0xFB, + 0xD5, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFE, + 0xFE, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x6F, 0xEF, 0xFB, 0xFF, 0xFF, 0xFF, 0xFB, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF6, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xFE, + 0xEF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE7, 0xFF, 0xFE, 0xFF, 0xF7, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x7F, 0xFA, 0xEF, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xE7, 0xFF, 0xFE, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xFE, 0xEF, 0xBF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xA7, 0xFF, 0xFC, 0xF7, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x7F, + 0xFE, 0xAE, 0xFF, 0xFF, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE7, + 0xF7, 0xFA, 0xFF, 0xFD, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xAF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xF7, 0xBE, 0xBD, 0xBD, 0xBD, 0xBD, 0x7D, 0x7B, 0x7B, + 0x7B, 0x7B, 0xFB, 0xAF, + 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xCA, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x6F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xE7, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xCF, 0xFE, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xDF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xEF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, 0xFF, 0xFF, + 0xFF, 0xE7, 0xF2, 0xFC, + 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xAE, 0xEF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x7E, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xEF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xBF, 0xFF, 0xFF, 0xFF, 0xBF, 0xFF, + 0xFE, 0xFE, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xDF, 0xEF, 0xDD, 0xFE, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFE, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xAF, 0xEF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBA, 0xFE, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0xFA, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xF6, 0x9C, 0xBD, 0xBD, 0xBD, 0xBD, 0x7D, 0x7B, 0x7B, 0x7B, 0x7B, 0xFB, + 0xAE, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x7A, 0xFF, 0xFF, 0xFF, + 0xFF, 0xDF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x6F, 0xEF, 0xF7, 0xFF, 0xFF, 0xFF, 0xDF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF7, 0xFE, + 0xFE, 0xFF, 0xFF, 0xFF, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xCF, 0xEB, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0x9E, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xEF, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFE, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xEF, 0xCB, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFD, + 0xFF, 0xFF, 0xFF, 0xFF, 0xBE, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xEF, + 0xEF, 0xFF, 0xFF, 0xFF, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFB, 0xAF, 0x7F, 0xFF, + 0xFF, 0xFF, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xEF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xBF, 0xFF, + 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xAE, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0xFA, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x7F, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xF7, 0xBC, 0xBD, + 0xBD, 0xBD, 0xBD, 0x7D, 0x7B, 0x7B, 0x7B, 0x7B, 0xFB, 0xAF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0xFA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x7F, + 0xAF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, + 0xFE, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, + 0xFF, 0xFF, 0xEF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xBF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xEF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0xFE, 0xFF, 0x9F, 0x9F, + 0x9F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0xFF, 0xEF, 0xDF, 0xDF, 0xDF, 0xDF, 0xCF, 0xB7, 0xBF, 0xBF, + 0xBF, 0xBF, 0xFF, 0xBC, + 0xB9, 0x9D, 0xBD, 0xBD, 0x7D, 0x7B, 0x7B, 0x7B, 0x7B, 0xFB, 0xEF, 0xD7, + 0xF5, 0xF3, 0xF1, 0xD1, + 0x65, 0xE3, 0xE3, 0xE3, 0xA3, 0xFF, 0xFE, 0x7F, 0xFE, 0xDE, 0xDE, 0xFF, + 0xBD, 0xBD, 0xBD, 0xBD, + 0xDF, 0xEF, 0xFB, 0xF7, 0xF3, 0xF3, 0xF3, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, + 0xFB, 0xFE, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF + + }; diff --git a/linux/pcmcia-cs/clients/pcnet_cs.c b/linux/pcmcia-cs/clients/pcnet_cs.c new file mode 100644 index 0000000..8b3c1ec --- /dev/null +++ b/linux/pcmcia-cs/clients/pcnet_cs.c @@ -0,0 +1,1702 @@ +/*====================================================================== + + A PCMCIA ethernet driver for NS8390-based cards + + This driver supports the D-Link DE-650 and Linksys EthernetCard + cards, the newer D-Link and Linksys combo cards, Accton EN2212 + cards, the RPTI EP400, and the PreMax PE-200 in non-shared-memory + mode, and the IBM Credit Card Adapter, the NE4100, the Thomas + Conrad ethernet card, and the Kingston KNE-PCM/x in shared-memory + mode. It will also handle the Socket EA card in either mode. + + Copyright (C) 1999 David A. Hinds -- dahinds@users.sourceforge.net + + pcnet_cs.c 1.153 2003/11/09 18:53:09 + + The network driver code is based on Donald Becker's NE2000 code: + + Written 1992,1993 by Donald Becker. + Copyright 1993 United States Government as represented by the + Director, National Security Agency. This software may be used and + distributed according to the terms of the GNU General Public License, + incorporated herein by reference. + Donald Becker may be reached at becker@scyld.com + + Based also on Keith Moore's changes to Don Becker's code, for IBM + CCAE support. Drivers merged back together, and shared-memory + Socket EA support added, by Ken Raeburn, September 1995. + +======================================================================*/ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/ptrace.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <asm/io.h> +#include <asm/system.h> +#include <asm/byteorder.h> +#include <asm/uaccess.h> + +#include <linux/netdevice.h> +#include <../drivers/net/8390.h> + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/ciscode.h> +#include <pcmcia/ds.h> +#include <pcmcia/cisreg.h> + +#define PCNET_CMD 0x00 +#define PCNET_DATAPORT 0x10 /* NatSemi-defined port window offset. */ +#define PCNET_RESET 0x1f /* Issue a read to reset, a write to clear. */ +#define PCNET_MISC 0x18 /* For IBM CCAE and Socket EA cards */ + +#define PCNET_START_PG 0x40 /* First page of TX buffer */ +#define PCNET_STOP_PG 0x80 /* Last page +1 of RX ring */ + +/* Socket EA cards have a larger packet buffer */ +#define SOCKET_START_PG 0x01 +#define SOCKET_STOP_PG 0xff + +#define PCNET_RDC_TIMEOUT (2*HZ/100) /* Max wait in jiffies for Tx RDC */ + +static char *if_names[] = { "auto", "10baseT", "10base2"}; + +#ifdef PCMCIA_DEBUG +static int pc_debug = PCMCIA_DEBUG; +MODULE_PARM(pc_debug, "i"); +#define DEBUG(n, args...) if (pc_debug>(n)) printk(KERN_DEBUG args) +static char *version = +"pcnet_cs.c 1.153 2003/11/09 18:53:09 (David Hinds)"; +#else +#define DEBUG(n, args...) +#endif + +/*====================================================================*/ + +/* Module parameters */ + +MODULE_AUTHOR("David Hinds <dahinds@users.sourceforge.net>"); +MODULE_DESCRIPTION("NE2000 compatible PCMCIA ethernet driver"); +MODULE_LICENSE("GPL"); + +#define INT_MODULE_PARM(n, v) static int n = v; MODULE_PARM(n, "i") + +/* Bit map of interrupts to choose from */ +INT_MODULE_PARM(irq_mask, 0xdeb8); +static int irq_list[4] = { -1 }; +MODULE_PARM(irq_list, "1-4i"); + +INT_MODULE_PARM(if_port, 1); /* Transceiver type */ +INT_MODULE_PARM(use_big_buf, 1); /* use 64K packet buffer? */ +INT_MODULE_PARM(mem_speed, 0); /* shared mem speed, in ns */ +INT_MODULE_PARM(delay_output, 0); /* pause after xmit? */ +INT_MODULE_PARM(delay_time, 4); /* in usec */ +INT_MODULE_PARM(use_shmem, -1); /* use shared memory? */ +INT_MODULE_PARM(full_duplex, 0); /* full duplex? */ + +/* Ugh! Let the user hardwire the hardware address for queer cards */ +static int hw_addr[6] = { 0, /* ... */ }; +MODULE_PARM(hw_addr, "6i"); + +/*====================================================================*/ + +static void mii_phy_probe(struct net_device *dev); +static void pcnet_config(dev_link_t *link); +static void pcnet_release(u_long arg); +static int pcnet_event(event_t event, int priority, + event_callback_args_t *args); +static int pcnet_open(struct net_device *dev); +static int pcnet_close(struct net_device *dev); +static int ei_ioctl(struct net_device *dev, struct ifreq *rq, int cmd); +static void ei_irq_wrapper(int irq, void *dev_id, struct pt_regs *regs); +static void ei_watchdog(u_long arg); +static void pcnet_reset_8390(struct net_device *dev); +static int set_config(struct net_device *dev, struct ifmap *map); +static int setup_shmem_window(dev_link_t *link, int start_pg, + int stop_pg, int cm_offset); +static int setup_dma_config(dev_link_t *link, int start_pg, + int stop_pg); + +static dev_link_t *pcnet_attach(void); +static void pcnet_detach(dev_link_t *); + +static dev_info_t dev_info = "pcnet_cs"; +static dev_link_t *dev_list; + +/*====================================================================*/ + +typedef struct hw_info_t { + u_int offset; + u_char a0, a1, a2; + u_int flags; +} hw_info_t; + +#define DELAY_OUTPUT 0x01 +#define HAS_MISC_REG 0x02 +#define USE_BIG_BUF 0x04 +#define HAS_IBM_MISC 0x08 +#define IS_DL10019 0x10 +#define IS_DL10022 0x20 +#define HAS_MII 0x40 +#define USE_SHMEM 0x80 /* autodetected */ + +#define AM79C9XX_HOME_PHY 0x00006B90 /* HomePNA PHY */ +#define AM79C9XX_ETH_PHY 0x00006B70 /* 10baseT PHY */ +#define MII_PHYID_REV_MASK 0xfffffff0 +#define MII_PHYID_REG1 0x02 +#define MII_PHYID_REG2 0x03 + +static hw_info_t hw_info[] = { + { /* Accton EN2212 */ 0x0ff0, 0x00, 0x00, 0xe8, DELAY_OUTPUT }, + { /* Allied Telesis LA-PCM */ 0x0ff0, 0x00, 0x00, 0xf4, 0 }, + { /* APEX MultiCard */ 0x03f4, 0x00, 0x20, 0xe5, 0 }, + { /* ASANTE FriendlyNet */ 0x4910, 0x00, 0x00, 0x94, + DELAY_OUTPUT | HAS_IBM_MISC }, + { /* Danpex EN-6200P2 */ 0x0110, 0x00, 0x40, 0xc7, 0 }, + { /* DataTrek NetCard */ 0x0ff0, 0x00, 0x20, 0xe8, 0 }, + { /* Dayna CommuniCard E */ 0x0110, 0x00, 0x80, 0x19, 0 }, + { /* D-Link DE-650 */ 0x0040, 0x00, 0x80, 0xc8, 0 }, + { /* EP-210 Ethernet */ 0x0110, 0x00, 0x40, 0x33, 0 }, + { /* EP4000 Ethernet */ 0x01c0, 0x00, 0x00, 0xb4, 0 }, + { /* Epson EEN10B */ 0x0ff0, 0x00, 0x00, 0x48, + HAS_MISC_REG | HAS_IBM_MISC }, + { /* ELECOM Laneed LD-CDWA */ 0xb8, 0x08, 0x00, 0x42, 0 }, + { /* Hypertec Ethernet */ 0x01c0, 0x00, 0x40, 0x4c, 0 }, + { /* IBM CCAE */ 0x0ff0, 0x08, 0x00, 0x5a, + HAS_MISC_REG | HAS_IBM_MISC }, + { /* IBM CCAE */ 0x0ff0, 0x00, 0x04, 0xac, + HAS_MISC_REG | HAS_IBM_MISC }, + { /* IBM CCAE */ 0x0ff0, 0x00, 0x06, 0x29, + HAS_MISC_REG | HAS_IBM_MISC }, + { /* IBM FME */ 0x0374, 0x08, 0x00, 0x5a, + HAS_MISC_REG | HAS_IBM_MISC }, + { /* IBM FME */ 0x0374, 0x00, 0x04, 0xac, + HAS_MISC_REG | HAS_IBM_MISC }, + { /* Kansai KLA-PCM/T */ 0x0ff0, 0x00, 0x60, 0x87, + HAS_MISC_REG | HAS_IBM_MISC }, + { /* NSC DP83903 */ 0x0374, 0x08, 0x00, 0x17, + HAS_MISC_REG | HAS_IBM_MISC }, + { /* NSC DP83903 */ 0x0374, 0x00, 0xc0, 0xa8, + HAS_MISC_REG | HAS_IBM_MISC }, + { /* NSC DP83903 */ 0x0374, 0x00, 0xa0, 0xb0, + HAS_MISC_REG | HAS_IBM_MISC }, + { /* NSC DP83903 */ 0x0198, 0x00, 0x20, 0xe0, + HAS_MISC_REG | HAS_IBM_MISC }, + { /* I-O DATA PCLA/T */ 0x0ff0, 0x00, 0xa0, 0xb0, 0 }, + { /* Katron PE-520 */ 0x0110, 0x00, 0x40, 0xf6, 0 }, + { /* Kingston KNE-PCM/x */ 0x0ff0, 0x00, 0xc0, 0xf0, + HAS_MISC_REG | HAS_IBM_MISC }, + { /* Kingston KNE-PCM/x */ 0x0ff0, 0xe2, 0x0c, 0x0f, + HAS_MISC_REG | HAS_IBM_MISC }, + { /* Kingston KNE-PC2 */ 0x0180, 0x00, 0xc0, 0xf0, 0 }, + { /* Maxtech PCN2000 */ 0x5000, 0x00, 0x00, 0xe8, 0 }, + { /* NDC Instant-Link */ 0x003a, 0x00, 0x80, 0xc6, 0 }, + { /* NE2000 Compatible */ 0x0ff0, 0x00, 0xa0, 0x0c, 0 }, + { /* Network General Sniffer */ 0x0ff0, 0x00, 0x00, 0x65, + HAS_MISC_REG | HAS_IBM_MISC }, + { /* Panasonic VEL211 */ 0x0ff0, 0x00, 0x80, 0x45, + HAS_MISC_REG | HAS_IBM_MISC }, + { /* PreMax PE-200 */ 0x07f0, 0x00, 0x20, 0xe0, 0 }, + { /* RPTI EP400 */ 0x0110, 0x00, 0x40, 0x95, 0 }, + { /* SCM Ethernet */ 0x0ff0, 0x00, 0x20, 0xcb, 0 }, + { /* Socket EA */ 0x4000, 0x00, 0xc0, 0x1b, + DELAY_OUTPUT | HAS_MISC_REG | USE_BIG_BUF }, + { /* Socket LP-E CF+ */ 0x01c0, 0x00, 0xc0, 0x1b, 0 }, + { /* SuperSocket RE450T */ 0x0110, 0x00, 0xe0, 0x98, 0 }, + { /* Volktek NPL-402CT */ 0x0060, 0x00, 0x40, 0x05, 0 }, + { /* NEC PC-9801N-J12 */ 0x0ff0, 0x00, 0x00, 0x4c, 0 }, + { /* PCMCIA Technology OEM */ 0x01c8, 0x00, 0xa0, 0x0c, 0 } +}; + +#define NR_INFO (sizeof(hw_info)/sizeof(hw_info_t)) + +static hw_info_t default_info = { 0, 0, 0, 0, 0 }; +static hw_info_t dl10019_info = { 0, 0, 0, 0, IS_DL10019|HAS_MII }; +static hw_info_t dl10022_info = { 0, 0, 0, 0, IS_DL10022|HAS_MII }; + +typedef struct pcnet_dev_t { + struct net_device dev; /* so &dev == &pcnet_dev_t */ + dev_link_t link; + dev_node_t node; + u_int flags; + caddr_t base; + struct timer_list watchdog; + int stale, fast_poll; + u_char phy_id; + u_char eth_phy, pna_phy; + u_short link_status; + u_long mii_reset; +} pcnet_dev_t; + +/*====================================================================== + + This bit of code is used to avoid unregistering network devices + at inappropriate times. 2.2 and later kernels are fairly picky + about when this can happen. + +======================================================================*/ + +static void flush_stale_links(void) +{ + dev_link_t *link, *next; + for (link = dev_list; link; link = next) { + next = link->next; + if (link->state & DEV_STALE_LINK) + pcnet_detach(link); + } +} + +/*====================================================================*/ + +static void cs_error(client_handle_t handle, int func, int ret) +{ + error_info_t err = { func, ret }; + CardServices(ReportError, handle, &err); +} + +/*====================================================================== + + 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 pcnet_init(struct net_device *dev) +{ + return 0; +} + +/*====================================================================== + + pcnet_attach() creates an "instance" of the driver, allocating + local data structures for one device. The device is registered + with Card Services. + +======================================================================*/ + +static dev_link_t *pcnet_attach(void) +{ + pcnet_dev_t *info; + dev_link_t *link; + struct net_device *dev; + client_reg_t client_reg; + int i, ret; + + DEBUG(0, "pcnet_attach()\n"); + flush_stale_links(); + + /* Create new ethernet device */ + info = kmalloc(sizeof(*info), GFP_KERNEL); + if (!info) return NULL; + memset(info, 0, sizeof(*info)); + link = &info->link; dev = &info->dev; + link->priv = info; + + init_timer(&link->release); + link->release.function = &pcnet_release; + link->release.data = (u_long)link; + link->irq.Attributes = IRQ_TYPE_EXCLUSIVE; + link->irq.IRQInfo1 = IRQ_INFO2_VALID|IRQ_LEVEL_ID; + if (irq_list[0] == -1) + link->irq.IRQInfo2 = irq_mask; + else + for (i = 0; i < 4; i++) + link->irq.IRQInfo2 |= 1 << irq_list[i]; + link->conf.Attributes = CONF_ENABLE_IRQ; + link->conf.IntType = INT_MEMORY_AND_IO; + + ethdev_init(dev); + init_dev_name(dev, info->node); + dev->init = &pcnet_init; + dev->open = &pcnet_open; + dev->stop = &pcnet_close; + dev->set_config = &set_config; + + /* 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 = &pcnet_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(link->handle, RegisterClient, ret); + pcnet_detach(link); + return NULL; + } + + return link; +} /* pcnet_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 pcnet_detach(dev_link_t *link) +{ + pcnet_dev_t *info = link->priv; + dev_link_t **linkp; + + DEBUG(0, "pcnet_detach(0x%p)\n", link); + + /* Locate device structure */ + for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next) + if (*linkp == link) break; + if (*linkp == NULL) + return; + + del_timer(&link->release); + if (link->state & DEV_CONFIG) { + pcnet_release((u_long)link); + if (link->state & DEV_STALE_CONFIG) { + link->state |= DEV_STALE_LINK; + return; + } + } + + if (link->handle) + CardServices(DeregisterClient, link->handle); + + /* Unlink device structure, free bits */ + *linkp = link->next; + if (link->dev) + unregister_netdev(&info->dev); + kfree(info); + +} /* pcnet_detach */ + +/*====================================================================== + + This probes for a card's hardware address, for card types that + encode this information in their CIS. + +======================================================================*/ + +static hw_info_t *get_hwinfo(dev_link_t *link) +{ + struct net_device *dev = link->priv; + win_req_t req; + memreq_t mem; + u_char *base, *virt; + int i, j; + + /* Allocate a small memory window */ + req.Attributes = WIN_DATA_WIDTH_8|WIN_MEMORY_TYPE_AM|WIN_ENABLE; + req.Base = 0; req.Size = 0; + req.AccessSpeed = 0; + link->win = (window_handle_t)link->handle; + i = CardServices(RequestWindow, &link->win, &req); + if (i != CS_SUCCESS) { + cs_error(link->handle, RequestWindow, i); + return NULL; + } + + virt = ioremap(req.Base, req.Size); + mem.Page = 0; + for (i = 0; i < NR_INFO; i++) { + mem.CardOffset = hw_info[i].offset & ~(req.Size-1); + CardServices(MapMemPage, link->win, &mem); + base = &virt[hw_info[i].offset & (req.Size-1)]; + if ((readb(base+0) == hw_info[i].a0) && + (readb(base+2) == hw_info[i].a1) && + (readb(base+4) == hw_info[i].a2)) + break; + } + if (i < NR_INFO) { + for (j = 0; j < 6; j++) + dev->dev_addr[j] = readb(base + (j<<1)); + } + + iounmap(virt); + j = CardServices(ReleaseWindow, link->win); + if (j != CS_SUCCESS) + cs_error(link->handle, ReleaseWindow, j); + return (i < NR_INFO) ? hw_info+i : NULL; +} /* get_hwinfo */ + +/*====================================================================== + + This probes for a card's hardware address by reading the PROM. + It checks the address against a list of known types, then falls + back to a simple NE2000 clone signature check. + +======================================================================*/ + +static hw_info_t *get_prom(dev_link_t *link) +{ + struct net_device *dev = link->priv; + ioaddr_t ioaddr = dev->base_addr; + u_char prom[32]; + int i, j; + + /* This is lifted straight from drivers/net/ne.c */ + struct { + u_char value, offset; + } program_seq[] = { + {E8390_NODMA+E8390_PAGE0+E8390_STOP, E8390_CMD}, /* Select page 0*/ + {0x48, EN0_DCFG}, /* Set byte-wide (0x48) access. */ + {0x00, EN0_RCNTLO}, /* Clear the count regs. */ + {0x00, EN0_RCNTHI}, + {0x00, EN0_IMR}, /* Mask completion irq. */ + {0xFF, EN0_ISR}, + {E8390_RXOFF, EN0_RXCR}, /* 0x20 Set to monitor */ + {E8390_TXOFF, EN0_TXCR}, /* 0x02 and loopback mode. */ + {32, EN0_RCNTLO}, + {0x00, EN0_RCNTHI}, + {0x00, EN0_RSARLO}, /* DMA starting at 0x0000. */ + {0x00, EN0_RSARHI}, + {E8390_RREAD+E8390_START, E8390_CMD}, + }; + + pcnet_reset_8390(dev); + mdelay(10); + + for (i = 0; i < sizeof(program_seq)/sizeof(program_seq[0]); i++) + outb_p(program_seq[i].value, ioaddr + program_seq[i].offset); + + for (i = 0; i < 32; i++) + prom[i] = inb(ioaddr + PCNET_DATAPORT); + for (i = 0; i < NR_INFO; i++) { + if ((prom[0] == hw_info[i].a0) && + (prom[2] == hw_info[i].a1) && + (prom[4] == hw_info[i].a2)) + break; + } + if ((i < NR_INFO) || ((prom[28] == 0x57) && (prom[30] == 0x57))) { + for (j = 0; j < 6; j++) + dev->dev_addr[j] = prom[j<<1]; + return (i < NR_INFO) ? hw_info+i : &default_info; + } + return NULL; +} /* get_prom */ + +/*====================================================================== + + For DL10019 based cards, like the Linksys EtherFast + +======================================================================*/ + +static hw_info_t *get_dl10019(dev_link_t *link) +{ + struct net_device *dev = link->priv; + int i; + u_char sum; + + for (sum = 0, i = 0x14; i < 0x1c; i++) + sum += inb_p(dev->base_addr + i); + if (sum != 0xff) + return NULL; + for (i = 0; i < 6; i++) + dev->dev_addr[i] = inb_p(dev->base_addr + 0x14 + i); + i = inb(dev->base_addr + 0x1f); + return ((i == 0x91)||(i == 0x99)) ? &dl10022_info : &dl10019_info; +} + +/*====================================================================== + + For Asix AX88190 based cards + +======================================================================*/ + +static hw_info_t *get_ax88190(dev_link_t *link) +{ + struct net_device *dev = link->priv; + ioaddr_t ioaddr = dev->base_addr; + int i, j; + + /* Not much of a test, but the alternatives are messy */ + if (link->conf.ConfigBase != 0x03c0) + return NULL; + + outb_p(0x01, ioaddr + EN0_DCFG); /* Set word-wide access. */ + outb_p(0x00, ioaddr + EN0_RSARLO); /* DMA starting at 0x0400. */ + outb_p(0x04, ioaddr + EN0_RSARHI); + outb_p(E8390_RREAD+E8390_START, ioaddr + E8390_CMD); + + for (i = 0; i < 6; i += 2) { + j = inw(ioaddr + PCNET_DATAPORT); + dev->dev_addr[i] = j & 0xff; + dev->dev_addr[i+1] = j >> 8; + } + printk(KERN_NOTICE "pcnet_cs: this is an AX88190 card!\n"); + printk(KERN_NOTICE "pcnet_cs: use axnet_cs instead.\n"); + return NULL; +} + +/*====================================================================== + + This should be totally unnecessary... but when we can't figure + out the hardware address any other way, we'll let the user hard + wire it when the module is initialized. + +======================================================================*/ + +static hw_info_t *get_hwired(dev_link_t *link) +{ + struct net_device *dev = link->priv; + int i; + + for (i = 0; i < 6; i++) + if (hw_addr[i] != 0) break; + if (i == 6) + return NULL; + + for (i = 0; i < 6; i++) + dev->dev_addr[i] = hw_addr[i]; + + return &default_info; +} /* get_hwired */ + +/*====================================================================== + + pcnet_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. + +======================================================================*/ + +#define CS_CHECK(fn, args...) \ +while ((last_ret=CardServices(last_fn=(fn), args))!=0) goto cs_failed + +#define CFG_CHECK(fn, args...) \ +if (CardServices(fn, args) != 0) goto next_entry + +static int try_io_port(dev_link_t *link) +{ + int j, ret; + if (link->io.NumPorts1 == 32) { + link->io.Attributes1 = IO_DATA_PATH_WIDTH_AUTO; + if (link->io.NumPorts2 > 0) { + /* for master/slave multifunction cards */ + link->io.Attributes2 = IO_DATA_PATH_WIDTH_8; + link->irq.Attributes = + IRQ_TYPE_DYNAMIC_SHARING|IRQ_FIRST_SHARED; + } + } else { + /* This should be two 16-port windows */ + link->io.Attributes1 = IO_DATA_PATH_WIDTH_8; + link->io.Attributes2 = IO_DATA_PATH_WIDTH_16; + } + if (link->io.BasePort1 == 0) { + link->io.IOAddrLines = 16; + for (j = 0; j < 0x400; j += 0x20) { + link->io.BasePort1 = j ^ 0x300; + link->io.BasePort2 = (j ^ 0x300) + 0x10; + ret = CardServices(RequestIO, link->handle, &link->io); + if (ret == CS_SUCCESS) return ret; + } + return ret; + } else { + return CardServices(RequestIO, link->handle, &link->io); + } +} + +static void pcnet_config(dev_link_t *link) +{ + client_handle_t handle = link->handle; + pcnet_dev_t *info = link->priv; + struct net_device *dev = &info->dev; + tuple_t tuple; + cisparse_t parse; + int i, last_ret, last_fn, start_pg, stop_pg, cm_offset; + int manfid = 0, prodid = 0, has_shmem = 0; + u_short buf[64]; + config_info_t conf; + hw_info_t *hw_info; + + DEBUG(0, "pcnet_config(0x%p)\n", link); + + tuple.Attributes = 0; + tuple.TupleData = (cisdata_t *)buf; + tuple.TupleDataMax = sizeof(buf); + tuple.TupleOffset = 0; + tuple.DesiredTuple = CISTPL_CONFIG; + CS_CHECK(GetFirstTuple, handle, &tuple); + CS_CHECK(GetTupleData, handle, &tuple); + CS_CHECK(ParseTuple, handle, &tuple, &parse); + link->conf.ConfigBase = parse.config.base; + link->conf.Present = parse.config.rmask[0]; + + /* Configure card */ + link->state |= DEV_CONFIG; + + /* Look up current Vcc */ + CS_CHECK(GetConfigurationInfo, handle, &conf); + link->conf.Vcc = conf.Vcc; + + tuple.DesiredTuple = CISTPL_MANFID; + tuple.Attributes = TUPLE_RETURN_COMMON; + if ((CardServices(GetFirstTuple, handle, &tuple) == CS_SUCCESS) && + (CardServices(GetTupleData, handle, &tuple) == CS_SUCCESS)) { + manfid = le16_to_cpu(buf[0]); + prodid = le16_to_cpu(buf[1]); + } + + tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY; + tuple.Attributes = 0; + CS_CHECK(GetFirstTuple, handle, &tuple); + while (last_ret == CS_SUCCESS) { + cistpl_cftable_entry_t *cfg = &(parse.cftable_entry); + cistpl_io_t *io = &(parse.cftable_entry.io); + + CFG_CHECK(GetTupleData, handle, &tuple); + CFG_CHECK(ParseTuple, handle, &tuple, &parse); + if ((cfg->index == 0) || (cfg->io.nwin == 0)) + goto next_entry; + + link->conf.ConfigIndex = cfg->index; + /* For multifunction cards, by convention, we configure the + network function with window 0, and serial with window 1 */ + if (io->nwin > 1) { + i = (io->win[1].len > io->win[0].len); + link->io.BasePort2 = io->win[1-i].base; + link->io.NumPorts2 = io->win[1-i].len; + } else { + i = link->io.NumPorts2 = 0; + } + has_shmem = ((cfg->mem.nwin == 1) && + (cfg->mem.win[0].len >= 0x4000)); + link->io.BasePort1 = io->win[i].base; + link->io.NumPorts1 = io->win[i].len; + link->io.IOAddrLines = io->flags & CISTPL_IO_LINES_MASK; + if (link->io.NumPorts1 + link->io.NumPorts2 >= 32) { + last_ret = try_io_port(link); + if (last_ret == CS_SUCCESS) break; + } + next_entry: + last_ret = CardServices(GetNextTuple, handle, &tuple); + } + if (last_ret != CS_SUCCESS) { + cs_error(handle, RequestIO, last_ret); + goto failed; + } + + CS_CHECK(RequestIRQ, handle, &link->irq); + + if (link->io.NumPorts2 == 8) { + link->conf.Attributes |= CONF_ENABLE_SPKR; + link->conf.Status = CCSR_AUDIO_ENA; + } + if ((manfid == MANFID_IBM) && + (prodid == PRODID_IBM_HOME_AND_AWAY)) + link->conf.ConfigIndex |= 0x10; + + CS_CHECK(RequestConfiguration, handle, &link->conf); + dev->irq = link->irq.AssignedIRQ; + dev->base_addr = link->io.BasePort1; + if (info->flags & HAS_MISC_REG) { + if ((if_port == 1) || (if_port == 2)) + dev->if_port = if_port; + else + printk(KERN_NOTICE "pcnet_cs: invalid if_port requested\n"); + } else { + dev->if_port = 0; + } + if (register_netdev(dev) != 0) { + printk(KERN_NOTICE "pcnet_cs: register_netdev() failed\n"); + goto failed; + } + + hw_info = get_hwinfo(link); + if (hw_info == NULL) + hw_info = get_prom(link); + if (hw_info == NULL) + hw_info = get_dl10019(link); + if (hw_info == NULL) + hw_info = get_ax88190(link); + if (hw_info == NULL) + hw_info = get_hwired(link); + + if (hw_info == NULL) { + printk(KERN_NOTICE "pcnet_cs: unable to read hardware net" + " address for io base %#3lx\n", dev->base_addr); + unregister_netdev(dev); + goto failed; + } + + info->flags = hw_info->flags; + /* Check for user overrides */ + info->flags |= (delay_output) ? DELAY_OUTPUT : 0; + if ((manfid == MANFID_SOCKET) && + ((prodid == PRODID_SOCKET_LPE) || + (prodid == PRODID_SOCKET_LPE_CF) || + (prodid == PRODID_SOCKET_EIO))) + info->flags &= ~USE_BIG_BUF; + if (!use_big_buf) + info->flags &= ~USE_BIG_BUF; + + if (info->flags & USE_BIG_BUF) { + start_pg = SOCKET_START_PG; + stop_pg = SOCKET_STOP_PG; + cm_offset = 0x10000; + } else { + start_pg = PCNET_START_PG; + stop_pg = PCNET_STOP_PG; + cm_offset = 0; + } + + /* has_shmem is ignored if use_shmem != -1 */ + if ((use_shmem == 0) || (!has_shmem && (use_shmem == -1)) || + (setup_shmem_window(link, start_pg, stop_pg, cm_offset) != 0)) + setup_dma_config(link, start_pg, stop_pg); + + ei_status.name = "NE2000"; + ei_status.word16 = 1; + ei_status.reset_8390 = &pcnet_reset_8390; + + copy_dev_name(info->node, dev); + link->dev = &info->node; + + if (info->flags & (IS_DL10019|IS_DL10022)) { + u_char id = inb(dev->base_addr + 0x1a); + dev->do_ioctl = &ei_ioctl; + mii_phy_probe(dev); + if ((id == 0x30) && !info->pna_phy && (info->eth_phy == 4)) + info->eth_phy = 0; + printk(KERN_INFO "%s: NE2000 (DL100%d rev %02x): ", + dev->name, ((info->flags & IS_DL10022) ? 22 : 19), id); + if (info->pna_phy) + printk("PNA, "); + } else + printk(KERN_INFO "%s: NE2000 Compatible: ", dev->name); + printk("io %#3lx, irq %d,", dev->base_addr, dev->irq); + if (info->flags & USE_SHMEM) + printk (" mem %#5lx,", dev->mem_start); + if (info->flags & HAS_MISC_REG) + printk(" %s xcvr,", if_names[dev->if_port]); + printk(" hw_addr "); + for (i = 0; i < 6; i++) + printk("%02X%s", dev->dev_addr[i], ((i<5) ? ":" : "\n")); + link->state &= ~DEV_CONFIG_PENDING; + return; + +cs_failed: + cs_error(link->handle, last_fn, last_ret); +failed: + pcnet_release((u_long)link); + link->state &= ~DEV_CONFIG_PENDING; + return; +} /* pcnet_config */ + +/*====================================================================== + + After a card is removed, pcnet_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 pcnet_release(u_long arg) +{ + dev_link_t *link = (dev_link_t *)arg; + pcnet_dev_t *info = link->priv; + + DEBUG(0, "pcnet_release(0x%p)\n", link); + + if (link->open) { + DEBUG(1, "pcnet_cs: release postponed, '%s' still open\n", + info->node.dev_name); + link->state |= DEV_STALE_CONFIG; + return; + } + + if (info->flags & USE_SHMEM) { + iounmap(info->base); + CardServices(ReleaseWindow, link->win); + } + CardServices(ReleaseConfiguration, link->handle); + CardServices(ReleaseIO, link->handle, &link->io); + CardServices(ReleaseIRQ, link->handle, &link->irq); + + link->state &= ~DEV_CONFIG; + +} /* pcnet_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. + +======================================================================*/ + +static int pcnet_event(event_t event, int priority, + event_callback_args_t *args) +{ + dev_link_t *link = args->client_data; + pcnet_dev_t *info = link->priv; + + DEBUG(2, "pcnet_event(0x%06x)\n", event); + + switch (event) { + case CS_EVENT_CARD_REMOVAL: + link->state &= ~DEV_PRESENT; + if (link->state & DEV_CONFIG) { + netif_device_detach(&info->dev); + mod_timer(&link->release, jiffies + HZ/20); + } + break; + case CS_EVENT_CARD_INSERTION: + link->state |= DEV_PRESENT | DEV_CONFIG_PENDING; + pcnet_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) + netif_device_detach(&info->dev); + 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) { + pcnet_reset_8390(&info->dev); + NS8390_init(&info->dev, 1); + netif_device_attach(&info->dev); + } + } + break; + } + return 0; +} /* pcnet_event */ + +/*====================================================================== + + MII interface support for DL10019 and DL10022 based cards + + On the DL10019, the MII IO direction bit is 0x10; on the DL10022 + it is 0x20. Setting both bits seems to work on both card types. + +======================================================================*/ + +#define DLINK_GPIO 0x1c +#define DLINK_DIAG 0x1d +#define DLINK_EEPROM 0x1e + +#define MDIO_SHIFT_CLK 0x80 +#define MDIO_DATA_OUT 0x40 +#define MDIO_DIR_WRITE 0x30 +#define MDIO_DATA_WRITE0 (MDIO_DIR_WRITE) +#define MDIO_DATA_WRITE1 (MDIO_DIR_WRITE | MDIO_DATA_OUT) +#define MDIO_DATA_READ 0x10 +#define MDIO_MASK 0x0f + +static void mdio_sync(ioaddr_t addr) +{ + int bits, mask = inb(addr) & MDIO_MASK; + for (bits = 0; bits < 32; bits++) { + outb(mask | MDIO_DATA_WRITE1, addr); + outb(mask | MDIO_DATA_WRITE1 | MDIO_SHIFT_CLK, addr); + } +} + +static int mdio_read(ioaddr_t addr, int phy_id, int loc) +{ + u_int cmd = (0x06<<10)|(phy_id<<5)|loc; + int i, retval = 0, mask = inb(addr) & MDIO_MASK; + + mdio_sync(addr); + for (i = 13; i >= 0; i--) { + int dat = (cmd&(1<<i)) ? MDIO_DATA_WRITE1 : MDIO_DATA_WRITE0; + outb(mask | dat, addr); + outb(mask | dat | MDIO_SHIFT_CLK, addr); + } + for (i = 19; i > 0; i--) { + outb(mask, addr); + retval = (retval << 1) | ((inb(addr) & MDIO_DATA_READ) != 0); + outb(mask | MDIO_SHIFT_CLK, addr); + } + return (retval>>1) & 0xffff; +} + +static void mdio_write(ioaddr_t addr, int phy_id, int loc, int value) +{ + u_int cmd = (0x05<<28)|(phy_id<<23)|(loc<<18)|(1<<17)|value; + int i, mask = inb(addr) & MDIO_MASK; + + mdio_sync(addr); + for (i = 31; i >= 0; i--) { + int dat = (cmd&(1<<i)) ? MDIO_DATA_WRITE1 : MDIO_DATA_WRITE0; + outb(mask | dat, addr); + outb(mask | dat | MDIO_SHIFT_CLK, addr); + } + for (i = 1; i >= 0; i--) { + outb(mask, addr); + outb(mask | MDIO_SHIFT_CLK, addr); + } +} + +static void mdio_reset(ioaddr_t addr, int phy_id) +{ + outb_p(0x08, addr); + outb_p(0x0c, addr); + outb_p(0x08, addr); + outb_p(0x0c, addr); + outb_p(0x00, addr); +} + +/*====================================================================== + + EEPROM access routines for DL10019 and DL10022 based cards + +======================================================================*/ + +#define EE_EEP 0x40 +#define EE_ASIC 0x10 +#define EE_CS 0x08 +#define EE_CK 0x04 +#define EE_DO 0x02 +#define EE_DI 0x01 +#define EE_ADOT 0x01 /* DataOut for ASIC */ +#define EE_READ_CMD 0x06 + +#define DL19FDUPLX 0x0400 /* DL10019 Full duplex mode */ + +static int read_eeprom(ioaddr_t ioaddr, int location) +{ + int i, retval = 0; + ioaddr_t ee_addr = ioaddr + DLINK_EEPROM; + int read_cmd = location | (EE_READ_CMD << 8); + + outb(0, ee_addr); + outb(EE_EEP|EE_CS, ee_addr); + + /* Shift the read command bits out. */ + for (i = 10; i >= 0; i--) { + short dataval = (read_cmd & (1 << i)) ? EE_DO : 0; + outb_p(EE_EEP|EE_CS|dataval, ee_addr); + outb_p(EE_EEP|EE_CS|dataval|EE_CK, ee_addr); + } + outb(EE_EEP|EE_CS, ee_addr); + + for (i = 16; i > 0; i--) { + outb_p(EE_EEP|EE_CS | EE_CK, ee_addr); + retval = (retval << 1) | ((inb(ee_addr) & EE_DI) ? 1 : 0); + outb_p(EE_EEP|EE_CS, ee_addr); + } + + /* Terminate the EEPROM access. */ + outb(0, ee_addr); + return retval; +} + +/* + The internal ASIC registers can be changed by EEPROM READ access + with EE_ASIC bit set. + In ASIC mode, EE_ADOT is used to output the data to the ASIC. +*/ + +static void write_asic(ioaddr_t ioaddr, int location, short asic_data) +{ + int i; + ioaddr_t ee_addr = ioaddr + DLINK_EEPROM; + short dataval; + int read_cmd = location | (EE_READ_CMD << 8); + + asic_data |= read_eeprom(ioaddr, location); + + outb(0, ee_addr); + outb(EE_ASIC|EE_CS|EE_DI, ee_addr); + + read_cmd = read_cmd >> 1; + + /* Shift the read command bits out. */ + for (i = 9; i >= 0; i--) { + dataval = (read_cmd & (1 << i)) ? EE_DO : 0; + outb_p(EE_ASIC|EE_CS|EE_DI|dataval, ee_addr); + outb_p(EE_ASIC|EE_CS|EE_DI|dataval|EE_CK, ee_addr); + outb_p(EE_ASIC|EE_CS|EE_DI|dataval, ee_addr); + } + // sync + outb(EE_ASIC|EE_CS, ee_addr); + outb(EE_ASIC|EE_CS|EE_CK, ee_addr); + outb(EE_ASIC|EE_CS, ee_addr); + + for (i = 15; i >= 0; i--) { + dataval = (asic_data & (1 << i)) ? EE_ADOT : 0; + outb_p(EE_ASIC|EE_CS|dataval, ee_addr); + outb_p(EE_ASIC|EE_CS|dataval|EE_CK, ee_addr); + outb_p(EE_ASIC|EE_CS|dataval, ee_addr); + } + + /* Terminate the ASIC access. */ + outb(EE_ASIC|EE_DI, ee_addr); + outb(EE_ASIC|EE_DI| EE_CK, ee_addr); + outb(EE_ASIC|EE_DI, ee_addr); + + outb(0, ee_addr); +} + +/*====================================================================*/ + +static void set_misc_reg(struct net_device *dev) +{ + ioaddr_t nic_base = dev->base_addr; + pcnet_dev_t *info = (pcnet_dev_t *)dev; + u_char tmp; + + if (info->flags & HAS_MISC_REG) { + tmp = inb_p(nic_base + PCNET_MISC) & ~3; + if (dev->if_port == 2) + tmp |= 1; + if (info->flags & USE_BIG_BUF) + tmp |= 2; + if (info->flags & HAS_IBM_MISC) + tmp |= 8; + outb_p(tmp, nic_base + PCNET_MISC); + } + if (info->flags & IS_DL10022) { + if (info->flags & HAS_MII) { + mdio_reset(nic_base + DLINK_GPIO, info->eth_phy); + /* Restart MII autonegotiation */ + mdio_write(nic_base + DLINK_GPIO, info->eth_phy, 0, 0x0000); + mdio_write(nic_base + DLINK_GPIO, info->eth_phy, 0, 0x1200); + info->mii_reset = jiffies; + } else { + outb(full_duplex ? 4 : 0, nic_base + DLINK_DIAG); + } + } +} + +/*====================================================================*/ + +static void mii_phy_probe(struct net_device *dev) +{ + pcnet_dev_t *info = (pcnet_dev_t *)dev; + ioaddr_t mii_addr = dev->base_addr + DLINK_GPIO; + int i; + u_int tmp, phyid; + + for (i = 31; i >= 0; i--) { + tmp = mdio_read(mii_addr, i, 1); + if ((tmp == 0) || (tmp == 0xffff)) + continue; + tmp = mdio_read(mii_addr, i, MII_PHYID_REG1); + phyid = tmp << 16; + phyid |= mdio_read(mii_addr, i, MII_PHYID_REG2); + phyid &= MII_PHYID_REV_MASK; + DEBUG(0, "%s: MII at %d is 0x%08x\n", dev->name, i, phyid); + if (phyid == AM79C9XX_HOME_PHY) { + info->pna_phy = i; + } else if (phyid != AM79C9XX_ETH_PHY) { + info->eth_phy = i; + } + } +} + +static int pcnet_open(struct net_device *dev) +{ + pcnet_dev_t *info = (pcnet_dev_t *)dev; + dev_link_t *link = &info->link; + + DEBUG(2, "pcnet_open('%s')\n", dev->name); + + if (!DEV_OK(link)) + return -ENODEV; + + link->open++; + MOD_INC_USE_COUNT; + + set_misc_reg(dev); + request_irq(dev->irq, ei_irq_wrapper, SA_SHIRQ, dev_info, dev); + + info->phy_id = info->eth_phy; + info->link_status = 0x00; + info->watchdog.function = &ei_watchdog; + info->watchdog.data = (u_long)info; + info->watchdog.expires = jiffies + HZ; + add_timer(&info->watchdog); + + return ei_open(dev); +} /* pcnet_open */ + +/*====================================================================*/ + +static int pcnet_close(struct net_device *dev) +{ + pcnet_dev_t *info = (pcnet_dev_t *)dev; + dev_link_t *link = &info->link; + + DEBUG(2, "pcnet_close('%s')\n", dev->name); + + ei_close(dev); + free_irq(dev->irq, dev); + + link->open--; + netif_stop_queue(dev); + netif_mark_down(dev); + del_timer(&info->watchdog); + if (link->state & DEV_STALE_CONFIG) + mod_timer(&link->release, jiffies + HZ/20); + + MOD_DEC_USE_COUNT; + + return 0; +} /* pcnet_close */ + +/*====================================================================== + + Hard reset the card. This used to pause for the same period that + a 8390 reset command required, but that shouldn't be necessary. + +======================================================================*/ + +static void pcnet_reset_8390(struct net_device *dev) +{ + ioaddr_t nic_base = dev->base_addr; + int i; + + ei_status.txing = ei_status.dmaing = 0; + + outb_p(E8390_NODMA+E8390_PAGE0+E8390_STOP, nic_base + E8390_CMD); + + outb(inb(nic_base + PCNET_RESET), nic_base + PCNET_RESET); + + for (i = 0; i < 100; i++) { + if ((inb_p(nic_base+EN0_ISR) & ENISR_RESET) != 0) + break; + udelay(100); + } + outb_p(ENISR_RESET, nic_base + EN0_ISR); /* Ack intr. */ + + if (i == 100) + printk(KERN_ERR "%s: pcnet_reset_8390() did not complete.\n", + dev->name); + set_misc_reg(dev); + +} /* pcnet_reset_8390 */ + +/*====================================================================*/ + +static int set_config(struct net_device *dev, struct ifmap *map) +{ + pcnet_dev_t *info = (pcnet_dev_t *)dev; + if ((map->port != (u_char)(-1)) && (map->port != dev->if_port)) { + if (!(info->flags & HAS_MISC_REG)) + return -EOPNOTSUPP; + else if ((map->port < 1) || (map->port > 2)) + return -EINVAL; + dev->if_port = map->port; + printk(KERN_INFO "%s: switched to %s port\n", + dev->name, if_names[dev->if_port]); + NS8390_init(dev, 1); + } + return 0; +} + +/*====================================================================*/ + +static void ei_irq_wrapper(int irq, void *dev_id, struct pt_regs *regs) +{ + pcnet_dev_t *info = dev_id; + info->stale = 0; + ei_interrupt(irq, dev_id, regs); +} + +static void ei_watchdog(u_long arg) +{ + pcnet_dev_t *info = (pcnet_dev_t *)(arg); + struct net_device *dev = &info->dev; + ioaddr_t nic_base = dev->base_addr; + ioaddr_t mii_addr = nic_base + DLINK_GPIO; + u_short link; + + if (!netif_device_present(dev)) goto reschedule; + + /* Check for pending interrupt with expired latency timer: with + this, we can limp along even if the interrupt is blocked */ + outb_p(E8390_NODMA+E8390_PAGE0, nic_base + E8390_CMD); + if (info->stale++ && (inb_p(nic_base + EN0_ISR) & ENISR_ALL)) { + if (!info->fast_poll) + printk(KERN_INFO "%s: interrupt(s) dropped!\n", dev->name); + ei_irq_wrapper(dev->irq, dev, NULL); + info->fast_poll = HZ; + } + if (info->fast_poll) { + info->fast_poll--; + info->watchdog.expires = jiffies + 1; + add_timer(&info->watchdog); + return; + } + + if (!(info->flags & HAS_MII)) + goto reschedule; + + mdio_read(mii_addr, info->phy_id, 1); + link = mdio_read(mii_addr, info->phy_id, 1); + if (!link || (link == 0xffff)) { + if (info->eth_phy) { + info->phy_id = info->eth_phy = 0; + } else { + printk(KERN_INFO "%s: MII is missing!\n", dev->name); + info->flags &= ~HAS_MII; + } + goto reschedule; + } + + link &= 0x0004; + if (link != info->link_status) { + u_short p = mdio_read(mii_addr, info->phy_id, 5); + printk(KERN_INFO "%s: %s link beat\n", dev->name, + (link) ? "found" : "lost"); + if (link && (info->flags & IS_DL10022)) { + /* Disable collision detection on full duplex links */ + outb((p & 0x0140) ? 4 : 0, nic_base + DLINK_DIAG); + } else if (link && (info->flags & IS_DL10019)) { + /* Disable collision detection on full duplex links */ + write_asic(dev->base_addr, 4, (p & 0x140) ? DL19FDUPLX : 0); + } + if (link) { + if (info->phy_id == info->eth_phy) { + if (p) + printk(KERN_INFO "%s: autonegotiation complete: " + "%sbaseT-%cD selected\n", dev->name, + ((p & 0x0180) ? "100" : "10"), + ((p & 0x0140) ? 'F' : 'H')); + else + printk(KERN_INFO "%s: link partner did not " + "autonegotiate\n", dev->name); + } + NS8390_init(dev, 1); + } + info->link_status = link; + } + if (info->pna_phy && (jiffies - info->mii_reset > 6*HZ)) { + link = mdio_read(mii_addr, info->eth_phy, 1) & 0x0004; + if (((info->phy_id == info->pna_phy) && link) || + ((info->phy_id != info->pna_phy) && !link)) { + /* isolate this MII and try flipping to the other one */ + mdio_write(mii_addr, info->phy_id, 0, 0x0400); + info->phy_id ^= info->pna_phy ^ info->eth_phy; + printk(KERN_INFO "%s: switched to %s transceiver\n", dev->name, + (info->phy_id == info->eth_phy) ? "ethernet" : "PNA"); + mdio_write(mii_addr, info->phy_id, 0, + (info->phy_id == info->eth_phy) ? 0x1000 : 0); + info->link_status = 0; + info->mii_reset = jiffies; + } + } + +reschedule: + info->watchdog.expires = jiffies + HZ; + add_timer(&info->watchdog); +} + +/*====================================================================*/ + +static int ei_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) +{ + pcnet_dev_t *info = (pcnet_dev_t *)dev; + u16 *data = (u16 *)&rq->ifr_data; + ioaddr_t mii_addr = dev->base_addr + DLINK_GPIO; + switch (cmd) { + case SIOCDEVPRIVATE: + data[0] = info->phy_id; + case SIOCDEVPRIVATE+1: + data[3] = mdio_read(mii_addr, data[0], data[1] & 0x1f); + return 0; + case SIOCDEVPRIVATE+2: + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + mdio_write(mii_addr, data[0], data[1] & 0x1f, data[2]); + return 0; + } + return -EOPNOTSUPP; +} + +/*====================================================================*/ + +static void dma_get_8390_hdr(struct net_device *dev, + struct e8390_pkt_hdr *hdr, + int ring_page) +{ + ioaddr_t nic_base = dev->base_addr; + + if (ei_status.dmaing) { + printk(KERN_NOTICE "%s: DMAing conflict in dma_block_input." + "[DMAstat:%1x][irqlock:%1x]\n", + dev->name, ei_status.dmaing, ei_status.irqlock); + return; + } + + ei_status.dmaing |= 0x01; + outb_p(E8390_NODMA+E8390_PAGE0+E8390_START, nic_base + PCNET_CMD); + outb_p(sizeof(struct e8390_pkt_hdr), nic_base + EN0_RCNTLO); + outb_p(0, nic_base + EN0_RCNTHI); + outb_p(0, nic_base + EN0_RSARLO); /* On page boundary */ + outb_p(ring_page, nic_base + EN0_RSARHI); + outb_p(E8390_RREAD+E8390_START, nic_base + PCNET_CMD); + + insw(nic_base + PCNET_DATAPORT, hdr, + sizeof(struct e8390_pkt_hdr)>>1); + /* Fix for big endian systems */ + hdr->count = le16_to_cpu(hdr->count); + + outb_p(ENISR_RDC, nic_base + EN0_ISR); /* Ack intr. */ + ei_status.dmaing &= ~0x01; +} + +/*====================================================================*/ + +static void dma_block_input(struct net_device *dev, int count, + struct sk_buff *skb, int ring_offset) +{ + ioaddr_t nic_base = dev->base_addr; + int xfer_count = count; + char *buf = skb->data; + +#ifdef PCMCIA_DEBUG + if ((ei_debug > 4) && (count != 4)) + printk(KERN_DEBUG "%s: [bi=%d]\n", dev->name, count+4); +#endif + if (ei_status.dmaing) { + printk(KERN_NOTICE "%s: DMAing conflict in dma_block_input." + "[DMAstat:%1x][irqlock:%1x]\n", + dev->name, ei_status.dmaing, ei_status.irqlock); + return; + } + ei_status.dmaing |= 0x01; + outb_p(E8390_NODMA+E8390_PAGE0+E8390_START, nic_base + PCNET_CMD); + outb_p(count & 0xff, nic_base + EN0_RCNTLO); + outb_p(count >> 8, nic_base + EN0_RCNTHI); + outb_p(ring_offset & 0xff, nic_base + EN0_RSARLO); + outb_p(ring_offset >> 8, nic_base + EN0_RSARHI); + outb_p(E8390_RREAD+E8390_START, nic_base + PCNET_CMD); + + insw(nic_base + PCNET_DATAPORT,buf,count>>1); + if (count & 0x01) + buf[count-1] = inb(nic_base + PCNET_DATAPORT), xfer_count++; + + /* This was for the ALPHA version only, but enough people have + encountering problems that it is still here. */ +#ifdef PCMCIA_DEBUG + if (ei_debug > 4) { /* DMA termination address check... */ + int addr, tries = 20; + do { + /* DON'T check for 'inb_p(EN0_ISR) & ENISR_RDC' here + -- it's broken for Rx on some cards! */ + int high = inb_p(nic_base + EN0_RSARHI); + int low = inb_p(nic_base + EN0_RSARLO); + addr = (high << 8) + low; + if (((ring_offset + xfer_count) & 0xff) == (addr & 0xff)) + break; + } while (--tries > 0); + if (tries <= 0) + printk(KERN_NOTICE "%s: RX transfer address mismatch," + "%#4.4x (expected) vs. %#4.4x (actual).\n", + dev->name, ring_offset + xfer_count, addr); + } +#endif + outb_p(ENISR_RDC, nic_base + EN0_ISR); /* Ack intr. */ + ei_status.dmaing &= ~0x01; +} /* dma_block_input */ + +/*====================================================================*/ + +static void dma_block_output(struct net_device *dev, int count, + const u_char *buf, const int start_page) +{ + ioaddr_t nic_base = dev->base_addr; + pcnet_dev_t *info = (pcnet_dev_t *)dev; +#ifdef PCMCIA_DEBUG + int retries = 0; +#endif + u_long dma_start; + +#ifdef PCMCIA_DEBUG + if (ei_debug > 4) + printk(KERN_DEBUG "%s: [bo=%d]\n", dev->name, count); +#endif + + /* Round the count up for word writes. Do we need to do this? + What effect will an odd byte count have on the 8390? + I should check someday. */ + if (count & 0x01) + count++; + if (ei_status.dmaing) { + printk(KERN_NOTICE "%s: DMAing conflict in dma_block_output." + "[DMAstat:%1x][irqlock:%1x]\n", + dev->name, ei_status.dmaing, ei_status.irqlock); + return; + } + ei_status.dmaing |= 0x01; + /* We should already be in page 0, but to be safe... */ + outb_p(E8390_PAGE0+E8390_START+E8390_NODMA, nic_base+PCNET_CMD); + +#ifdef PCMCIA_DEBUG + retry: +#endif + + outb_p(ENISR_RDC, nic_base + EN0_ISR); + + /* Now the normal output. */ + outb_p(count & 0xff, nic_base + EN0_RCNTLO); + outb_p(count >> 8, nic_base + EN0_RCNTHI); + outb_p(0x00, nic_base + EN0_RSARLO); + outb_p(start_page, nic_base + EN0_RSARHI); + + outb_p(E8390_RWRITE+E8390_START, nic_base + PCNET_CMD); + outsw(nic_base + PCNET_DATAPORT, buf, count>>1); + + dma_start = jiffies; + +#ifdef PCMCIA_DEBUG + /* This was for the ALPHA version only, but enough people have + encountering problems that it is still here. */ + if (ei_debug > 4) { /* DMA termination address check... */ + int addr, tries = 20; + do { + int high = inb_p(nic_base + EN0_RSARHI); + int low = inb_p(nic_base + EN0_RSARLO); + addr = (high << 8) + low; + if ((start_page << 8) + count == addr) + break; + } while (--tries > 0); + if (tries <= 0) { + printk(KERN_NOTICE "%s: Tx packet transfer address mismatch," + "%#4.4x (expected) vs. %#4.4x (actual).\n", + dev->name, (start_page << 8) + count, addr); + if (retries++ == 0) + goto retry; + } + } +#endif + + while ((inb_p(nic_base + EN0_ISR) & ENISR_RDC) == 0) + if (jiffies - dma_start > PCNET_RDC_TIMEOUT) { + printk(KERN_NOTICE "%s: timeout waiting for Tx RDC.\n", + dev->name); + pcnet_reset_8390(dev); + NS8390_init(dev, 1); + break; + } + + outb_p(ENISR_RDC, nic_base + EN0_ISR); /* Ack intr. */ + if (info->flags & DELAY_OUTPUT) + udelay((long)delay_time); + ei_status.dmaing &= ~0x01; +} + +/*====================================================================*/ + +static int setup_dma_config(dev_link_t *link, int start_pg, + int stop_pg) +{ + struct net_device *dev = link->priv; + + ei_status.tx_start_page = start_pg; + ei_status.rx_start_page = start_pg + TX_PAGES; + ei_status.stop_page = stop_pg; + + /* set up block i/o functions */ + ei_status.get_8390_hdr = &dma_get_8390_hdr; + ei_status.block_input = &dma_block_input; + ei_status.block_output = &dma_block_output; + + return 0; +} + +/*====================================================================*/ + +static void copyin(u_char *dest, u_char *src, int c) +{ + u_short *d = (u_short *)dest, *s = (u_short *)src; + int odd; + + if (c <= 0) + return; + odd = (c & 1); c >>= 1; + + if (c) { + do { *d++ = readw_ns(s++); } while (--c); + } + /* get last byte by fetching a word and masking */ + if (odd) + *((u_char *)d) = readw(s) & 0xff; +} + +static void copyout(u_char *dest, const u_char *src, int c) +{ + u_short *d = (u_short *)dest, *s = (u_short *)src; + int odd; + + if (c <= 0) + return; + odd = (c & 1); c >>= 1; + + if (c) { + do { writew_ns(*s++, d++); } while (--c); + } + /* copy last byte doing a read-modify-write */ + if (odd) + writew((readw(d) & 0xff00) | *(u_char *)s, d); +} + +/*====================================================================*/ + +static void shmem_get_8390_hdr(struct net_device *dev, + struct e8390_pkt_hdr *hdr, + int ring_page) +{ + void *xfer_start = (void *)(dev->rmem_start + (ring_page << 8) + - (ei_status.rx_start_page << 8)); + + copyin((void *)hdr, xfer_start, sizeof(struct e8390_pkt_hdr)); + /* Fix for big endian systems */ + hdr->count = le16_to_cpu(hdr->count); +} + +/*====================================================================*/ + +static void shmem_block_input(struct net_device *dev, int count, + struct sk_buff *skb, int ring_offset) +{ + void *xfer_start = (void *)(dev->rmem_start + ring_offset + - (ei_status.rx_start_page << 8)); + char *buf = skb->data; + + if (xfer_start + count > (void *)dev->rmem_end) { + /* We must wrap the input move. */ + int semi_count = (void*)dev->rmem_end - xfer_start; + copyin(buf, xfer_start, semi_count); + buf += semi_count; + ring_offset = ei_status.rx_start_page << 8; + xfer_start = (void *)dev->rmem_start; + count -= semi_count; + } + copyin(buf, xfer_start, count); +} + +/*====================================================================*/ + +static void shmem_block_output(struct net_device *dev, int count, + const u_char *buf, const int start_page) +{ + void *shmem = (void *)dev->mem_start + (start_page << 8); + shmem -= ei_status.tx_start_page << 8; + copyout(shmem, buf, count); +} + +/*====================================================================*/ + +static int setup_shmem_window(dev_link_t *link, int start_pg, + int stop_pg, int cm_offset) +{ + struct net_device *dev = link->priv; + pcnet_dev_t *info = link->priv; + win_req_t req; + memreq_t mem; + int i, window_size, offset, last_ret, last_fn; + + window_size = (stop_pg - start_pg) << 8; + if (window_size > 32 * 1024) + window_size = 32 * 1024; + + /* Make sure it's a power of two. */ + while ((window_size & (window_size - 1)) != 0) + window_size += window_size & ~(window_size - 1); + + /* Allocate a memory window */ + req.Attributes = WIN_DATA_WIDTH_16|WIN_MEMORY_TYPE_CM|WIN_ENABLE; + req.Attributes |= WIN_USE_WAIT; + req.Base = 0; req.Size = window_size; + req.AccessSpeed = mem_speed; + link->win = (window_handle_t)link->handle; + CS_CHECK(RequestWindow, &link->win, &req); + + mem.CardOffset = (start_pg << 8) + cm_offset; + offset = mem.CardOffset % window_size; + mem.CardOffset -= offset; + mem.Page = 0; + CS_CHECK(MapMemPage, link->win, &mem); + + /* Try scribbling on the buffer */ + info->base = ioremap(req.Base, window_size); + for (i = 0; i < (TX_PAGES<<8); i += 2) + writew_ns((i>>1), info->base+offset+i); + udelay(100); + for (i = 0; i < (TX_PAGES<<8); i += 2) + if (readw_ns(info->base+offset+i) != (i>>1)) break; + pcnet_reset_8390(dev); + if (i != (TX_PAGES<<8)) { + iounmap(info->base); + CardServices(ReleaseWindow, link->win); + info->base = NULL; link->win = NULL; + goto failed; + } + + dev->mem_start = (u_long)info->base + offset; + dev->rmem_start = dev->mem_start + (TX_PAGES<<8); + dev->mem_end = dev->rmem_end = (u_long)info->base + req.Size; + + ei_status.tx_start_page = start_pg; + ei_status.rx_start_page = start_pg + TX_PAGES; + ei_status.stop_page = start_pg + ((req.Size - offset) >> 8); + + /* set up block i/o functions */ + ei_status.get_8390_hdr = &shmem_get_8390_hdr; + ei_status.block_input = &shmem_block_input; + ei_status.block_output = &shmem_block_output; + + info->flags |= USE_SHMEM; + return 0; + +cs_failed: + cs_error(link->handle, last_fn, last_ret); +failed: + return 1; +} + +/*====================================================================*/ + +static int __init init_pcnet_cs(void) +{ + servinfo_t serv; + DEBUG(0, "%s\n", version); + CardServices(GetCardServicesInfo, &serv); + if (serv.Revision != CS_RELEASE_CODE) { + printk(KERN_NOTICE "pcnet_cs: Card Services release " + "does not match!\n"); + return -EINVAL; + } + register_pccard_driver(&dev_info, &pcnet_attach, &pcnet_detach); + return 0; +} + +static void __exit exit_pcnet_cs(void) +{ + DEBUG(0, "pcnet_cs: unloading\n"); + unregister_pccard_driver(&dev_info); + while (dev_list != NULL) + pcnet_detach(dev_list); +} + +module_init(init_pcnet_cs); +module_exit(exit_pcnet_cs); diff --git a/linux/pcmcia-cs/clients/smc91c92_cs.c b/linux/pcmcia-cs/clients/smc91c92_cs.c new file mode 100644 index 0000000..782d504 --- /dev/null +++ b/linux/pcmcia-cs/clients/smc91c92_cs.c @@ -0,0 +1,2135 @@ +/*====================================================================== + + 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.123 2003/08/25 15:57:41 + + 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. + +======================================================================*/ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/crc32.h> +#include <asm/io.h> +#include <asm/system.h> +#include <asm/uaccess.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/if_arp.h> +#include <linux/ioport.h> + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/cisreg.h> +#include <pcmcia/ciscode.h> +#include <pcmcia/ds.h> + +/* Ositech Seven of Diamonds firmware */ +#include "ositech.h" + +/*====================================================================*/ + +static char *if_names[] = { "auto", "10baseT", "10base2"}; + +/* Module parameters */ + +MODULE_DESCRIPTION("SMC 91c92 series PCMCIA ethernet driver"); +MODULE_LICENSE("GPL"); + +#define INT_MODULE_PARM(n, v) static int n = v; MODULE_PARM(n, "i") + +/* + Transceiver/media type. + 0 = auto + 1 = 10baseT (and autoselect if #define AUTOSELECT), + 2 = AUI/10base2, +*/ +INT_MODULE_PARM(if_port, 0); + +/* Bit map of interrupts to choose from. */ +INT_MODULE_PARM(irq_mask, 0xdeb8); +static int irq_list[4] = { -1 }; +MODULE_PARM(irq_list, "1-4i"); + +#ifdef PCMCIA_DEBUG +INT_MODULE_PARM(pc_debug, PCMCIA_DEBUG); +static const char *version = +"smc91c92_cs.c 0.09 1996/8/4 Donald Becker, becker@scyld.com.\n"; +#define DEBUG(n, args...) if (pc_debug>(n)) printk(KERN_DEBUG args) +#else +#define DEBUG(n, args...) +#endif + +/*====================================================================*/ + +/* Operational parameter that usually are not changed. */ + +/* Time in jiffies before concluding Tx hung */ +#define TX_TIMEOUT ((400*HZ)/1000) + +/* Maximum events (Rx packets, etc.) to handle at each interrupt. */ +#define INTR_WORK 4 + +/* Times to check the check the chip before concluding that it doesn't + currently have room for another Tx packet. */ +#define MEMORY_WAIT_TIME 8 + +static dev_info_t dev_info = "smc91c92_cs"; + +static dev_link_t *dev_list; + +struct smc_private { + dev_link_t link; + struct net_device dev; + u_short manfid; + u_short cardid; + struct net_device_stats stats; + dev_node_t node; + struct sk_buff *saved_skb; + int packets_waiting; + caddr_t base; + u_short cfg; + struct timer_list media; + int watchdog, tx_err; + u_short media_status; + u_short fast_poll; + u_short link_status; + int phy_id; + int duplex; + int rx_ovrn; +}; + +/* Special definitions for Megahertz multifunction cards */ +#define MEGAHERTZ_ISR 0x0380 + +/* Special function registers for Motorola Mariner */ +#define MOT_LAN 0x0000 +#define MOT_UART 0x0020 +#define MOT_EEPROM 0x20 + +#define MOT_NORMAL \ +(COR_LEVEL_REQ | COR_FUNC_ENA | COR_ADDR_DECODE | COR_IREQ_ENA) + +/* Special function registers for Ositech cards */ +#define OSITECH_AUI_CTL 0x0c +#define OSITECH_PWRDOWN 0x0d +#define OSITECH_RESET 0x0e +#define OSITECH_ISR 0x0f +#define OSITECH_AUI_PWR 0x0c +#define OSITECH_RESET_ISR 0x0e + +#define OSI_AUI_PWR 0x40 +#define OSI_LAN_PWRDOWN 0x02 +#define OSI_MODEM_PWRDOWN 0x01 +#define OSI_LAN_RESET 0x02 +#define OSI_MODEM_RESET 0x01 + +/* Symbolic constants for the SMC91c9* series chips, from Erik Stahlman. */ +#define BANK_SELECT 14 /* Window select register. */ +#define SMC_SELECT_BANK(x) { outw(x, ioaddr + BANK_SELECT); } + +/* Bank 0 registers. */ +#define TCR 0 /* transmit control register */ +#define TCR_CLEAR 0 /* do NOTHING */ +#define TCR_ENABLE 0x0001 /* if this is 1, we can transmit */ +#define TCR_PAD_EN 0x0080 /* pads short packets to 64 bytes */ +#define TCR_MONCSN 0x0400 /* Monitor Carrier. */ +#define TCR_FDUPLX 0x0800 /* Full duplex mode. */ +#define TCR_NORMAL TCR_ENABLE | TCR_PAD_EN + +#define EPH 2 /* Ethernet Protocol Handler report. */ +#define EPH_TX_SUC 0x0001 +#define EPH_SNGLCOL 0x0002 +#define EPH_MULCOL 0x0004 +#define EPH_LTX_MULT 0x0008 +#define EPH_16COL 0x0010 +#define EPH_SQET 0x0020 +#define EPH_LTX_BRD 0x0040 +#define EPH_TX_DEFR 0x0080 +#define EPH_LAT_COL 0x0200 +#define EPH_LOST_CAR 0x0400 +#define EPH_EXC_DEF 0x0800 +#define EPH_CTR_ROL 0x1000 +#define EPH_RX_OVRN 0x2000 +#define EPH_LINK_OK 0x4000 +#define EPH_TX_UNRN 0x8000 +#define MEMINFO 8 /* Memory Information Register */ +#define MEMCFG 10 /* Memory Configuration Register */ + +/* Bank 1 registers. */ +#define CONFIG 0 +#define CFG_MII_SELECT 0x8000 /* 91C100 only */ +#define CFG_NO_WAIT 0x1000 +#define CFG_FULL_STEP 0x0400 +#define CFG_SET_SQLCH 0x0200 +#define CFG_AUI_SELECT 0x0100 +#define CFG_16BIT 0x0080 +#define CFG_DIS_LINK 0x0040 +#define CFG_STATIC 0x0030 +#define CFG_IRQ_SEL_1 0x0004 +#define CFG_IRQ_SEL_0 0x0002 +#define BASE_ADDR 2 +#define ADDR0 4 +#define GENERAL 10 +#define CONTROL 12 +#define CTL_STORE 0x0001 +#define CTL_RELOAD 0x0002 +#define CTL_EE_SELECT 0x0004 +#define CTL_TE_ENABLE 0x0020 +#define CTL_CR_ENABLE 0x0040 +#define CTL_LE_ENABLE 0x0080 +#define CTL_AUTO_RELEASE 0x0800 +#define CTL_POWERDOWN 0x2000 + +/* Bank 2 registers. */ +#define MMU_CMD 0 +#define MC_ALLOC 0x20 /* or with number of 256 byte packets */ +#define MC_RESET 0x40 +#define MC_RELEASE 0x80 /* remove and release the current rx packet */ +#define MC_FREEPKT 0xA0 /* Release packet in PNR register */ +#define MC_ENQUEUE 0xC0 /* Enqueue the packet for transmit */ +#define PNR_ARR 2 +#define FIFO_PORTS 4 +#define FP_RXEMPTY 0x8000 +#define POINTER 6 +#define PTR_AUTO_INC 0x0040 +#define PTR_READ 0x2000 +#define PTR_AUTOINC 0x4000 +#define PTR_RCV 0x8000 +#define DATA_1 8 +#define INTERRUPT 12 +#define IM_RCV_INT 0x1 +#define IM_TX_INT 0x2 +#define IM_TX_EMPTY_INT 0x4 +#define IM_ALLOC_INT 0x8 +#define IM_RX_OVRN_INT 0x10 +#define IM_EPH_INT 0x20 + +#define RCR 4 +enum RxCfg { RxAllMulti = 0x0004, RxPromisc = 0x0002, + RxEnable = 0x0100, RxStripCRC = 0x0200}; +#define RCR_SOFTRESET 0x8000 /* resets the chip */ +#define RCR_STRIP_CRC 0x200 /* strips CRC */ +#define RCR_ENABLE 0x100 /* IFF this is set, we can receive packets */ +#define RCR_ALMUL 0x4 /* receive all multicast packets */ +#define RCR_PROMISC 0x2 /* enable promiscuous mode */ + +/* 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 + +/* Transmit status bits. */ +#define TS_SUCCESS 0x0001 +#define TS_16COL 0x0010 +#define TS_LATCOL 0x0200 +#define TS_LOSTCAR 0x0400 + +/* Receive status bits. */ +#define RS_ALGNERR 0x8000 +#define RS_BADCRC 0x2000 +#define RS_ODDFRAME 0x1000 +#define RS_TOOLONG 0x0800 +#define RS_TOOSHORT 0x0400 +#define RS_MULTICAST 0x0001 +#define RS_ERRORS (RS_ALGNERR | RS_BADCRC | RS_TOOLONG | RS_TOOSHORT) + +#define set_bits(v, p) outw(inw(p)|(v), (p)) +#define mask_bits(v, p) outw(inw(p)&(v), (p)) + +/*====================================================================*/ + +static dev_link_t *smc91c92_attach(void); +static void smc91c92_detach(dev_link_t *); +static void smc91c92_config(dev_link_t *link); +static void smc91c92_release(u_long arg); +static int smc91c92_event(event_t event, int priority, + event_callback_args_t *args); + +static int smc_open(struct net_device *dev); +static int smc_close(struct net_device *dev); +static int smc_ioctl(struct net_device *dev, struct ifreq *rq, int cmd); +static void smc_tx_timeout(struct net_device *dev); +static int smc_start_xmit(struct sk_buff *skb, struct net_device *dev); +static void smc_interrupt(int irq, void *dev_id, struct pt_regs *regs); +static void smc_rx(struct net_device *dev); +static struct net_device_stats *smc_get_stats(struct net_device *dev); +static void set_rx_mode(struct net_device *dev); +static int s9k_config(struct net_device *dev, struct ifmap *map); +static void smc_set_xcvr(struct net_device *dev, int if_port); +static void smc_reset(struct net_device *dev); +static void media_check(u_long arg); +static void mdio_sync(ioaddr_t addr); +static int mdio_read(struct net_device *dev, int phy_id, int loc); +static void mdio_write(struct net_device *dev, int phy_id, int loc, int value); + +/*====================================================================== + + This bit of code is used to avoid unregistering network devices + at inappropriate times. 2.2 and later kernels are fairly picky + about when this can happen. + +======================================================================*/ + +static void flush_stale_links(void) +{ + dev_link_t *link, *next; + for (link = dev_list; link; link = next) { + next = link->next; + if (link->state & DEV_STALE_LINK) + smc91c92_detach(link); + } +} + +/*====================================================================*/ + +static void cs_error(client_handle_t handle, int func, int ret) +{ + error_info_t err = { func, ret }; + CardServices(ReportError, handle, &err); +} + +/*====================================================================== + + smc91c92_attach() creates an "instance" of the driver, allocating + local data structures for one device. The device is registered + with Card Services. + +======================================================================*/ + +static dev_link_t *smc91c92_attach(void) +{ + client_reg_t client_reg; + struct smc_private *smc; + dev_link_t *link; + struct net_device *dev; + int i, ret; + + DEBUG(0, "smc91c92_attach()\n"); + flush_stale_links(); + + /* Create new ethernet device */ + smc = kmalloc(sizeof(struct smc_private), GFP_KERNEL); + if (!smc) return NULL; + memset(smc, 0, sizeof(struct smc_private)); + link = &smc->link; dev = &smc->dev; + + init_timer(&link->release); + link->release.function = &smc91c92_release; + link->release.data = (u_long)link; + link->io.NumPorts1 = 16; + link->io.Attributes1 = IO_DATA_PATH_WIDTH_AUTO; + link->io.IOAddrLines = 4; + link->irq.Attributes = IRQ_TYPE_EXCLUSIVE | IRQ_HANDLE_PRESENT; + link->irq.IRQInfo1 = IRQ_INFO2_VALID|IRQ_LEVEL_ID; + if (irq_list[0] == -1) + link->irq.IRQInfo2 = irq_mask; + else + for (i = 0; i < 4; i++) + link->irq.IRQInfo2 |= 1 << irq_list[i]; + link->irq.Handler = &smc_interrupt; + link->conf.Attributes = CONF_ENABLE_IRQ; + link->conf.Vcc = 50; + link->conf.IntType = INT_MEMORY_AND_IO; + + /* The SMC91c92-specific entries in the device structure. */ + dev->hard_start_xmit = &smc_start_xmit; + dev->get_stats = &smc_get_stats; + dev->set_config = &s9k_config; + dev->set_multicast_list = &set_rx_mode; + ether_setup(dev); + init_dev_name(dev, smc->node); + dev->open = &smc_open; + dev->stop = &smc_close; + dev->do_ioctl = &smc_ioctl; +#ifdef HAVE_TX_TIMEOUT + dev->tx_timeout = smc_tx_timeout; + dev->watchdog_timeo = TX_TIMEOUT; +#endif + dev->priv = link->priv = link->irq.Instance = smc; + + /* 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 = &smc91c92_event; + client_reg.Version = 0x0210; + client_reg.event_callback_args.client_data = link; + ret = CardServices(RegisterClient, &link->handle, &client_reg); + if (ret != 0) { + cs_error(link->handle, RegisterClient, ret); + smc91c92_detach(link); + return NULL; + } + + return link; +} /* smc91c92_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 smc91c92_detach(dev_link_t *link) +{ + struct smc_private *smc = link->priv; + dev_link_t **linkp; + + DEBUG(0, "smc91c92_detach(0x%p)\n", link); + + /* Locate device structure */ + for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next) + if (*linkp == link) break; + if (*linkp == NULL) + return; + + del_timer(&link->release); + if (link->state & DEV_CONFIG) { + smc91c92_release((u_long)link); + if (link->state & DEV_STALE_CONFIG) { + link->state |= DEV_STALE_LINK; + return; + } + } + + if (link->handle) + CardServices(DeregisterClient, link->handle); + + /* Unlink device structure, free bits */ + *linkp = link->next; + if (link->dev) + unregister_netdev(&smc->dev); + kfree(smc); + +} /* smc91c92_detach */ + +/*====================================================================*/ + +static int cvt_ascii_address(struct net_device *dev, char *s) +{ + int i, j, da, c; + + if (strlen(s) != 12) + return -1; + for (i = 0; i < 6; i++) { + da = 0; + for (j = 0; j < 2; j++) { + c = *s++; + da <<= 4; + da += ((c >= '0') && (c <= '9')) ? + (c - '0') : ((c & 0x0f) + 9); + } + dev->dev_addr[i] = da; + } + return 0; +} + +/*====================================================================*/ + +static int get_tuple(int fn, client_handle_t handle, tuple_t *tuple, + cisparse_t *parse) +{ + int i; + i = CardServices(fn, handle, tuple); + if (i != CS_SUCCESS) return i; + i = CardServices(GetTupleData, handle, tuple); + if (i != CS_SUCCESS) return i; + return CardServices(ParseTuple, handle, tuple, parse); +} + +#define first_tuple(a, b, c) get_tuple(GetFirstTuple, a, b, c) +#define next_tuple(a, b, c) get_tuple(GetNextTuple, a, b, c) + +/*====================================================================== + + Configuration stuff for Megahertz cards + + 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. + +======================================================================*/ + +static int mhz_3288_power(dev_link_t *link) +{ + struct smc_private *smc = link->priv; + u_char tmp; + + /* Read the ISR twice... */ + readb(smc->base+MEGAHERTZ_ISR); + udelay(5); + readb(smc->base+MEGAHERTZ_ISR); + + /* Pause 200ms... */ + mdelay(200); + + /* Now read and write the COR... */ + tmp = readb(smc->base + link->conf.ConfigBase + CISREG_COR); + udelay(5); + writeb(tmp, smc->base + link->conf.ConfigBase + CISREG_COR); + + return 0; +} + +static int mhz_mfc_config(dev_link_t *link) +{ + struct smc_private *smc = link->priv; + struct net_device *dev = &smc->dev; + tuple_t tuple; + cisparse_t parse; + u_char buf[255]; + cistpl_cftable_entry_t *cf = &parse.cftable_entry; + win_req_t req; + memreq_t mem; + int i, k; + + link->conf.Attributes |= CONF_ENABLE_SPKR; + link->conf.Status = CCSR_AUDIO_ENA; + link->irq.Attributes = + IRQ_TYPE_DYNAMIC_SHARING|IRQ_FIRST_SHARED|IRQ_HANDLE_PRESENT; + link->io.IOAddrLines = 16; + link->io.Attributes2 = IO_DATA_PATH_WIDTH_8; + link->io.NumPorts2 = 8; + + tuple.Attributes = tuple.TupleOffset = 0; + tuple.TupleData = (cisdata_t *)buf; + tuple.TupleDataMax = sizeof(buf); + tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY; + + i = first_tuple(link->handle, &tuple, &parse); + /* The Megahertz combo cards have modem-like CIS entries, so + we have to explicitly try a bunch of port combinations. */ + while (i == CS_SUCCESS) { + link->conf.ConfigIndex = cf->index; + link->io.BasePort2 = cf->io.win[0].base; + for (k = 0; k < 0x400; k += 0x10) { + if (k & 0x80) continue; + link->io.BasePort1 = k ^ 0x300; + i = CardServices(RequestIO, link->handle, &link->io); + if (i == CS_SUCCESS) break; + } + if (i == CS_SUCCESS) break; + i = next_tuple(link->handle, &tuple, &parse); + } + if (i != CS_SUCCESS) + return i; + dev->base_addr = link->io.BasePort1; + + /* Allocate a memory window, for accessing the ISR */ + req.Attributes = WIN_DATA_WIDTH_8|WIN_MEMORY_TYPE_AM|WIN_ENABLE; + req.Base = req.Size = 0; + req.AccessSpeed = 0; + link->win = (window_handle_t)link->handle; + i = CardServices(RequestWindow, &link->win, &req); + if (i != CS_SUCCESS) + return i; + smc->base = ioremap(req.Base, req.Size); + mem.CardOffset = mem.Page = 0; + if (smc->manfid == MANFID_MOTOROLA) + mem.CardOffset = link->conf.ConfigBase; + i = CardServices(MapMemPage, link->win, &mem); + + if ((i == CS_SUCCESS) + && (smc->manfid == MANFID_MEGAHERTZ) + && (smc->cardid == PRODID_MEGAHERTZ_EM3288)) + mhz_3288_power(link); + + return i; +} + +static int mhz_setup(dev_link_t *link) +{ + client_handle_t handle = link->handle; + struct smc_private *smc = link->priv; + struct net_device *dev = &smc->dev; + tuple_t tuple; + cisparse_t parse; + u_char buf[255], *station_addr; + + tuple.Attributes = tuple.TupleOffset = 0; + tuple.TupleData = buf; + tuple.TupleDataMax = sizeof(buf); + + /* Read the station address from the CIS. It is stored as the last + (fourth) string in the Version 1 Version/ID tuple. */ + tuple.DesiredTuple = CISTPL_VERS_1; + if (first_tuple(handle, &tuple, &parse) != CS_SUCCESS) + return -1; + /* Ugh -- the EM1144 card has two VERS_1 tuples!?! */ + if (next_tuple(handle, &tuple, &parse) != CS_SUCCESS) + first_tuple(handle, &tuple, &parse); + if (parse.version_1.ns > 3) { + station_addr = parse.version_1.str + parse.version_1.ofs[3]; + if (cvt_ascii_address(dev, station_addr) == 0) + return 0; + } + + /* Another possibility: for the EM3288, in a special tuple */ + tuple.DesiredTuple = 0x81; + if (CardServices(GetFirstTuple, handle, &tuple) != CS_SUCCESS) + return -1; + if (CardServices(GetTupleData, handle, &tuple) != CS_SUCCESS) + return -1; + buf[12] = '\0'; + if (cvt_ascii_address(dev, buf) == 0) + return 0; + + return -1; +} + +/*====================================================================== + + Configuration stuff for the Motorola Mariner + + mot_config() writes directly to the Mariner configuration + registers because the CIS is just bogus. + +======================================================================*/ + +static void mot_config(dev_link_t *link) +{ + struct smc_private *smc = link->priv; + struct net_device *dev = &smc->dev; + ioaddr_t ioaddr = dev->base_addr; + ioaddr_t iouart = link->io.BasePort2; + + /* Set UART base address and force map with COR bit 1 */ + writeb(iouart & 0xff, smc->base + MOT_UART + CISREG_IOBASE_0); + writeb((iouart >> 8) & 0xff, smc->base + MOT_UART + CISREG_IOBASE_1); + writeb(MOT_NORMAL, smc->base + MOT_UART + CISREG_COR); + + /* Set SMC base address and force map with COR bit 1 */ + writeb(ioaddr & 0xff, smc->base + MOT_LAN + CISREG_IOBASE_0); + writeb((ioaddr >> 8) & 0xff, smc->base + MOT_LAN + CISREG_IOBASE_1); + writeb(MOT_NORMAL, smc->base + MOT_LAN + CISREG_COR); + + /* Wait for things to settle down */ + mdelay(100); +} + +static int mot_setup(dev_link_t *link) +{ + struct smc_private *smc = link->priv; + struct net_device *dev = &smc->dev; + ioaddr_t ioaddr = dev->base_addr; + int i, wait, loop; + u_int addr; + + /* Read Ethernet address from Serial EEPROM */ + + for (i = 0; i < 3; i++) { + SMC_SELECT_BANK(2); + outw(MOT_EEPROM + i, ioaddr + POINTER); + SMC_SELECT_BANK(1); + outw((CTL_RELOAD | CTL_EE_SELECT), ioaddr + CONTROL); + + for (loop = wait = 0; loop < 200; loop++) { + udelay(10); + wait = ((CTL_RELOAD | CTL_STORE) & inw(ioaddr + CONTROL)); + if (wait == 0) break; + } + + if (wait) + return -1; + + addr = inw(ioaddr + GENERAL); + dev->dev_addr[2*i] = addr & 0xff; + dev->dev_addr[2*i+1] = (addr >> 8) & 0xff; + } + + return 0; +} + +/*====================================================================*/ + +static int smc_config(dev_link_t *link) +{ + struct smc_private *smc = link->priv; + struct net_device *dev = &smc->dev; + tuple_t tuple; + cisparse_t parse; + u_char buf[255]; + cistpl_cftable_entry_t *cf = &parse.cftable_entry; + int i; + + tuple.Attributes = tuple.TupleOffset = 0; + tuple.TupleData = (cisdata_t *)buf; + tuple.TupleDataMax = sizeof(buf); + tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY; + + link->io.NumPorts1 = 16; + i = first_tuple(link->handle, &tuple, &parse); + while (i != CS_NO_MORE_ITEMS) { + if (i == CS_SUCCESS) { + link->conf.ConfigIndex = cf->index; + link->io.BasePort1 = cf->io.win[0].base; + link->io.IOAddrLines = cf->io.flags & CISTPL_IO_LINES_MASK; + i = CardServices(RequestIO, link->handle, &link->io); + if (i == CS_SUCCESS) break; + } + i = next_tuple(link->handle, &tuple, &parse); + } + if (i == CS_SUCCESS) + dev->base_addr = link->io.BasePort1; + return i; +} + +static int smc_setup(dev_link_t *link) +{ + client_handle_t handle = link->handle; + struct smc_private *smc = link->priv; + struct net_device *dev = &smc->dev; + tuple_t tuple; + cisparse_t parse; + cistpl_lan_node_id_t *node_id; + u_char buf[255], *station_addr; + int i; + + tuple.Attributes = tuple.TupleOffset = 0; + tuple.TupleData = buf; + tuple.TupleDataMax = sizeof(buf); + + /* Check for a LAN function extension tuple */ + tuple.DesiredTuple = CISTPL_FUNCE; + i = first_tuple(handle, &tuple, &parse); + while (i == CS_SUCCESS) { + if (parse.funce.type == CISTPL_FUNCE_LAN_NODE_ID) + break; + i = next_tuple(handle, &tuple, &parse); + } + if (i == CS_SUCCESS) { + node_id = (cistpl_lan_node_id_t *)parse.funce.data; + if (node_id->nb == 6) { + for (i = 0; i < 6; i++) + dev->dev_addr[i] = node_id->id[i]; + return 0; + } + } + + /* Try the third string in the Version 1 Version/ID tuple. */ + tuple.DesiredTuple = CISTPL_VERS_1; + if (first_tuple(handle, &tuple, &parse) != CS_SUCCESS) + return -1; + station_addr = parse.version_1.str + parse.version_1.ofs[2]; + if (cvt_ascii_address(dev, station_addr) == 0) + return 0; + + return -1; +} + +/*====================================================================*/ + +static int osi_config(dev_link_t *link) +{ + struct smc_private *smc = link->priv; + struct net_device *dev = &smc->dev; + static ioaddr_t com[4] = { 0x3f8, 0x2f8, 0x3e8, 0x2e8 }; + int i, j; + + link->conf.Attributes |= CONF_ENABLE_SPKR; + link->conf.Status = CCSR_AUDIO_ENA; + link->irq.Attributes = + IRQ_TYPE_DYNAMIC_SHARING|IRQ_FIRST_SHARED|IRQ_HANDLE_PRESENT; + link->io.NumPorts1 = 64; + link->io.Attributes2 = IO_DATA_PATH_WIDTH_8; + link->io.NumPorts2 = 8; + link->io.IOAddrLines = 16; + + /* Enable Hard Decode, LAN, Modem */ + link->conf.ConfigIndex = 0x23; + + for (i = j = 0; j < 4; j++) { + link->io.BasePort2 = com[j]; + i = CardServices(RequestIO, link->handle, &link->io); + if (i == CS_SUCCESS) break; + } + if (i != CS_SUCCESS) { + /* Fallback: turn off hard decode */ + link->conf.ConfigIndex = 0x03; + link->io.NumPorts2 = 0; + i = CardServices(RequestIO, link->handle, &link->io); + } + dev->base_addr = link->io.BasePort1 + 0x10; + return i; +} + +static int osi_setup(dev_link_t *link, u_short manfid, u_short cardid) +{ + client_handle_t handle = link->handle; + struct smc_private *smc = link->priv; + struct net_device *dev = &smc->dev; + tuple_t tuple; + u_char buf[255]; + int i; + + tuple.Attributes = TUPLE_RETURN_COMMON; + tuple.TupleData = buf; + tuple.TupleDataMax = sizeof(buf); + tuple.TupleOffset = 0; + + /* Read the station address from tuple 0x90, subtuple 0x04 */ + tuple.DesiredTuple = 0x90; + i = CardServices(GetFirstTuple, handle, &tuple); + while (i == CS_SUCCESS) { + i = CardServices(GetTupleData, handle, &tuple); + if ((i != CS_SUCCESS) || (buf[0] == 0x04)) + break; + i = CardServices(GetNextTuple, handle, &tuple); + } + if (i != CS_SUCCESS) + return -1; + for (i = 0; i < 6; i++) + dev->dev_addr[i] = buf[i+2]; + + if (((manfid == MANFID_OSITECH) && + (cardid == PRODID_OSITECH_SEVEN)) || + ((manfid == MANFID_PSION) && + (cardid == PRODID_PSION_NET100))) { + /* Download the Seven of Diamonds firmware */ + for (i = 0; i < sizeof(__Xilinx7OD); i++) { + outb(__Xilinx7OD[i], link->io.BasePort1+2); + udelay(50); + } + } else if (manfid == MANFID_OSITECH) { + /* Make sure both functions are powered up */ + set_bits(0x300, link->io.BasePort1 + OSITECH_AUI_PWR); + /* Now, turn on the interrupt for both card functions */ + set_bits(0x300, link->io.BasePort1 + OSITECH_RESET_ISR); + DEBUG(2, "AUI/PWR: %4.4x RESET/ISR: %4.4x\n", + inw(link->io.BasePort1 + OSITECH_AUI_PWR), + inw(link->io.BasePort1 + OSITECH_RESET_ISR)); + } + + return 0; +} + +/*====================================================================== + + This verifies that the chip is some SMC91cXX variant, and returns + the revision code if successful. Otherwise, it returns -ENODEV. + +======================================================================*/ + +static int check_sig(dev_link_t *link) +{ + struct smc_private *smc = link->priv; + struct net_device *dev = &smc->dev; + ioaddr_t ioaddr = dev->base_addr; + int width; + u_short s; + + 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->io.Attributes1 == 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) { + event_callback_args_t args; + printk(KERN_INFO "smc91c92_cs: using 8-bit IO window.\n"); + args.client_data = link; + smc91c92_event(CS_EVENT_RESET_PHYSICAL, 0, &args); + CardServices(ReleaseIO, link->handle, &link->io); + link->io.Attributes1 = IO_DATA_PATH_WIDTH_8; + CardServices(RequestIO, link->handle, &link->io); + smc91c92_event(CS_EVENT_CARD_RESET, 0, &args); + return check_sig(link); + } + return -ENODEV; +} + +/*====================================================================== + + smc91c92_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. + +======================================================================*/ + +#define CS_EXIT_TEST(ret, svc, label) \ +if (ret != CS_SUCCESS) { cs_error(link->handle, svc, ret); goto label; } + +static void smc91c92_config(dev_link_t *link) +{ + client_handle_t handle = link->handle; + struct smc_private *smc = link->priv; + struct net_device *dev = &smc->dev; + tuple_t tuple; + cisparse_t parse; + u_short buf[32]; + char *name; + int i, j, rev; + ioaddr_t ioaddr; + + DEBUG(0, "smc91c92_config(0x%p)\n", link); + + tuple.Attributes = tuple.TupleOffset = 0; + tuple.TupleData = (cisdata_t *)buf; + tuple.TupleDataMax = sizeof(buf); + + tuple.DesiredTuple = CISTPL_CONFIG; + i = first_tuple(handle, &tuple, &parse); + CS_EXIT_TEST(i, ParseTuple, config_failed); + link->conf.ConfigBase = parse.config.base; + link->conf.Present = parse.config.rmask[0]; + + tuple.DesiredTuple = CISTPL_MANFID; + tuple.Attributes = TUPLE_RETURN_COMMON; + if (first_tuple(handle, &tuple, &parse) == CS_SUCCESS) { + smc->manfid = parse.manfid.manf; + smc->cardid = parse.manfid.card; + } + + /* Configure card */ + link->state |= DEV_CONFIG; + + if ((smc->manfid == MANFID_OSITECH) && + (smc->cardid != PRODID_OSITECH_SEVEN)) { + i = osi_config(link); + } else if ((smc->manfid == MANFID_MOTOROLA) || + ((smc->manfid == MANFID_MEGAHERTZ) && + ((smc->cardid == PRODID_MEGAHERTZ_VARIOUS) || + (smc->cardid == PRODID_MEGAHERTZ_EM3288)))) { + i = mhz_mfc_config(link); + } else { + i = smc_config(link); + } + CS_EXIT_TEST(i, RequestIO, config_failed); + + i = CardServices(RequestIRQ, link->handle, &link->irq); + CS_EXIT_TEST(i, RequestIRQ, config_failed); + i = CardServices(RequestConfiguration, link->handle, &link->conf); + CS_EXIT_TEST(i, RequestConfiguration, config_failed); + + if (smc->manfid == MANFID_MOTOROLA) + mot_config(link); + + dev->irq = link->irq.AssignedIRQ; + + if ((if_port >= 0) && (if_port <= 2)) + dev->if_port = if_port; + else + printk(KERN_NOTICE "smc91c92_cs: invalid if_port requested\n"); + + if (register_netdev(dev) != 0) { + printk(KERN_ERR "smc91c92_cs: register_netdev() failed\n"); + goto config_undo; + } + + 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) { + printk(KERN_NOTICE "smc91c92_cs: Unable to find hardware address.\n"); + goto config_undo; + } + + copy_dev_name(smc->node, dev); + link->dev = &smc->node; + 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; + } + printk(KERN_INFO "%s: smc91c%s rev %d: io %#3lx, irq %d, " + "hw_addr ", dev->name, name, (rev & 0x0f), dev->base_addr, + dev->irq); + for (i = 0; i < 6; i++) + printk("%02X%s", dev->dev_addr[i], ((i<5) ? ":" : "\n")); + + ioaddr = dev->base_addr; + if (rev > 0) { + u_long mir, mcr; + SMC_SELECT_BANK(0); + mir = inw(ioaddr + MEMINFO) & 0xff; + if (mir == 0xff) mir++; + /* Get scale factor for memory size */ + mcr = ((rev >> 4) > 3) ? inw(ioaddr + MEMCFG) : 0x0200; + mir *= 128 * (1<<((mcr >> 9) & 7)); + if (mir & 0x3ff) + printk(KERN_INFO " %lu byte", mir); + else + printk(KERN_INFO " %lu kb", mir>>10); + SMC_SELECT_BANK(1); + smc->cfg = inw(ioaddr + CONFIG) & ~CFG_AUI_SELECT; + smc->cfg |= CFG_NO_WAIT | CFG_16BIT | CFG_STATIC; + if (smc->manfid == MANFID_OSITECH) + smc->cfg |= CFG_IRQ_SEL_1 | CFG_IRQ_SEL_0; + if ((rev >> 4) >= 7) + smc->cfg |= CFG_MII_SELECT; + printk(" buffer, %s xcvr\n", (smc->cfg & CFG_MII_SELECT) ? + "MII" : if_names[dev->if_port]); + } + + if (smc->cfg & CFG_MII_SELECT) { + SMC_SELECT_BANK(3); + + for (i = 0; i < 32; i++) { + j = mdio_read(dev, i, 1); + if ((j != 0) && (j != 0xffff)) break; + } + smc->phy_id = (i < 32) ? i : -1; + if (i < 32) { + DEBUG(0, " MII transceiver at index %d, status %x.\n", i, j); + } else { + printk(KERN_NOTICE " No MII transceivers found!\n"); + } + + SMC_SELECT_BANK(0); + } + + link->state &= ~DEV_CONFIG_PENDING; + return; + +config_undo: + unregister_netdev(dev); +config_failed: /* CS_EXIT_TEST() calls jump to here... */ + smc91c92_release((u_long)link); + link->state &= ~DEV_CONFIG_PENDING; + +} /* smc91c92_config */ + +/*====================================================================== + + After a card is removed, smc91c92_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 smc91c92_release(u_long arg) +{ + dev_link_t *link = (dev_link_t *)arg; + struct smc_private *smc = link->priv; + + DEBUG(0, "smc91c92_release(0x%p)\n", link); + + if (link->open) { + DEBUG(1, "smc91c92_cs: release postponed, '%s' still open\n", + link->dev->dev_name); + link->state |= DEV_STALE_CONFIG; + return; + } + + CardServices(ReleaseConfiguration, link->handle); + CardServices(ReleaseIO, link->handle, &link->io); + CardServices(ReleaseIRQ, link->handle, &link->irq); + if (link->win) { + iounmap(smc->base); + CardServices(ReleaseWindow, link->win); + } + + link->state &= ~DEV_CONFIG; + +} /* smc91c92_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. + +======================================================================*/ + +static int smc91c92_event(event_t event, int priority, + event_callback_args_t *args) +{ + dev_link_t *link = args->client_data; + struct smc_private *smc = link->priv; + struct net_device *dev = &smc->dev; + int i; + + DEBUG(1, "smc91c92_event(0x%06x)\n", event); + + switch (event) { + case CS_EVENT_CARD_REMOVAL: + link->state &= ~DEV_PRESENT; + if (link->state & DEV_CONFIG) { + netif_device_detach(dev); + mod_timer(&link->release, jiffies + HZ/20); + } + break; + case CS_EVENT_CARD_INSERTION: + link->state |= DEV_PRESENT | DEV_CONFIG_PENDING; + smc91c92_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) + netif_device_detach(dev); + 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) { + if ((smc->manfid == MANFID_MEGAHERTZ) && + (smc->cardid == PRODID_MEGAHERTZ_EM3288)) + mhz_3288_power(link); + CardServices(RequestConfiguration, link->handle, &link->conf); + if (smc->manfid == MANFID_MOTOROLA) + mot_config(link); + if ((smc->manfid == MANFID_OSITECH) && + (smc->cardid != PRODID_OSITECH_SEVEN)) { + /* Power up the card and enable interrupts */ + set_bits(0x0300, dev->base_addr-0x10+OSITECH_AUI_PWR); + set_bits(0x0300, dev->base_addr-0x10+OSITECH_RESET_ISR); + } + if (((smc->manfid == MANFID_OSITECH) && + (smc->cardid == PRODID_OSITECH_SEVEN)) || + ((smc->manfid == MANFID_PSION) && + (smc->cardid == PRODID_PSION_NET100))) { + /* Download the Seven of Diamonds firmware */ + for (i = 0; i < sizeof(__Xilinx7OD); i++) { + outb(__Xilinx7OD[i], link->io.BasePort1+2); + udelay(50); + } + } + if (link->open) { + smc_reset(dev); + netif_device_attach(dev); + } + } + break; + } + return 0; +} /* smc91c92_event */ + +/*====================================================================== + + MII interface support for SMC91cXX based cards +======================================================================*/ + +#define MDIO_SHIFT_CLK 0x04 +#define MDIO_DATA_OUT 0x01 +#define MDIO_DIR_WRITE 0x08 +#define MDIO_DATA_WRITE0 (MDIO_DIR_WRITE) +#define MDIO_DATA_WRITE1 (MDIO_DIR_WRITE | MDIO_DATA_OUT) +#define MDIO_DATA_READ 0x02 + +static void mdio_sync(ioaddr_t addr) +{ + int bits; + for (bits = 0; bits < 32; bits++) { + outb(MDIO_DATA_WRITE1, addr); + outb(MDIO_DATA_WRITE1 | MDIO_SHIFT_CLK, addr); + } +} + +static int mdio_read(struct net_device *dev, int phy_id, int loc) +{ + ioaddr_t 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; +} + +static void mdio_write(struct net_device *dev, int phy_id, int loc, int value) +{ + ioaddr_t 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); + } +} + +/*====================================================================== + + The driver core code, most of which should be common with a + non-PCMCIA implementation. + +======================================================================*/ + +#ifdef PCMCIA_DEBUG +static void smc_dump(struct net_device *dev) +{ + ioaddr_t ioaddr = dev->base_addr; + u_short i, w, save; + save = inw(ioaddr + BANK_SELECT); + for (w = 0; w < 4; w++) { + SMC_SELECT_BANK(w); + printk(KERN_DEBUG "bank %d: ", w); + for (i = 0; i < 14; i += 2) + printk(" %04x", inw(ioaddr + i)); + printk("\n"); + } + outw(save, ioaddr + BANK_SELECT); +} +#endif + +static int smc_open(struct net_device *dev) +{ + struct smc_private *smc = dev->priv; + dev_link_t *link = &smc->link; + +#ifdef PCMCIA_DEBUG + DEBUG(0, "%s: smc_open(%p), ID/Window %4.4x.\n", + dev->name, dev, inw(dev->base_addr + BANK_SELECT)); + if (pc_debug > 1) smc_dump(dev); +#endif + + /* Check that the PCMCIA card is still here. */ + if (!DEV_OK(link)) + return -ENODEV; + /* Physical device present signature. */ + if (check_sig(link) < 0) { + printk("smc91c92_cs: Yikes! Bad chip signature!\n"); + return -ENODEV; + } + link->open++; + MOD_INC_USE_COUNT; + + netif_start_queue(dev); + netif_mark_up(dev); + smc->saved_skb = 0; + smc->packets_waiting = 0; + + smc_reset(dev); + smc->media.function = &media_check; + smc->media.data = (u_long)smc; + smc->media.expires = jiffies + HZ; + add_timer(&smc->media); + + return 0; +} /* smc_open */ + +/*====================================================================*/ + +static int smc_close(struct net_device *dev) +{ + struct smc_private *smc = dev->priv; + dev_link_t *link = &smc->link; + ioaddr_t ioaddr = dev->base_addr; + + DEBUG(0, "%s: smc_close(), status %4.4x.\n", + dev->name, inw(ioaddr + BANK_SELECT)); + + netif_stop_queue(dev); + netif_mark_down(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 ); + + link->open--; + del_timer(&smc->media); + if (link->state & DEV_STALE_CONFIG) + mod_timer(&link->release, jiffies + HZ/20); + + MOD_DEC_USE_COUNT; + + return 0; +} /* smc_close */ + +static int smc_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) +{ + struct smc_private *smc = dev->priv; + u16 *data = (u16 *)&rq->ifr_data; + ushort saved_bank; + ioaddr_t ioaddr = dev->base_addr; + + if (!(smc->cfg & CFG_MII_SELECT)) + return -EOPNOTSUPP; + + saved_bank = inw(ioaddr + BANK_SELECT); + SMC_SELECT_BANK(3); + + switch (cmd) { + case SIOCDEVPRIVATE: + data[0] = smc->phy_id; + case SIOCDEVPRIVATE+1: + data[3] = mdio_read(dev, data[0], data[1] & 0x1f); + SMC_SELECT_BANK(saved_bank); + return 0; + case SIOCDEVPRIVATE+2: + if (!capable(CAP_NET_ADMIN)) { + SMC_SELECT_BANK(saved_bank); + return -EPERM; + } + mdio_write(dev, data[0], data[1] & 0x1f, data[2]); + SMC_SELECT_BANK(saved_bank); + return 0; + } + SMC_SELECT_BANK(saved_bank); + return -EOPNOTSUPP; +} +/*====================================================================== + + Transfer a packet to the hardware and trigger the packet send. + This may be called at either from either the Tx queue code + or the interrupt handler. + +======================================================================*/ + +static void smc_hardware_send_packet(struct net_device * dev) +{ + struct smc_private *smc = dev->priv; + struct sk_buff *skb = smc->saved_skb; + ioaddr_t ioaddr = dev->base_addr; + u_char packet_no; + + if (!skb) { + printk(KERN_ERR "%s: In XMIT with no packet to send.\n", dev->name); + 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. */ + printk(KERN_WARNING "%s: 91c92 hardware Tx buffer allocation" + " failed, status %#2.2x.\n", dev->name, packet_no); + dev_kfree_skb_irq(skb); + smc->saved_skb = NULL; + netif_start_queue(dev); + return; + } + + add_tx_bytes(&smc->stats, 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. */ + + DEBUG(2, "%s: Trying to xmit packet of length %d.\n", + dev->name, 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); + + smc->saved_skb = NULL; + dev_kfree_skb_irq(skb); + dev->trans_start = jiffies; + netif_start_queue(dev); + return; +} + +/*====================================================================*/ + +static void smc_tx_timeout(struct net_device *dev) +{ + struct smc_private *smc = dev->priv; + ioaddr_t ioaddr = dev->base_addr; + + printk(KERN_NOTICE "%s: SMC91c92 transmit timed out, " + "Tx_status %2.2x status %4.4x.\n", + dev->name, inw(ioaddr)&0xff, inw(ioaddr + 2)); + smc->stats.tx_errors++; + smc_reset(dev); + dev->trans_start = jiffies; + smc->saved_skb = NULL; + netif_wake_queue(dev); +} + +static int smc_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct smc_private *smc = dev->priv; + ioaddr_t ioaddr = dev->base_addr; + u_short num_pages; + short time_out, ir; + + tx_timeout_check(dev, smc_tx_timeout); + skb_tx_check(dev, skb); + + DEBUG(2, "%s: smc_start_xmit(length = %d) called," + " status %4.4x.\n", dev->name, skb->len, inw(ioaddr + 2)); + + if (smc->saved_skb) { + /* THIS SHOULD NEVER HAPPEN. */ + smc->stats.tx_aborted_errors++; + printk(KERN_DEBUG "%s: Internal error -- sent packet while busy.\n", + dev->name); + return 1; + } + smc->saved_skb = skb; + + num_pages = skb->len >> 8; + + if (num_pages > 7) { + printk(KERN_ERR "%s: Far too big packet error.\n", dev->name); + DEV_KFREE_SKB (skb); + smc->saved_skb = NULL; + smc->stats.tx_dropped++; + return 0; /* Do not re-queue this packet. */ + } + /* A packet is now waiting. */ + smc->packets_waiting++; + + 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.. */ + return 0; + } + } + + /* Otherwise defer until the Tx-space-allocated interrupt. */ + DEBUG(2, "%s: memory allocation deferred.\n", dev->name); + outw((IM_ALLOC_INT << 8) | (ir & 0xff00), ioaddr + INTERRUPT); + + return 0; +} + +/*====================================================================== + + Handle a Tx anomolous event. Entered while in Window 2. + +======================================================================*/ + +static void smc_tx_err(struct net_device * dev) +{ + struct smc_private *smc = (struct smc_private *)dev->priv; + ioaddr_t ioaddr = dev->base_addr; + int saved_packet = inw(ioaddr + PNR_ARR) & 0xff; + int packet_no = inw(ioaddr + FIFO_PORTS) & 0x7f; + int tx_status; + + /* select this as the packet to read from */ + outw(packet_no, ioaddr + PNR_ARR); + + /* read the first word from this packet */ + outw(PTR_AUTOINC | PTR_READ | 0, ioaddr + POINTER); + + tx_status = inw(ioaddr + DATA_1); + + smc->stats.tx_errors++; + if (tx_status & TS_LOSTCAR) smc->stats.tx_carrier_errors++; + if (tx_status & TS_LATCOL) smc->stats.tx_window_errors++; + if (tx_status & TS_16COL) { + smc->stats.tx_aborted_errors++; + smc->tx_err++; + } + + if (tx_status & TS_SUCCESS) { + printk(KERN_NOTICE "%s: Successful packet caused error " + "interrupt?\n", dev->name); + } + /* re-enable transmit */ + SMC_SELECT_BANK(0); + outw(inw(ioaddr + TCR) | TCR_ENABLE | smc->duplex, ioaddr + TCR); + SMC_SELECT_BANK(2); + + outw(MC_FREEPKT, ioaddr + MMU_CMD); /* Free the packet memory. */ + + /* one less packet waiting for me */ + smc->packets_waiting--; + + outw(saved_packet, ioaddr + PNR_ARR); + return; +} + +/*====================================================================*/ + +static void smc_eph_irq(struct net_device *dev) +{ + struct smc_private *smc = dev->priv; + ioaddr_t ioaddr = dev->base_addr; + u_short card_stats, ephs; + + SMC_SELECT_BANK(0); + ephs = inw(ioaddr + EPH); + DEBUG(2, "%s: Ethernet protocol handler interrupt, status" + " %4.4x.\n", dev->name, ephs); + /* Could be a counter roll-over warning: update stats. */ + card_stats = inw(ioaddr + COUNTER); + /* single collisions */ + smc->stats.collisions += card_stats & 0xF; + card_stats >>= 4; + /* multiple collisions */ + smc->stats.collisions += card_stats & 0xF; +#if 0 /* These are for when linux supports these statistics */ + card_stats >>= 4; /* deferred */ + card_stats >>= 4; /* excess deferred */ +#endif + /* If we had a transmit error we must re-enable the transmitter. */ + outw(inw(ioaddr + TCR) | TCR_ENABLE | smc->duplex, ioaddr + TCR); + + /* Clear a link error interrupt. */ + SMC_SELECT_BANK(1); + outw(CTL_AUTO_RELEASE | 0x0000, ioaddr + CONTROL); + outw(CTL_AUTO_RELEASE | CTL_TE_ENABLE | CTL_CR_ENABLE, + ioaddr + CONTROL); + SMC_SELECT_BANK(2); +} + +/*====================================================================*/ + +static void smc_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct smc_private *smc = dev_id; + struct net_device *dev = &smc->dev; + ioaddr_t ioaddr; + u_short saved_bank, saved_pointer, mask, status; + char bogus_cnt = INTR_WORK; /* Work we are willing to do. */ + + if (!netif_device_present(dev)) + return; + ioaddr = dev->base_addr; + + DEBUG(3, "%s: SMC91c92 interrupt %d at %#x.\n", dev->name, + irq, ioaddr); + + 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. */ + DEBUG(1, "%s: SMC91c92 interrupt %d for non-existent" + "/ejected device.\n", dev->name, irq); + goto irq_done; + } + + SMC_SELECT_BANK(2); + saved_pointer = inw(ioaddr + POINTER); + mask = inw(ioaddr + INTERRUPT) >> 8; + /* clear all interrupts */ + outw(0, ioaddr + INTERRUPT); + + do { /* read the status flag, and mask it */ + status = inw(ioaddr + INTERRUPT) & 0xff; + DEBUG(3, "%s: Status is %#2.2x (mask %#2.2x).\n", dev->name, + status, mask); + if ((status & mask) == 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; + smc->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) { + smc->stats.rx_errors++; + smc->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); + + DEBUG(3, " Restoring saved registers mask %2.2x bank %4.4x" + " pointer %4.4x.\n", mask, saved_bank, saved_pointer); + + /* restore state register */ + outw((mask<<8), ioaddr + INTERRUPT); + outw(saved_pointer, ioaddr + POINTER); + SMC_SELECT_BANK(saved_bank); + + DEBUG(3, "%s: Exiting interrupt IRQ%d.\n", dev->name, irq); + +irq_done: + + if ((smc->manfid == MANFID_OSITECH) && + (smc->cardid != PRODID_OSITECH_SEVEN)) { + /* Retrigger interrupt if needed */ + mask_bits(0x00ff, ioaddr-0x10+OSITECH_RESET_ISR); + set_bits(0x0300, ioaddr-0x10+OSITECH_RESET_ISR); + } + if (smc->manfid == MANFID_MOTOROLA) { + u_char cor; + cor = readb(smc->base + MOT_UART + CISREG_COR); + writeb(cor & ~COR_IREQ_ENA, smc->base + MOT_UART + CISREG_COR); + writeb(cor, smc->base + MOT_UART + CISREG_COR); + cor = readb(smc->base + MOT_LAN + CISREG_COR); + writeb(cor & ~COR_IREQ_ENA, smc->base + MOT_LAN + CISREG_COR); + writeb(cor, smc->base + MOT_LAN + CISREG_COR); + } +#ifdef DOES_NOT_WORK + if (smc->base != NULL) { /* Megahertz MFC's */ + readb(smc->base+MEGAHERTZ_ISR); + readb(smc->base+MEGAHERTZ_ISR); + } +#endif +} + +/*====================================================================*/ + +static void smc_rx(struct net_device *dev) +{ + struct smc_private *smc = (struct smc_private *)dev->priv; + ioaddr_t 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) { + printk(KERN_ERR "%s: smc_rx() with nothing on Rx FIFO.\n", + dev->name); + 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; + + DEBUG(2, "%s: Receive status %4.4x length %d.\n", + dev->name, rx_status, packet_length); + + if (!(rx_status & RS_ERRORS)) { + /* do stuff to make a new packet */ + struct sk_buff *skb; + + /* Note: packet_length adds 5 or 6 extra bytes here! */ + skb = dev_alloc_skb(packet_length+2); + + if (skb == NULL) { + DEBUG(1, "%s: Low memory, packet dropped.\n", dev->name); + smc->stats.rx_dropped++; + outw(MC_RELEASE, ioaddr + MMU_CMD); + return; + } + + packet_length -= (rx_status & RS_ODDFRAME ? 5 : 6); + skb_reserve(skb, 2); + insw(ioaddr+DATA_1, skb_put(skb, packet_length), + (packet_length+1)>>1); + skb->protocol = eth_type_trans(skb, dev); + + skb->dev = dev; + netif_rx(skb); + dev->last_rx = jiffies; + smc->stats.rx_packets++; + add_rx_bytes(&smc->stats, packet_length); + if (rx_status & RS_MULTICAST) + smc->stats.multicast++; + } else { + /* error ... */ + smc->stats.rx_errors++; + + if (rx_status & RS_ALGNERR) smc->stats.rx_frame_errors++; + if (rx_status & (RS_TOOSHORT | RS_TOOLONG)) + smc->stats.rx_length_errors++; + if (rx_status & RS_BADCRC) smc->stats.rx_crc_errors++; + } + /* Let the MMU free the memory of this packet. */ + outw(MC_RELEASE, ioaddr + MMU_CMD); + + return; +} + +/*====================================================================*/ + +static struct net_device_stats *smc_get_stats(struct net_device *dev) +{ + struct smc_private *smc = (struct smc_private *)dev->priv; + /* Nothing to update - the 91c92 is a pretty primative chip. */ + return &smc->stats; +} + +/*====================================================================== + + Calculate values for the hardware multicast filter hash table. + +======================================================================*/ + +static void fill_multicast_tbl(int count, struct dev_mc_list *addrs, + u_char *multicast_table) +{ + struct dev_mc_list *mc_addr; + + for (mc_addr = addrs; mc_addr && --count > 0; mc_addr = mc_addr->next) { + u_int position = ether_crc(6, mc_addr->dmi_addr); +#ifndef final_version /* Verify multicast address. */ + if ((mc_addr->dmi_addr[0] & 1) == 0) + continue; +#endif + multicast_table[position >> 29] |= 1 << ((position >> 26) & 7); + } +} + +/*====================================================================== + + Set the receive mode. + + 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. + +======================================================================*/ + +static void set_rx_mode(struct net_device *dev) +{ + ioaddr_t ioaddr = dev->base_addr; + u_int multicast_table[ 2 ] = { 0, }; + unsigned long flags; + u_short rx_cfg_setting; + + if (dev->flags & IFF_PROMISC) { + printk(KERN_NOTICE "%s: setting Rx mode to promiscuous.\n", dev->name); + rx_cfg_setting = RxStripCRC | RxEnable | RxPromisc | RxAllMulti; + } else if (dev->flags & IFF_ALLMULTI) + rx_cfg_setting = RxStripCRC | RxEnable | RxAllMulti; + else { + if (dev->mc_count) { + fill_multicast_tbl(dev->mc_count, dev->mc_list, + (u_char *)multicast_table); + } + rx_cfg_setting = RxStripCRC | RxEnable; + } + + /* Load MC table and Rx setting into the chip without interrupts. */ + save_flags(flags); + cli(); + SMC_SELECT_BANK(3); + outl(multicast_table[0], ioaddr + MULTICAST0); + outl(multicast_table[1], ioaddr + MULTICAST4); + SMC_SELECT_BANK(0); + outw(rx_cfg_setting, ioaddr + RCR); + SMC_SELECT_BANK(2); + restore_flags(flags); + + return; +} + +/*====================================================================== + + Senses when a card's config changes. Here, it's coax or TP. + +======================================================================*/ + +static int s9k_config(struct net_device *dev, struct ifmap *map) +{ + struct smc_private *smc = dev->priv; + if ((map->port != (u_char)(-1)) && (map->port != dev->if_port)) { + if (smc->cfg & CFG_MII_SELECT) + return -EOPNOTSUPP; + else if (map->port > 2) + return -EINVAL; + dev->if_port = map->port; + printk(KERN_INFO "%s: switched to %s port\n", + dev->name, if_names[dev->if_port]); + smc_reset(dev); + } + return 0; +} + +/*====================================================================== + + Reset the chip, reloading every register that might be corrupted. + +======================================================================*/ + +/* + Set transceiver type, perhaps to something other than what the user + specified in dev->if_port. +*/ +static void smc_set_xcvr(struct net_device *dev, int if_port) +{ + struct smc_private *smc = (struct smc_private *)dev->priv; + ioaddr_t ioaddr = dev->base_addr; + u_short saved_bank; + + saved_bank = inw(ioaddr + BANK_SELECT); + SMC_SELECT_BANK(1); + if (if_port == 2) { + outw(smc->cfg | CFG_AUI_SELECT, ioaddr + CONFIG); + if ((smc->manfid == MANFID_OSITECH) && + (smc->cardid != PRODID_OSITECH_SEVEN)) + set_bits(OSI_AUI_PWR, ioaddr - 0x10 + OSITECH_AUI_PWR); + smc->media_status = ((dev->if_port == 0) ? 0x0001 : 0x0002); + } else { + outw(smc->cfg, ioaddr + CONFIG); + if ((smc->manfid == MANFID_OSITECH) && + (smc->cardid != PRODID_OSITECH_SEVEN)) + mask_bits(~OSI_AUI_PWR, ioaddr - 0x10 + OSITECH_AUI_PWR); + smc->media_status = ((dev->if_port == 0) ? 0x0012 : 0x4001); + } + SMC_SELECT_BANK(saved_bank); +} + +static void smc_reset(struct net_device *dev) +{ + ioaddr_t ioaddr = dev->base_addr; + struct smc_private *smc = dev->priv; + int i; + + DEBUG(0, "%s: smc91c92 reset called.\n", dev->name); + + /* 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 succesfully 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); + + /* Reset the MMU */ + SMC_SELECT_BANK(2); + outw(MC_RESET, ioaddr + MMU_CMD); + outw(0, ioaddr + INTERRUPT); + + /* Re-enable the chip. */ + SMC_SELECT_BANK(0); + outw(((smc->cfg & CFG_MII_SELECT) ? 0 : TCR_MONCSN) | + TCR_ENABLE | TCR_PAD_EN | smc->duplex, ioaddr + TCR); + set_rx_mode(dev); + + if (smc->cfg & CFG_MII_SELECT) { + SMC_SELECT_BANK(3); + + /* Reset MII */ + mdio_write(dev, smc->phy_id, 0, 0x8000); + + /* Advertise 100F, 100H, 10F, 10H */ + mdio_write(dev, smc->phy_id, 4, 0x01e1); + + /* Restart MII autonegotiation */ + mdio_write(dev, smc->phy_id, 0, 0x0000); + mdio_write(dev, smc->phy_id, 0, 0x1200); + } + + /* Enable interrupts. */ + SMC_SELECT_BANK(2); + outw((IM_EPH_INT | IM_RX_OVRN_INT | IM_RCV_INT) << 8, + ioaddr + INTERRUPT); +} + +/*====================================================================== + + Media selection timer routine + +======================================================================*/ + +static void media_check(u_long arg) +{ + struct smc_private *smc = (struct smc_private *)(arg); + struct net_device *dev = &smc->dev; + ioaddr_t ioaddr = dev->base_addr; + u_short i, media, saved_bank; + u_short link; + + saved_bank = inw(ioaddr + BANK_SELECT); + + if (!netif_device_present(dev)) + goto reschedule; + + SMC_SELECT_BANK(2); + + /* need MC_RESET to keep the memory consistent. errata? */ + if (smc->rx_ovrn) { + outw(MC_RESET, ioaddr + MMU_CMD); + smc->rx_ovrn = 0; + } + i = inw(ioaddr + INTERRUPT); + SMC_SELECT_BANK(0); + media = inw(ioaddr + EPH) & EPH_LINK_OK; + SMC_SELECT_BANK(1); + media |= (inw(ioaddr + CONFIG) & CFG_AUI_SELECT) ? 2 : 1; + + /* 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) + printk(KERN_INFO "%s: interrupt(s) dropped!\n", dev->name); + smc_interrupt(dev->irq, smc, NULL); + smc->fast_poll = HZ; + } + if (smc->fast_poll) { + smc->fast_poll--; + smc->media.expires = jiffies + 1; + add_timer(&smc->media); + SMC_SELECT_BANK(saved_bank); + return; + } + + if (smc->cfg & CFG_MII_SELECT) { + if (smc->phy_id < 0) + goto reschedule; + + SMC_SELECT_BANK(3); + link = mdio_read(dev, smc->phy_id, 1); + if (!link || (link == 0xffff)) { + printk(KERN_INFO "%s: MII is missing!\n", dev->name); + smc->phy_id = -1; + goto reschedule; + } + + link &= 0x0004; + if (link != smc->link_status) { + u_short p = mdio_read(dev, smc->phy_id, 5); + printk(KERN_INFO "%s: %s link beat\n", dev->name, + (link) ? "found" : "lost"); + smc->duplex = (((p & 0x0100) || ((p & 0x1c0) == 0x40)) + ? TCR_FDUPLX : 0); + if (link) { + printk(KERN_INFO "%s: autonegotiation complete: " + "%sbaseT-%cD selected\n", dev->name, + ((p & 0x0180) ? "100" : "10"), + (smc->duplex ? 'F' : 'H')); + } + SMC_SELECT_BANK(0); + outw(inw(ioaddr + TCR) | smc->duplex, ioaddr + TCR); + smc->link_status = link; + } + goto reschedule; + } + + /* Ignore collisions unless we've had no rx's recently */ + if (jiffies - dev->last_rx > HZ) { + if (smc->tx_err || (smc->media_status & EPH_16COL)) + media |= EPH_16COL; + } + smc->tx_err = 0; + + if (media != smc->media_status) { + if ((media & smc->media_status & 1) && + ((smc->media_status ^ media) & EPH_LINK_OK)) + printk(KERN_INFO "%s: %s link beat\n", dev->name, + (smc->media_status & EPH_LINK_OK ? "lost" : "found")); + else if ((media & smc->media_status & 2) && + ((smc->media_status ^ media) & EPH_16COL)) + printk(KERN_INFO "%s: coax cable %s\n", dev->name, + (media & EPH_16COL ? "problem" : "ok")); + if (dev->if_port == 0) { + if (media & 1) { + if (media & EPH_LINK_OK) + printk(KERN_INFO "%s: flipped to 10baseT\n", + dev->name); + else + smc_set_xcvr(dev, 2); + } else { + if (media & EPH_16COL) + smc_set_xcvr(dev, 1); + else + printk(KERN_INFO "%s: flipped to 10base2\n", + dev->name); + } + } + smc->media_status = media; + } + +reschedule: + smc->media.expires = jiffies + HZ; + add_timer(&smc->media); + SMC_SELECT_BANK(saved_bank); +} + + +/*====================================================================*/ + +static int __init init_smc91c92_cs(void) +{ + servinfo_t serv; + DEBUG(0, "%s\n", version); + CardServices(GetCardServicesInfo, &serv); + if (serv.Revision != CS_RELEASE_CODE) { + printk(KERN_ERR + "smc91c92_cs: Card Services release does not match!\n"); + return -EINVAL; + } + register_pccard_driver(&dev_info, &smc91c92_attach, &smc91c92_detach); + return 0; +} + +static void __exit exit_smc91c92_cs(void) +{ + DEBUG(0, "smc91c92_cs: unloading\n"); + unregister_pccard_driver(&dev_info); + while (dev_list != NULL) + smc91c92_detach(dev_list); +} + +module_init(init_smc91c92_cs); +module_exit(exit_smc91c92_cs); diff --git a/linux/pcmcia-cs/clients/xirc2ps_cs.c b/linux/pcmcia-cs/clients/xirc2ps_cs.c new file mode 100644 index 0000000..0cf41d3 --- /dev/null +++ b/linux/pcmcia-cs/clients/xirc2ps_cs.c @@ -0,0 +1,2086 @@ +/* [xirc2ps_cs.c wk 03.11.99] (1.40 1999/11/18 00:06:03) + * Xircom CreditCard Ethernet Adapter IIps driver + * Xircom Realport 10/100 (RE-100) driver + * + * This driver supports various Xircom CreditCard Ethernet adapters + * including the CE2, CE IIps, RE-10, CEM28, CEM33, CE33, CEM56, + * CE3-100, CE3B, RE-100, REM10BT, and REM56G-100. + * + * 2000-09-24 <psheer@icon.co.za> The Xircom CE3B-100 may not + * autodetect the media properly. In this case use the + * if_port=1 (for 10BaseT) or if_port=4 (for 100BaseT) options + * to force the media type. + * + * Written originally by Werner Koch based on David Hinds' skeleton of the + * PCMCIA driver. + * + * Copyright (c) 1997,1998 Werner Koch (dd9jn) + * + * This driver 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. + * + * It 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + * + * ALTERNATIVELY, this driver may be distributed under the terms of + * the following license, in which case the provisions of this license + * are required INSTEAD OF the GNU General Public License. (This clause + * is necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/ptrace.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/in.h> +#include <linux/delay.h> +#include <asm/io.h> +#include <asm/system.h> +#include <asm/bitops.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/if_arp.h> +#include <linux/ioport.h> + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/cisreg.h> +#include <pcmcia/ciscode.h> + +#ifndef MANFID_COMPAQ + #define MANFID_COMPAQ 0x0138 + #define MANFID_COMPAQ2 0x0183 /* is this correct? */ +#endif + +#include <pcmcia/ds.h> + +/* Time in jiffies before concluding Tx hung */ +#define TX_TIMEOUT ((400*HZ)/1000) + +/**************** + * Some constants used to access the hardware + */ + +/* Register offsets and value constans */ +#define XIRCREG_CR 0 /* Command register (wr) */ +enum xirc_cr { + TransmitPacket = 0x01, + SoftReset = 0x02, + EnableIntr = 0x04, + ForceIntr = 0x08, + ClearTxFIFO = 0x10, + ClearRxOvrun = 0x20, + RestartTx = 0x40 +}; +#define XIRCREG_ESR 0 /* Ethernet status register (rd) */ +enum xirc_esr { + FullPktRcvd = 0x01, /* full packet in receive buffer */ + PktRejected = 0x04, /* a packet has been rejected */ + TxPktPend = 0x08, /* TX Packet Pending */ + IncorPolarity = 0x10, + MediaSelect = 0x20 /* set if TP, clear if AUI */ +}; +#define XIRCREG_PR 1 /* Page Register select */ +#define XIRCREG_EDP 4 /* Ethernet Data Port Register */ +#define XIRCREG_ISR 6 /* Ethernet Interrupt Status Register */ +enum xirc_isr { + TxBufOvr = 0x01, /* TX Buffer Overflow */ + PktTxed = 0x02, /* Packet Transmitted */ + MACIntr = 0x04, /* MAC Interrupt occurred */ + TxResGrant = 0x08, /* Tx Reservation Granted */ + RxFullPkt = 0x20, /* Rx Full Packet */ + RxPktRej = 0x40, /* Rx Packet Rejected */ + ForcedIntr= 0x80 /* Forced Interrupt */ +}; +#define XIRCREG1_IMR0 12 /* Ethernet Interrupt Mask Register (on page 1)*/ +#define XIRCREG1_IMR1 13 +#define XIRCREG0_TSO 8 /* Transmit Space Open Register (on page 0)*/ +#define XIRCREG0_TRS 10 /* Transmit reservation Size Register (page 0)*/ +#define XIRCREG0_DO 12 /* Data Offset Register (page 0) (wr) */ +#define XIRCREG0_RSR 12 /* Receive Status Register (page 0) (rd) */ +enum xirc_rsr { + PhyPkt = 0x01, /* set:physical packet, clear: multicast packet */ + BrdcstPkt = 0x02, /* set if it is a broadcast packet */ + PktTooLong = 0x04, /* set if packet length > 1518 */ + AlignErr = 0x10, /* incorrect CRC and last octet not complete */ + CRCErr = 0x20, /* incorrect CRC and last octet is complete */ + PktRxOk = 0x80 /* received ok */ +}; +#define XIRCREG0_PTR 13 /* packets transmitted register (rd) */ +#define XIRCREG0_RBC 14 /* receive byte count regsister (rd) */ +#define XIRCREG1_ECR 14 /* ethernet configurationn register */ +enum xirc_ecr { + FullDuplex = 0x04, /* enable full duplex mode */ + LongTPMode = 0x08, /* adjust for longer lengths of TP cable */ + DisablePolCor = 0x10,/* disable auto polarity correction */ + DisableLinkPulse = 0x20, /* disable link pulse generation */ + DisableAutoTx = 0x40, /* disable auto-transmit */ +}; +#define XIRCREG2_RBS 8 /* receive buffer start register */ +#define XIRCREG2_LED 10 /* LED Configuration register */ +/* values for the leds: Bits 2-0 for led 1 + * 0 disabled Bits 5-3 for led 2 + * 1 collision + * 2 noncollision + * 3 link_detected + * 4 incor_polarity + * 5 jabber + * 6 auto_assertion + * 7 rx_tx_activity + */ +#define XIRCREG2_MSR 12 /* Mohawk specific register */ + +#define XIRCREG4_GPR0 8 /* General Purpose Register 0 */ +#define XIRCREG4_GPR1 9 /* General Purpose Register 1 */ +#define XIRCREG2_GPR2 13 /* General Purpose Register 2 (page2!)*/ +#define XIRCREG4_BOV 10 /* Bonding Version Register */ +#define XIRCREG4_LMA 12 /* Local Memory Address Register */ +#define XIRCREG4_LMD 14 /* Local Memory Data Port */ +/* MAC register can only by accessed with 8 bit operations */ +#define XIRCREG40_CMD0 8 /* Command Register (wr) */ +enum xirc_cmd { /* Commands */ + Transmit = 0x01, + EnableRecv = 0x04, + DisableRecv = 0x08, + Abort = 0x10, + Online = 0x20, + IntrAck = 0x40, + Offline = 0x80 +}; +#define XIRCREG5_RHSA0 10 /* Rx Host Start Address */ +#define XIRCREG40_RXST0 9 /* Receive Status Register */ +#define XIRCREG40_TXST0 11 /* Transmit Status Register 0 */ +#define XIRCREG40_TXST1 12 /* Transmit Status Register 10 */ +#define XIRCREG40_RMASK0 13 /* Receive Mask Register */ +#define XIRCREG40_TMASK0 14 /* Transmit Mask Register 0 */ +#define XIRCREG40_TMASK1 15 /* Transmit Mask Register 0 */ +#define XIRCREG42_SWC0 8 /* Software Configuration 0 */ +#define XIRCREG42_SWC1 9 /* Software Configuration 1 */ +#define XIRCREG42_BOC 10 /* Back-Off Configuration */ +#define XIRCREG44_TDR0 8 /* Time Domain Reflectometry 0 */ +#define XIRCREG44_TDR1 9 /* Time Domain Reflectometry 1 */ +#define XIRCREG44_RXBC_LO 10 /* Rx Byte Count 0 (rd) */ +#define XIRCREG44_RXBC_HI 11 /* Rx Byte Count 1 (rd) */ +#define XIRCREG45_REV 15 /* Revision Register (rd) */ +#define XIRCREG50_IA 8 /* Individual Address (8-13) */ + +static char *if_names[] = { "Auto", "10BaseT", "10Base2", "AUI", "100BaseT" }; + +/**************** + * 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"); +#define DEBUG(n, args...) if (pc_debug>(n)) printk(KDBG_XIRC args) +#else +#define DEBUG(n, args...) +#endif +static char *version = +"xirc2ps_cs.c 1.31 1998/12/09 19:32:55 (dd9jn+kvh)"; + /* !--- CVS revision */ +#define KDBG_XIRC KERN_DEBUG "xirc2ps_cs: " +#define KERR_XIRC KERN_ERR "xirc2ps_cs: " +#define KWRN_XIRC KERN_WARNING "xirc2ps_cs: " +#define KNOT_XIRC KERN_NOTICE "xirc2ps_cs: " +#define KINF_XIRC KERN_INFO "xirc2ps_cs: " + +/* card types */ +#define XIR_UNKNOWN 0 /* unknown: not supported */ +#define XIR_CE 1 /* (prodid 1) different hardware: not supported */ +#define XIR_CE2 2 /* (prodid 2) */ +#define XIR_CE3 3 /* (prodid 3) */ +#define XIR_CEM 4 /* (prodid 1) different hardware: not supported */ +#define XIR_CEM2 5 /* (prodid 2) */ +#define XIR_CEM3 6 /* (prodid 3) */ +#define XIR_CEM33 7 /* (prodid 4) */ +#define XIR_CEM56M 8 /* (prodid 5) */ +#define XIR_CEM56 9 /* (prodid 6) */ +#define XIR_CM28 10 /* (prodid 3) modem only: not supported here */ +#define XIR_CM33 11 /* (prodid 4) modem only: not supported here */ +#define XIR_CM56 12 /* (prodid 5) modem only: not supported here */ +#define XIR_CG 13 /* (prodid 1) GSM modem only: not supported */ +#define XIR_CBE 14 /* (prodid 1) cardbus ethernet: not supported */ +/*====================================================================*/ + +/* Module parameters */ + +MODULE_DESCRIPTION("Xircom PCMCIA ethernet driver"); +MODULE_LICENSE("Dual MPL/GPL"); + +#define INT_MODULE_PARM(n, v) static int n = v; MODULE_PARM(n, "i") + +static int irq_list[4] = { -1 }; +MODULE_PARM(irq_list, "1-4i"); +INT_MODULE_PARM(irq_mask, 0xdeb8); +INT_MODULE_PARM(if_port, 0); +INT_MODULE_PARM(full_duplex, 0); +INT_MODULE_PARM(do_sound, 1); +INT_MODULE_PARM(lockup_hack, 0); /* anti lockup hack */ + +/*====================================================================*/ + +/* We do not process more than these number of bytes during one + * interrupt. (Of course we receive complete packets, so this is not + * an exact value). + * Something between 2000..22000; first value gives best interrupt latency, + * the second enables the usage of the complete on-chip buffer. We use the + * high value as the initial value. + */ +static unsigned maxrx_bytes = 22000; + +/* MII management prototypes */ +static void mii_idle(ioaddr_t ioaddr); +static void mii_putbit(ioaddr_t ioaddr, unsigned data); +static int mii_getbit(ioaddr_t ioaddr); +static void mii_wbits(ioaddr_t ioaddr, unsigned data, int len); +static unsigned mii_rd(ioaddr_t ioaddr, u_char phyaddr, u_char phyreg); +static void mii_wr(ioaddr_t ioaddr, u_char phyaddr, u_char phyreg, + unsigned data, int len); + +/* + * 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 event handler. + */ + +static int has_ce2_string(dev_link_t * link); +static void xirc2ps_config(dev_link_t * link); +static void xirc2ps_release(u_long arg); +static int xirc2ps_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 *xirc2ps_attach(void); +static void xirc2ps_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 xirc2ps_interrupt(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 = "xirc2ps_cs"; + +/**************** + * A linked list of "instances" of the 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. + */ + +typedef struct local_info_t { + dev_link_t link; + struct net_device dev; + dev_node_t node; + struct net_device_stats stats; + int card_type; + int probe_port; + int silicon; /* silicon revision. 0=old CE2, 1=Scipper, 4=Mohawk */ + int mohawk; /* a CE3 type card */ + int dingo; /* a CEM56 type card */ + int new_mii; /* has full 10baseT/100baseT MII */ + int modem; /* is a multi function card (i.e with a modem) */ + caddr_t dingo_ccr; /* only used for CEM56 cards */ + unsigned last_ptr_value; /* last packets transmitted value */ + const char *manf_str; +} local_info_t; + +/**************** + * Some more prototypes + */ +static int do_start_xmit(struct sk_buff *skb, struct net_device *dev); +static void do_tx_timeout(struct net_device *dev); +static struct net_device_stats *do_get_stats(struct net_device *dev); +static void set_addresses(struct net_device *dev); +static void set_multicast_list(struct net_device *dev); +static int set_card_type(dev_link_t *link, const void *s); +static int do_config(struct net_device *dev, struct ifmap *map); +static int do_open(struct net_device *dev); +static int do_ioctl(struct net_device *dev, struct ifreq *rq, int cmd); +static void hardreset(struct net_device *dev); +static void do_reset(struct net_device *dev, int full); +static int init_mii(struct net_device *dev); +static void do_powerdown(struct net_device *dev); +static int do_stop(struct net_device *dev); + + +/*=============== Helper functions =========================*/ +static void +flush_stale_links(void) +{ + dev_link_t *link, *next; + for (link = dev_list; link; link = next) { + next = link->next; + if (link->state & DEV_STALE_LINK) + xirc2ps_detach(link); + } +} + +static void +cs_error(client_handle_t handle, int func, int ret) +{ + error_info_t err = { func, ret }; + CardServices(ReportError, handle, &err); +} + +static int +get_tuple_data(int fn, client_handle_t handle, tuple_t *tuple) +{ + int err; + + if ((err=CardServices(fn, handle, tuple))) + return err; + return CardServices(GetTupleData, handle, tuple); +} + +static int +get_tuple(int fn, client_handle_t handle, tuple_t *tuple, cisparse_t *parse) +{ + int err; + + if ((err=get_tuple_data(fn, handle, tuple))) + return err; + return CardServices(ParseTuple, handle, tuple, parse); +} + +#define first_tuple(a, b, c) get_tuple(GetFirstTuple, a, b, c) +#define next_tuple(a, b, c) get_tuple(GetNextTuple, a, b, c) + +#define SelectPage(pgnr) outb((pgnr), ioaddr + XIRCREG_PR) +#define GetByte(reg) ((unsigned)inb(ioaddr + (reg))) +#define GetWord(reg) ((unsigned)inw(ioaddr + (reg))) +#define PutByte(reg,value) outb((value), ioaddr+(reg)) +#define PutWord(reg,value) outw((value), ioaddr+(reg)) + +static void +busy_loop(u_long len) +{ + if (in_interrupt()) { + u_long timeout = jiffies + len; + u_long flags; + save_flags(flags); + sti(); + while (timeout >= jiffies) + ; + restore_flags(flags); + } else { + __set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(len); + } +} + + +/*====== Functions used for debugging =================================*/ +#if defined(PCMCIA_DEBUG) && 0 /* reading regs may change system status */ +static void +PrintRegisters(struct net_device *dev) +{ + ioaddr_t ioaddr = dev->base_addr; + + if (pc_debug > 1) { + int i, page; + + printk(KDBG_XIRC "Register common: "); + for (i = 0; i < 8; i++) + printk(" %2.2x", GetByte(i)); + printk("\n"); + for (page = 0; page <= 8; page++) { + printk(KDBG_XIRC "Register page %2x: ", page); + SelectPage(page); + for (i = 8; i < 16; i++) + printk(" %2.2x", GetByte(i)); + printk("\n"); + } + for (page=0x40 ; page <= 0x5f; page++) { + if (page == 0x43 || (page >= 0x46 && page <= 0x4f) + || (page >= 0x51 && page <=0x5e)) + continue; + printk(KDBG_XIRC "Register page %2x: ", page); + SelectPage(page); + for (i = 8; i < 16; i++) + printk(" %2.2x", GetByte(i)); + printk("\n"); + } + } +} +#endif /* PCMCIA_DEBUG */ + +/*============== MII Management functions ===============*/ + +/**************** + * Turn around for read + */ +static void +mii_idle(ioaddr_t ioaddr) +{ + PutByte(XIRCREG2_GPR2, 0x04|0); /* drive MDCK low */ + udelay(1); + PutByte(XIRCREG2_GPR2, 0x04|1); /* and drive MDCK high */ + udelay(1); +} + +/**************** + * Write a bit to MDI/O + */ +static void +mii_putbit(ioaddr_t ioaddr, unsigned data) +{ + #if 1 + if (data) { + PutByte(XIRCREG2_GPR2, 0x0c|2|0); /* set MDIO */ + udelay(1); + PutByte(XIRCREG2_GPR2, 0x0c|2|1); /* and drive MDCK high */ + udelay(1); + } else { + PutByte(XIRCREG2_GPR2, 0x0c|0|0); /* clear MDIO */ + udelay(1); + PutByte(XIRCREG2_GPR2, 0x0c|0|1); /* and drive MDCK high */ + udelay(1); + } + #else + if (data) { + PutWord(XIRCREG2_GPR2-1, 0x0e0e); + udelay(1); + PutWord(XIRCREG2_GPR2-1, 0x0f0f); + udelay(1); + } else { + PutWord(XIRCREG2_GPR2-1, 0x0c0c); + udelay(1); + PutWord(XIRCREG2_GPR2-1, 0x0d0d); + udelay(1); + } + #endif +} + +/**************** + * Get a bit from MDI/O + */ +static int +mii_getbit(ioaddr_t ioaddr) +{ + unsigned d; + + PutByte(XIRCREG2_GPR2, 4|0); /* drive MDCK low */ + udelay(1); + d = GetByte(XIRCREG2_GPR2); /* read MDIO */ + PutByte(XIRCREG2_GPR2, 4|1); /* drive MDCK high again */ + udelay(1); + return d & 0x20; /* read MDIO */ +} + +static void +mii_wbits(ioaddr_t ioaddr, unsigned data, int len) +{ + unsigned m = 1 << (len-1); + for (; m; m >>= 1) + mii_putbit(ioaddr, data & m); +} + +static unsigned +mii_rd(ioaddr_t ioaddr, u_char phyaddr, u_char phyreg) +{ + int i; + unsigned data=0, m; + + SelectPage(2); + for (i=0; i < 32; i++) /* 32 bit preamble */ + mii_putbit(ioaddr, 1); + mii_wbits(ioaddr, 0x06, 4); /* Start and opcode for read */ + mii_wbits(ioaddr, phyaddr, 5); /* PHY address to be accessed */ + mii_wbits(ioaddr, phyreg, 5); /* PHY register to read */ + mii_idle(ioaddr); /* turn around */ + mii_getbit(ioaddr); + + for (m = 1<<15; m; m >>= 1) + if (mii_getbit(ioaddr)) + data |= m; + mii_idle(ioaddr); + return data; +} + +static void +mii_wr(ioaddr_t ioaddr, u_char phyaddr, u_char phyreg, unsigned data, int len) +{ + int i; + + SelectPage(2); + for (i=0; i < 32; i++) /* 32 bit preamble */ + mii_putbit(ioaddr, 1); + mii_wbits(ioaddr, 0x05, 4); /* Start and opcode for write */ + mii_wbits(ioaddr, phyaddr, 5); /* PHY address to be accessed */ + mii_wbits(ioaddr, phyreg, 5); /* PHY Register to write */ + mii_putbit(ioaddr, 1); /* turn around */ + mii_putbit(ioaddr, 0); + mii_wbits(ioaddr, data, len); /* And write the data */ + mii_idle(ioaddr); +} + +/*============= Main bulk of functions =========================*/ + +/**************** + * xirc2ps_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 * +xirc2ps_attach(void) +{ + client_reg_t client_reg; + dev_link_t *link; + struct net_device *dev; + local_info_t *local; + int err; + + DEBUG(0, "attach()\n"); + flush_stale_links(); + + /* Allocate the device structure */ + local = kmalloc(sizeof(*local), GFP_KERNEL); + if (!local) return NULL; + memset(local, 0, sizeof(*local)); + link = &local->link; dev = &local->dev; + link->priv = dev->priv = local; + + init_timer(&link->release); + link->release.function = &xirc2ps_release; + link->release.data = (u_long) link; + + /* 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; + link->irq.Handler = xirc2ps_interrupt; + link->irq.Instance = dev; + + /* Fill in card specific entries */ + dev->hard_start_xmit = &do_start_xmit; + dev->set_config = &do_config; + dev->get_stats = &do_get_stats; + dev->do_ioctl = &do_ioctl; + dev->set_multicast_list = &set_multicast_list; + ether_setup(dev); + init_dev_name(dev, local->node); + dev->open = &do_open; + dev->stop = &do_stop; +#ifdef HAVE_TX_TIMEOUT + dev->tx_timeout = do_tx_timeout; + dev->watchdog_timeo = TX_TIMEOUT; +#endif + + /* 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 = &xirc2ps_event; + client_reg.Version = 0x0210; + client_reg.event_callback_args.client_data = link; + if ((err = CardServices(RegisterClient, &link->handle, &client_reg))) { + cs_error(link->handle, RegisterClient, err); + xirc2ps_detach(link); + return NULL; + } + + return link; +} /* xirc2ps_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 +xirc2ps_detach(dev_link_t * link) +{ + local_info_t *local = link->priv; + dev_link_t **linkp; + + DEBUG(0, "detach(0x%p)\n", link); + + /* Locate device structure */ + for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next) + if (*linkp == link) + break; + if (!*linkp) { + DEBUG(0, "detach(0x%p): dev_link lost\n", link); + 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(). + */ + del_timer(&link->release); + if (link->state & DEV_CONFIG) { + DEBUG(0, "detach postponed, '%s' still locked\n", + link->dev->dev_name); + link->state |= DEV_STALE_LINK; + return; + } + + /* Break the link with Card Services */ + if (link->handle) + CardServices(DeregisterClient, link->handle); + + /* Unlink device structure, free it */ + *linkp = link->next; + if (link->dev) + unregister_netdev(&local->dev); + kfree(local); + +} /* xirc2ps_detach */ + +/**************** + * Detect the type of the card. s is the buffer with the data of tuple 0x20 + * Returns: 0 := not supported + * mediaid=11 and prodid=47 + * Media-Id bits: + * Ethernet 0x01 + * Tokenring 0x02 + * Arcnet 0x04 + * Wireless 0x08 + * Modem 0x10 + * GSM only 0x20 + * Prod-Id bits: + * Pocket 0x10 + * External 0x20 + * Creditcard 0x40 + * Cardbus 0x80 + * + */ +static int +set_card_type(dev_link_t *link, const void *s) +{ + local_info_t *local = link->priv; + #ifdef PCMCIA_DEBUG + unsigned cisrev = ((const unsigned char *)s)[2]; + #endif + unsigned mediaid= ((const unsigned char *)s)[3]; + unsigned prodid = ((const unsigned char *)s)[4]; + + DEBUG(0, "cisrev=%02x mediaid=%02x prodid=%02x\n", + cisrev, mediaid, prodid); + + local->mohawk = 0; + local->dingo = 0; + local->modem = 0; + local->card_type = XIR_UNKNOWN; + if (!(prodid & 0x40)) { + printk(KNOT_XIRC "Ooops: Not a creditcard\n"); + return 0; + } + if (!(mediaid & 0x01)) { + printk(KNOT_XIRC "Not an Ethernet card\n"); + return 0; + } + if (mediaid & 0x10) { + local->modem = 1; + switch(prodid & 15) { + case 1: local->card_type = XIR_CEM ; break; + case 2: local->card_type = XIR_CEM2 ; break; + case 3: local->card_type = XIR_CEM3 ; break; + case 4: local->card_type = XIR_CEM33 ; break; + case 5: local->card_type = XIR_CEM56M; + local->mohawk = 1; + break; + case 6: + case 7: /* 7 is the RealPort 10/56 */ + local->card_type = XIR_CEM56 ; + local->mohawk = 1; + local->dingo = 1; + break; + } + } else { + switch(prodid & 15) { + case 1: local->card_type = has_ce2_string(link)? XIR_CE2 : XIR_CE ; + break; + case 15: + case 2: local->card_type = XIR_CE2; break; + case 3: local->card_type = XIR_CE3; + local->mohawk = 1; + break; + } + } + if (local->card_type == XIR_CE || local->card_type == XIR_CEM) { + printk(KNOT_XIRC "Sorry, this is an old CE card\n"); + return 0; + } + if (local->card_type == XIR_UNKNOWN) + printk(KNOT_XIRC "unknown card (mediaid=%02x prodid=%02x)\n", + mediaid, prodid); + + return 1; +} + +/**************** + * There are some CE2 cards out which claim to be a CE card. + * This function looks for a "CE2" in the 3rd version field. + * Returns: true if this is a CE2 + */ +static int +has_ce2_string(dev_link_t * link) +{ + client_handle_t handle = link->handle; + tuple_t tuple; + cisparse_t parse; + u_char buf[256]; + + tuple.Attributes = 0; + tuple.TupleData = buf; + tuple.TupleDataMax = 254; + tuple.TupleOffset = 0; + tuple.DesiredTuple = CISTPL_VERS_1; + if (!first_tuple(handle, &tuple, &parse) && parse.version_1.ns > 2) { + if (strstr(parse.version_1.str + parse.version_1.ofs[2], "CE2")) + return 1; + } + return 0; +} + +/**************** + * xirc2ps_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 +xirc2ps_config(dev_link_t * link) +{ + client_handle_t handle = link->handle; + local_info_t *local = link->priv; + struct net_device *dev = &local->dev; + tuple_t tuple; + cisparse_t parse; + ioaddr_t ioaddr; + int err, i; + u_char buf[64]; + cistpl_lan_node_id_t *node_id = (cistpl_lan_node_id_t*)parse.funce.data; + cistpl_cftable_entry_t *cf = &parse.cftable_entry; + + local->dingo_ccr = 0; + + DEBUG(0, "config(0x%p)\n", link); + + /* + * This reads the card's CONFIG tuple to find its configuration + * registers. + */ + tuple.Attributes = 0; + tuple.TupleData = buf; + tuple.TupleDataMax = 64; + tuple.TupleOffset = 0; + + /* Is this a valid card */ + tuple.DesiredTuple = CISTPL_MANFID; + if ((err=first_tuple(handle, &tuple, &parse))) { + printk(KNOT_XIRC "manfid not found in CIS\n"); + goto failure; + } + + switch(parse.manfid.manf) { + case MANFID_XIRCOM: + local->manf_str = "Xircom"; + break; + case MANFID_ACCTON: + local->manf_str = "Accton"; + break; + case MANFID_COMPAQ: + case MANFID_COMPAQ2: + local->manf_str = "Compaq"; + break; + case MANFID_INTEL: + local->manf_str = "Intel"; + break; + case MANFID_TOSHIBA: + local->manf_str = "Toshiba"; + break; + default: + printk(KNOT_XIRC "Unknown Card Manufacturer ID: 0x%04x\n", + (unsigned)parse.manfid.manf); + goto failure; + } + DEBUG(0, "found %s card\n", local->manf_str); + + if (!set_card_type(link, buf)) { + printk(KNOT_XIRC "this card is not supported\n"); + goto failure; + } + + /* get configuration stuff */ + tuple.DesiredTuple = CISTPL_CONFIG; + if ((err=first_tuple(handle, &tuple, &parse))) + goto cis_error; + link->conf.ConfigBase = parse.config.base; + link->conf.Present = parse.config.rmask[0]; + + /* get the ethernet address from the CIS */ + tuple.DesiredTuple = CISTPL_FUNCE; + for (err = first_tuple(handle, &tuple, &parse); !err; + err = next_tuple(handle, &tuple, &parse)) { + /* Once I saw two CISTPL_FUNCE_LAN_NODE_ID entries: + * the first one with a length of zero the second correct - + * so I skip all entries with length 0 */ + if (parse.funce.type == CISTPL_FUNCE_LAN_NODE_ID + && ((cistpl_lan_node_id_t *)parse.funce.data)->nb) + break; + } + if (err) { /* not found: try to get the node-id from tuple 0x89 */ + tuple.DesiredTuple = 0x89; /* data layout looks like tuple 0x22 */ + if (!(err = get_tuple_data(GetFirstTuple, handle, &tuple))) { + if (tuple.TupleDataLen == 8 && *buf == CISTPL_FUNCE_LAN_NODE_ID) + memcpy(&parse, buf, 8); + else + err = -1; + } + } + if (err) { /* another try (James Lehmer's CE2 version 4.1)*/ + tuple.DesiredTuple = CISTPL_FUNCE; + for (err = first_tuple(handle, &tuple, &parse); !err; + err = next_tuple(handle, &tuple, &parse)) { + if (parse.funce.type == 0x02 && parse.funce.data[0] == 1 + && parse.funce.data[1] == 6 && tuple.TupleDataLen == 13) { + buf[1] = 4; + memcpy(&parse, buf+1, 8); + break; + } + } + } + if (err) { + printk(KNOT_XIRC "node-id not found in CIS\n"); + goto failure; + } + node_id = (cistpl_lan_node_id_t *)parse.funce.data; + if (node_id->nb != 6) { + printk(KNOT_XIRC "malformed node-id in CIS\n"); + goto failure; + } + for (i=0; i < 6; i++) + dev->dev_addr[i] = node_id->id[i]; + + /* Configure card */ + link->state |= DEV_CONFIG; + + link->io.IOAddrLines =10; + link->io.Attributes1 = IO_DATA_PATH_WIDTH_16; + link->irq.Attributes = IRQ_HANDLE_PRESENT; + link->irq.IRQInfo1 = IRQ_INFO2_VALID | IRQ_LEVEL_ID; + if (irq_list[0] == -1) + link->irq.IRQInfo2 = irq_mask; + else { + for (i = 0; i < 4; i++) + link->irq.IRQInfo2 |= 1 << irq_list[i]; + } + if (local->modem) { + int pass; + + if (do_sound) { + link->conf.Attributes |= CONF_ENABLE_SPKR; + link->conf.Status |= CCSR_AUDIO_ENA; + } + link->irq.Attributes |= IRQ_TYPE_DYNAMIC_SHARING|IRQ_FIRST_SHARED ; + link->io.NumPorts2 = 8; + link->io.Attributes2 = IO_DATA_PATH_WIDTH_8; + if (local->dingo) { + /* Take the Modem IO port from the CIS and scan for a free + * Ethernet port */ + link->io.NumPorts1 = 16; /* no Mako stuff anymore */ + tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY; + for (err = first_tuple(handle, &tuple, &parse); !err; + err = next_tuple(handle, &tuple, &parse)) { + if (cf->io.nwin > 0 && (cf->io.win[0].base & 0xf) == 8) { + for (ioaddr = 0x300; ioaddr < 0x400; ioaddr += 0x10) { + link->conf.ConfigIndex = cf->index ; + link->io.BasePort2 = cf->io.win[0].base; + link->io.BasePort1 = ioaddr; + if (!(err=CardServices(RequestIO, link->handle, + &link->io))) + goto port_found; + } + } + } + } else { + link->io.NumPorts1 = 18; + /* We do 2 passes here: The first one uses the regular mapping and + * the second tries again, thereby considering that the 32 ports are + * mirrored every 32 bytes. Actually we use a mirrored port for + * the Mako if (on the first pass) the COR bit 5 is set. + */ + for (pass=0; pass < 2; pass++) { + tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY; + for (err = first_tuple(handle, &tuple, &parse); !err; + err = next_tuple(handle, &tuple, &parse)){ + if (cf->io.nwin > 0 && (cf->io.win[0].base & 0xf) == 8){ + link->conf.ConfigIndex = cf->index ; + link->io.BasePort2 = cf->io.win[0].base; + link->io.BasePort1 = link->io.BasePort2 + + (pass ? (cf->index & 0x20 ? -24:8) + : (cf->index & 0x20 ? 8:-24)); + if (!(err=CardServices(RequestIO, link->handle, + &link->io))) + goto port_found; + } + } + } + /* if special option: + * try to configure as Ethernet only. + * .... */ + } + printk(KNOT_XIRC "no ports available\n"); + } else { + link->irq.Attributes |= IRQ_TYPE_EXCLUSIVE; + link->io.NumPorts1 = 16; + for (ioaddr = 0x300; ioaddr < 0x400; ioaddr += 0x10) { + link->io.BasePort1 = ioaddr; + if (!(err=CardServices(RequestIO, link->handle, &link->io))) + goto port_found; + } + link->io.BasePort1 = 0; /* let CS decide */ + if ((err=CardServices(RequestIO, link->handle, &link->io))) { + cs_error(link->handle, RequestIO, err); + goto config_error; + } + } + port_found: + if (err) + goto config_error; + + /**************** + * Now allocate an interrupt line. Note that this does not + * actually assign a handler to the interrupt. + */ + if ((err=CardServices(RequestIRQ, link->handle, &link->irq))) { + cs_error(link->handle, RequestIRQ, err); + goto config_error; + } + + /**************** + * This actually configures the PCMCIA socket -- setting up + * the I/O windows and the interrupt mapping. + */ + if ((err=CardServices(RequestConfiguration, + link->handle, &link->conf))) { + cs_error(link->handle, RequestConfiguration, err); + goto config_error; + } + + if (local->dingo) { + conf_reg_t reg; + win_req_t req; + memreq_t mem; + + /* Reset the modem's BAR to the correct value + * This is necessary because in the RequestConfiguration call, + * the base address of the ethernet port (BasePort1) is written + * to the BAR registers of the modem. + */ + reg.Action = CS_WRITE; + reg.Offset = CISREG_IOBASE_0; + reg.Value = link->io.BasePort2 & 0xff; + if ((err = CardServices(AccessConfigurationRegister, link->handle, + ®))) { + cs_error(link->handle, AccessConfigurationRegister, err); + goto config_error; + } + reg.Action = CS_WRITE; + reg.Offset = CISREG_IOBASE_1; + reg.Value = (link->io.BasePort2 >> 8) & 0xff; + if ((err = CardServices(AccessConfigurationRegister, link->handle, + ®))) { + cs_error(link->handle, AccessConfigurationRegister, err); + goto config_error; + } + + /* There is no config entry for the Ethernet part which + * is at 0x0800. So we allocate a window into the attribute + * memory and write direct to the CIS registers + */ + req.Attributes = WIN_DATA_WIDTH_8|WIN_MEMORY_TYPE_AM|WIN_ENABLE; + req.Base = req.Size = 0; + req.AccessSpeed = 0; + link->win = (window_handle_t)link->handle; + if ((err = CardServices(RequestWindow, &link->win, &req))) { + cs_error(link->handle, RequestWindow, err); + goto config_error; + } + local->dingo_ccr = ioremap(req.Base,0x1000) + 0x0800; + mem.CardOffset = 0x0; + mem.Page = 0; + if ((err = CardServices(MapMemPage, link->win, &mem))) { + cs_error(link->handle, MapMemPage, err); + goto config_error; + } + + /* Setup the CCRs; there are no infos in the CIS about the Ethernet + * part. + */ + writeb(0x47, local->dingo_ccr + CISREG_COR); + ioaddr = link->io.BasePort1; + writeb(ioaddr & 0xff , local->dingo_ccr + CISREG_IOBASE_0); + writeb((ioaddr >> 8)&0xff , local->dingo_ccr + CISREG_IOBASE_1); + + #if 0 + { + u_char tmp; + printk(KERN_INFO "ECOR:"); + for (i=0; i < 7; i++) { + tmp = readb(local->dingo_ccr + i*2); + printk(" %02x", tmp); + } + printk("\n"); + printk(KERN_INFO "DCOR:"); + for (i=0; i < 4; i++) { + tmp = readb(local->dingo_ccr + 0x20 + i*2); + printk(" %02x", tmp); + } + printk("\n"); + printk(KERN_INFO "SCOR:"); + for (i=0; i < 10; i++) { + tmp = readb(local->dingo_ccr + 0x40 + i*2); + printk(" %02x", tmp); + } + printk("\n"); + } + #endif + + writeb(0x01, local->dingo_ccr + 0x20); + writeb(0x0c, local->dingo_ccr + 0x22); + writeb(0x00, local->dingo_ccr + 0x24); + writeb(0x00, local->dingo_ccr + 0x26); + writeb(0x00, local->dingo_ccr + 0x28); + } + + /* The if_port symbol can be set when the module is loaded */ + local->probe_port=0; + if (!if_port) { + local->probe_port = dev->if_port = 1; + } else if ((if_port >= 1 && if_port <= 2) || + (local->mohawk && if_port==4)) + dev->if_port = if_port; + else + printk(KNOT_XIRC "invalid if_port requested\n"); + + /* we can now register the device with the net subsystem */ + dev->irq = link->irq.AssignedIRQ; + dev->base_addr = link->io.BasePort1; + if ((err=register_netdev(dev))) { + printk(KNOT_XIRC "register_netdev() failed\n"); + goto config_error; + } + + copy_dev_name(local->node, dev); + link->dev = &local->node; + link->state &= ~DEV_CONFIG_PENDING; + + if (local->dingo) + do_reset(dev, 1); /* a kludge to make the cem56 work */ + + /* give some infos about the hardware */ + printk(KERN_INFO "%s: %s: port %#3lx, irq %d, hwaddr", + dev->name, local->manf_str,(u_long)dev->base_addr, (int)dev->irq); + for (i = 0; i < 6; i++) + printk("%c%02X", i?':':' ', dev->dev_addr[i]); + printk("\n"); + + return; + + config_error: + link->state &= ~DEV_CONFIG_PENDING; + xirc2ps_release((u_long)link); + return; + + cis_error: + printk(KNOT_XIRC "unable to parse CIS\n"); + failure: + link->state &= ~DEV_CONFIG_PENDING; +} /* xirc2ps_config */ + +/**************** + * After a card is removed, xirc2ps_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 +xirc2ps_release(u_long arg) +{ + dev_link_t *link = (dev_link_t *) arg; + local_info_t *local = link->priv; + struct net_device *dev = &local->dev; + + DEBUG(0, "release(0x%p)\n", link); + + /* + * If the device is currently in use, we won't release until it + * is actually closed. + */ + if (link->open) { + DEBUG(0, "release postponed, '%s' " + "still open\n", link->dev->dev_name); + link->state |= DEV_STALE_CONFIG; + return; + } + + if (link->win) { + local_info_t *local = dev->priv; + if (local->dingo) + iounmap(local->dingo_ccr - 0x0800); + CardServices(ReleaseWindow, link->win); + } + CardServices(ReleaseConfiguration, link->handle); + CardServices(ReleaseIO, link->handle, &link->io); + CardServices(ReleaseIRQ, link->handle, &link->irq); + link->state &= ~DEV_CONFIG; + +} /* xirc2ps_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 +xirc2ps_event(event_t event, int priority, + event_callback_args_t * args) +{ + dev_link_t *link = args->client_data; + local_info_t *lp = link->priv; + struct net_device *dev = &lp->dev; + + DEBUG(0, "event(%d)\n", (int)event); + + switch (event) { + case CS_EVENT_REGISTRATION_COMPLETE: + DEBUG(0, "registration complete\n"); + break; + case CS_EVENT_CARD_REMOVAL: + link->state &= ~DEV_PRESENT; + if (link->state & DEV_CONFIG) { + netif_device_detach(dev); + mod_timer(&link->release, jiffies + HZ/20); + } + break; + case CS_EVENT_CARD_INSERTION: + link->state |= DEV_PRESENT | DEV_CONFIG_PENDING; + xirc2ps_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) { + netif_device_detach(dev); + do_powerdown(dev); + } + 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) { + do_reset(dev,1); + netif_device_attach(dev); + } + } + break; + } + return 0; +} /* xirc2ps_event */ + +/*====================================================================*/ + +/**************** + * This is the Interrupt service route. + */ +static void +xirc2ps_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct net_device *dev = (struct net_device *)dev_id; + local_info_t *lp = dev->priv; + ioaddr_t ioaddr; + u_char saved_page; + unsigned bytes_rcvd; + unsigned int_status, eth_status, rx_status, tx_status; + unsigned rsr, pktlen; + ulong start_ticks = jiffies; /* fixme: jiffies rollover every 497 days + * is this something to worry about? + * -- on a laptop? + */ + + if (!netif_device_present(dev)) + return; + + ioaddr = dev->base_addr; + if (lp->mohawk) { /* must disable the interrupt */ + PutByte(XIRCREG_CR, 0); + } + + DEBUG(6, "%s: interrupt %d at %#x.\n", dev->name, irq, ioaddr); + + saved_page = GetByte(XIRCREG_PR); + /* Read the ISR to see whats the cause for the interrupt. + * This also clears the interrupt flags on CE2 cards + */ + int_status = GetByte(XIRCREG_ISR); + bytes_rcvd = 0; + loop_entry: + if (int_status == 0xff) { /* card may be ejected */ + DEBUG(3, "%s: interrupt %d for dead card\n", dev->name, irq); + goto leave; + } + eth_status = GetByte(XIRCREG_ESR); + + SelectPage(0x40); + rx_status = GetByte(XIRCREG40_RXST0); + PutByte(XIRCREG40_RXST0, (~rx_status & 0xff)); + tx_status = GetByte(XIRCREG40_TXST0); + tx_status |= GetByte(XIRCREG40_TXST1) << 8; + PutByte(XIRCREG40_TXST0, 0); + PutByte(XIRCREG40_TXST1, 0); + + DEBUG(3, "%s: ISR=%#2.2x ESR=%#2.2x RSR=%#2.2x TSR=%#4.4x\n", + dev->name, int_status, eth_status, rx_status, tx_status); + + /***** receive section ******/ + SelectPage(0); + while (eth_status & FullPktRcvd) { + rsr = GetByte(XIRCREG0_RSR); + if (bytes_rcvd > maxrx_bytes && (rsr & PktRxOk)) { + /* too many bytes received during this int, drop the rest of the + * packets */ + lp->stats.rx_dropped++; + DEBUG(2, "%s: RX drop, too much done\n", dev->name); + } else if (rsr & PktRxOk) { + struct sk_buff *skb; + + pktlen = GetWord(XIRCREG0_RBC); + bytes_rcvd += pktlen; + + DEBUG(5, "rsr=%#02x packet_length=%u\n", rsr, pktlen); + + skb = dev_alloc_skb(pktlen+3); /* 1 extra so we can use insw */ + if (!skb) { + printk(KNOT_XIRC "low memory, packet dropped (size=%u)\n", + pktlen); + lp->stats.rx_dropped++; + } else { /* okay get the packet */ + skb_reserve(skb, 2); + if (lp->silicon == 0 ) { /* work around a hardware bug */ + unsigned rhsa; /* receive start address */ + + SelectPage(5); + rhsa = GetWord(XIRCREG5_RHSA0); + SelectPage(0); + rhsa += 3; /* skip control infos */ + if (rhsa >= 0x8000) + rhsa = 0; + if (rhsa + pktlen > 0x8000) { + unsigned i; + u_char *buf = skb_put(skb, pktlen); + for (i=0; i < pktlen ; i++, rhsa++) { + buf[i] = GetByte(XIRCREG_EDP); + if (rhsa == 0x8000) { + rhsa = 0; + i--; + } + } + } else { + insw(ioaddr+XIRCREG_EDP, + skb_put(skb, pktlen), (pktlen+1)>>1); + } + } + #if 0 + else if (lp->mohawk) { + /* To use this 32 bit access we should use + * a manual optimized loop + * Also the words are swapped, we can get more + * performance by using 32 bit access and swapping + * the words in a register. Will need this for cardbus + * + * Note: don't forget to change the ALLOC_SKB to .. +3 + */ + unsigned i; + u_long *p = skb_put(skb, pktlen); + register u_long a; + ioaddr_t edpreg = ioaddr+XIRCREG_EDP-2; + for (i=0; i < len ; i += 4, p++) { + a = inl(edpreg); + __asm__("rorl $16,%0\n\t" + :"=q" (a) + : "0" (a)); + *p = a; + } + } + #endif + else { + insw(ioaddr+XIRCREG_EDP, skb_put(skb, pktlen), + (pktlen+1)>>1); + } + skb->protocol = eth_type_trans(skb, dev); + skb->dev = dev; + netif_rx(skb); + dev->last_rx = jiffies; + lp->stats.rx_packets++; + add_rx_bytes(&lp->stats, pktlen); + if (!(rsr & PhyPkt)) + lp->stats.multicast++; + } + } else { /* bad packet */ + DEBUG(5, "rsr=%#02x\n", rsr); + } + if (rsr & PktTooLong) { + lp->stats.rx_frame_errors++; + DEBUG(3, "%s: Packet too long\n", dev->name); + } + if (rsr & CRCErr) { + lp->stats.rx_crc_errors++; + DEBUG(3, "%s: CRC error\n", dev->name); + } + if (rsr & AlignErr) { + lp->stats.rx_fifo_errors++; /* okay ? */ + DEBUG(3, "%s: Alignment error\n", dev->name); + } + + /* clear the received/dropped/error packet */ + PutWord(XIRCREG0_DO, 0x8000); /* issue cmd: skip_rx_packet */ + + /* get the new ethernet status */ + eth_status = GetByte(XIRCREG_ESR); + } + if (rx_status & 0x10) { /* Receive overrun */ + lp->stats.rx_over_errors++; + PutByte(XIRCREG_CR, ClearRxOvrun); + DEBUG(3, "receive overrun cleared\n"); + } + + /***** transmit section ******/ + if (int_status & PktTxed) { + unsigned n, nn; + + n = lp->last_ptr_value; + nn = GetByte(XIRCREG0_PTR); + lp->last_ptr_value = nn; + if (nn < n) /* rollover */ + lp->stats.tx_packets += 256 - n; + else if (n == nn) { /* happens sometimes - don't know why */ + DEBUG(0, "PTR not changed?\n"); + } else + lp->stats.tx_packets += lp->last_ptr_value - n; + netif_wake_queue(dev); + } + if (tx_status & 0x0002) { /* Execessive collissions */ + DEBUG(0, "tx restarted due to execssive collissions\n"); + PutByte(XIRCREG_CR, RestartTx); /* restart transmitter process */ + } + if (tx_status & 0x0040) + lp->stats.tx_aborted_errors++; + + /* recalculate our work chunk so that we limit the duration of this + * ISR to about 1/10 of a second. + * Calculate only if we received a reasonable amount of bytes. + */ + if (bytes_rcvd > 1000) { + u_long duration = jiffies - start_ticks; + + if (duration >= HZ/10) { /* if more than about 1/10 second */ + maxrx_bytes = (bytes_rcvd * (HZ/10)) / duration; + if (maxrx_bytes < 2000) + maxrx_bytes = 2000; + else if (maxrx_bytes > 22000) + maxrx_bytes = 22000; + DEBUG(1, "set maxrx=%u (rcvd=%u ticks=%lu)\n", + maxrx_bytes, bytes_rcvd, duration); + } else if (!duration && maxrx_bytes < 22000) { + /* now much faster */ + maxrx_bytes += 2000; + if (maxrx_bytes > 22000) + maxrx_bytes = 22000; + DEBUG(1, "set maxrx=%u\n", maxrx_bytes); + } + } + + leave: + if (lockup_hack) { + if (int_status != 0xff && (int_status = GetByte(XIRCREG_ISR)) != 0) + goto loop_entry; + } + SelectPage(saved_page); + PutByte(XIRCREG_CR, EnableIntr); /* re-enable interrupts */ + /* Instead of dropping packets during a receive, we could + * force an interrupt with this command: + * PutByte(XIRCREG_CR, EnableIntr|ForceIntr); + */ +} /* xirc2ps_interrupt */ + +/*====================================================================*/ + +static void +do_tx_timeout(struct net_device *dev) +{ + local_info_t *lp = dev->priv; + printk(KERN_NOTICE "%s: transmit timed out\n", dev->name); + lp->stats.tx_errors++; + /* reset the card */ + do_reset(dev,1); + dev->trans_start = jiffies; + netif_wake_queue(dev); +} + +static int +do_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + local_info_t *lp = dev->priv; + ioaddr_t ioaddr = dev->base_addr; + int okay; + unsigned freespace; + unsigned pktlen = skb? skb->len : 0; + + DEBUG(1, "do_start_xmit(skb=%p, dev=%p) len=%u\n", + skb, dev, pktlen); + + tx_timeout_check(dev, do_tx_timeout); + skb_tx_check(dev, skb); + + /* adjust the packet length to min. required + * and hope that the buffer is large enough + * to provide some random data. + * fixme: For Mohawk we can change this by sending + * a larger packetlen than we actually have; the chip will + * pad this in his buffer with random bytes + */ + if (pktlen < ETH_ZLEN) + pktlen = ETH_ZLEN; + + SelectPage(0); + PutWord(XIRCREG0_TRS, (u_short)pktlen+2); + freespace = GetWord(XIRCREG0_TSO); + okay = freespace & 0x8000; + freespace &= 0x7fff; + /* TRS doesn't work - (indeed it is eliminated with sil-rev 1) */ + okay = pktlen +2 < freespace; + DEBUG(2 + (okay ? 2 : 0), "%s: avail. tx space=%u%s\n", + dev->name, freespace, okay ? " (okay)":" (not enough)"); + if (!okay) { /* not enough space */ + return 1; /* upper layer may decide to requeue this packet */ + } + /* send the packet */ + PutWord(XIRCREG_EDP, (u_short)pktlen); + outsw(ioaddr+XIRCREG_EDP, skb->data, pktlen>>1); + if (pktlen & 1) + PutByte(XIRCREG_EDP, skb->data[pktlen-1]); + + if (lp->mohawk) + PutByte(XIRCREG_CR, TransmitPacket|EnableIntr); + + DEV_KFREE_SKB (skb); + dev->trans_start = jiffies; + add_tx_bytes(&lp->stats, pktlen); + netif_start_queue(dev); + return 0; +} + +static struct net_device_stats * +do_get_stats(struct net_device *dev) +{ + local_info_t *lp = dev->priv; + + /* lp->stats.rx_missed_errors = GetByte(?) */ + return &lp->stats; +} + +/**************** + * Set all addresses: This first one is the individual address, + * the next 9 addresses are taken from the multicast list and + * the rest is filled with the individual address. + */ +static void +set_addresses(struct net_device *dev) +{ + ioaddr_t ioaddr = dev->base_addr; + local_info_t *lp = dev->priv; + struct dev_mc_list *dmi = dev->mc_list; + char *addr; + int i,j,k,n; + + SelectPage(k=0x50); + for (i=0,j=8,n=0; ; i++, j++) { + if (i > 5) { + if (++n > 9) + break; + i = 0; + } + if (j > 15) { + j = 8; + k++; + SelectPage(k); + } + + if (n && n <= dev->mc_count && dmi) { + addr = dmi->dmi_addr; + dmi = dmi->next; + } else + addr = dev->dev_addr; + + if (lp->mohawk) + PutByte(j, addr[5-i]); + else + PutByte(j, addr[i]); + } + SelectPage(0); +} + +/**************** + * Set or clear the multicast filter for this adaptor. + * We can filter up to 9 addresses, if more are requested we set + * multicast promiscuous mode. + */ + +static void +set_multicast_list(struct net_device *dev) +{ + ioaddr_t ioaddr = dev->base_addr; + + SelectPage(0x42); + if (dev->flags & IFF_PROMISC) { /* snoop */ + PutByte(XIRCREG42_SWC1, 0x06); /* set MPE and PME */ + } else if (dev->mc_count > 9 || (dev->flags & IFF_ALLMULTI)) { + PutByte(XIRCREG42_SWC1, 0x06); /* set MPE */ + } else if (dev->mc_count) { + /* the chip can filter 9 addresses perfectly */ + PutByte(XIRCREG42_SWC1, 0x00); + SelectPage(0x40); + PutByte(XIRCREG40_CMD0, Offline); + set_addresses(dev); + SelectPage(0x40); + PutByte(XIRCREG40_CMD0, EnableRecv | Online); + } else { /* standard usage */ + PutByte(XIRCREG42_SWC1, 0x00); + } + SelectPage(0); +} + +static int +do_config(struct net_device *dev, struct ifmap *map) +{ + local_info_t *local = dev->priv; + + DEBUG(0, "do_config(%p)\n", dev); + if (map->port != 255 && map->port != dev->if_port) { + if (map->port > 4) + return -EINVAL; + if (!map->port) { + local->probe_port = 1; + dev->if_port = 1; + } else { + local->probe_port = 0; + dev->if_port = map->port; + } + printk(KERN_INFO "%s: switching to %s port\n", + dev->name, if_names[dev->if_port]); + do_reset(dev,1); /* not the fine way :-) */ + } + return 0; +} + +/**************** + * Open the driver + */ +static int +do_open(struct net_device *dev) +{ + local_info_t *lp = dev->priv; + dev_link_t *link = &lp->link; + + DEBUG(0, "do_open(%p)\n", dev); + + /* Check that the PCMCIA card is still here. */ + /* Physical device present signature. */ + if (!DEV_OK(link)) + return -ENODEV; + + /* okay */ + link->open++; + MOD_INC_USE_COUNT; + + netif_start_queue(dev); + netif_mark_up(dev); + do_reset(dev,1); + + return 0; +} + +static int +do_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) +{ + local_info_t *local = dev->priv; + ioaddr_t ioaddr = dev->base_addr; + u16 *data = (u16 *)&rq->ifr_data; + + DEBUG(1, "%s: ioctl(%-.6s, %#04x) %04x %04x %04x %04x\n", + dev->name, rq->ifr_ifrn.ifrn_name, cmd, + data[0], data[1], data[2], data[3]); + + if (!local->mohawk) + return -EOPNOTSUPP; + + switch(cmd) { + case SIOCDEVPRIVATE: /* Get the address of the PHY in use. */ + data[0] = 0; /* we have only this address */ + /* fall trough */ + case SIOCDEVPRIVATE+1: /* Read the specified MII register. */ + data[3] = mii_rd(ioaddr, data[0] & 0x1f, data[1] & 0x1f); + break; + case SIOCDEVPRIVATE+2: /* Write the specified MII register */ + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + mii_wr(ioaddr, data[0] & 0x1f, data[1] & 0x1f, data[2], 16); + break; + default: + return -EOPNOTSUPP; + } + return 0; +} + +static void +hardreset(struct net_device *dev) +{ + local_info_t *local = dev->priv; + ioaddr_t ioaddr = dev->base_addr; + + SelectPage(4); + udelay(1); + PutByte(XIRCREG4_GPR1, 0); /* clear bit 0: power down */ + busy_loop(HZ/25); /* wait 40 msec */ + if (local->mohawk) + PutByte(XIRCREG4_GPR1, 1); /* set bit 0: power up */ + else + PutByte(XIRCREG4_GPR1, 1 | 4); /* set bit 0: power up, bit 2: AIC */ + busy_loop(HZ/50); /* wait 20 msec */ +} + +static void +do_reset(struct net_device *dev, int full) +{ + local_info_t *local = dev->priv; + ioaddr_t ioaddr = dev->base_addr; + unsigned value; + + DEBUG(0, "%s: do_reset(%p,%d)\n", dev? dev->name:"eth?", dev, full); + + hardreset(dev); + PutByte(XIRCREG_CR, SoftReset); /* set */ + busy_loop(HZ/50); /* wait 20 msec */ + PutByte(XIRCREG_CR, 0); /* clear */ + busy_loop(HZ/25); /* wait 40 msec */ + if (local->mohawk) { + SelectPage(4); + /* set pin GP1 and GP2 to output (0x0c) + * set GP1 to low to power up the ML6692 (0x00) + * set GP2 to high to power up the 10Mhz chip (0x02) + */ + PutByte(XIRCREG4_GPR0, 0x0e); + } + + /* give the circuits some time to power up */ + busy_loop(HZ/2); /* about 500ms */ + + local->last_ptr_value = 0; + local->silicon = local->mohawk ? (GetByte(XIRCREG4_BOV) & 0x70) >> 4 + : (GetByte(XIRCREG4_BOV) & 0x30) >> 4; + + if (local->probe_port) { + if (!local->mohawk) { + SelectPage(4); + PutByte(XIRCREG4_GPR0, 4); + local->probe_port = 0; + } + } else if (dev->if_port == 2) { /* enable 10Base2 */ + SelectPage(0x42); + PutByte(XIRCREG42_SWC1, 0xC0); + } else { /* enable 10BaseT */ + SelectPage(0x42); + PutByte(XIRCREG42_SWC1, 0x80); + } + busy_loop(HZ/25); /* wait 40 msec to let it complete */ + + #ifdef PCMCIA_DEBUG + if (pc_debug) { + SelectPage(0); + value = GetByte(XIRCREG_ESR); /* read the ESR */ + printk(KERN_DEBUG "%s: ESR is: %#02x\n", dev->name, value); + } + #endif + + /* setup the ECR */ + SelectPage(1); + PutByte(XIRCREG1_IMR0, 0xff); /* allow all ints */ + PutByte(XIRCREG1_IMR1, 1 ); /* and Set TxUnderrunDetect */ + value = GetByte(XIRCREG1_ECR); + #if 0 + if (local->mohawk) + value |= DisableLinkPulse; + PutByte(XIRCREG1_ECR, value); + #endif + DEBUG(0, "%s: ECR is: %#02x\n", dev->name, value); + + SelectPage(0x42); + PutByte(XIRCREG42_SWC0, 0x20); /* disable source insertion */ + + if (local->silicon != 1) { + /* set the local memory dividing line. + * The comments in the sample code say that this is only + * settable with the scipper version 2 which is revision 0. + * Always for CE3 cards + */ + SelectPage(2); + PutWord(XIRCREG2_RBS, 0x2000); + } + + if (full) + set_addresses(dev); + + /* Hardware workaround: + * The receive byte pointer after reset is off by 1 so we need + * to move the offset pointer back to 0. + */ + SelectPage(0); + PutWord(XIRCREG0_DO, 0x2000); /* change offset command, off=0 */ + + /* setup MAC IMRs and clear status registers */ + SelectPage(0x40); /* Bit 7 ... bit 0 */ + PutByte(XIRCREG40_RMASK0, 0xff); /* ROK, RAB, rsv, RO, CRC, AE, PTL, MP */ + PutByte(XIRCREG40_TMASK0, 0xff); /* TOK, TAB, SQE, LL, TU, JAB, EXC, CRS */ + PutByte(XIRCREG40_TMASK1, 0xb0); /* rsv, rsv, PTD, EXT, rsv,rsv,rsv, rsv*/ + PutByte(XIRCREG40_RXST0, 0x00); /* ROK, RAB, REN, RO, CRC, AE, PTL, MP */ + PutByte(XIRCREG40_TXST0, 0x00); /* TOK, TAB, SQE, LL, TU, JAB, EXC, CRS */ + PutByte(XIRCREG40_TXST1, 0x00); /* TEN, rsv, PTD, EXT, retry_counter:4 */ + + if (full && local->mohawk && init_mii(dev)) { + if (dev->if_port == 4 || local->dingo || local->new_mii) { + printk(KERN_INFO "%s: MII selected\n", dev->name); + SelectPage(2); + PutByte(XIRCREG2_MSR, GetByte(XIRCREG2_MSR) | 0x08); + busy_loop(HZ/50); + } else { + printk(KERN_INFO "%s: MII detected; using 10mbs\n", + dev->name); + SelectPage(0x42); + if (dev->if_port == 2) /* enable 10Base2 */ + PutByte(XIRCREG42_SWC1, 0xC0); + else /* enable 10BaseT */ + PutByte(XIRCREG42_SWC1, 0x80); + busy_loop(HZ/25); /* wait 40 msec to let it complete */ + } + if (full_duplex) + PutByte(XIRCREG1_ECR, GetByte(XIRCREG1_ECR | FullDuplex)); + } else { /* No MII */ + SelectPage(0); + value = GetByte(XIRCREG_ESR); /* read the ESR */ + dev->if_port = (value & MediaSelect) ? 1 : 2; + } + + /* configure the LEDs */ + SelectPage(2); + if (dev->if_port == 1 || dev->if_port == 4) /* TP: Link and Activity */ + PutByte(XIRCREG2_LED, 0x3b); + else /* Coax: Not-Collision and Activity */ + PutByte(XIRCREG2_LED, 0x3a); + + if (local->dingo) + PutByte(0x0b, 0x04); /* 100 Mbit LED */ + + /* enable receiver and put the mac online */ + if (full) { + SelectPage(0x40); + PutByte(XIRCREG40_CMD0, EnableRecv | Online); + } + + /* setup Ethernet IMR and enable interrupts */ + SelectPage(1); + PutByte(XIRCREG1_IMR0, 0xff); + udelay(1); + SelectPage(0); + PutByte(XIRCREG_CR, EnableIntr); + if (local->modem && !local->dingo) { /* do some magic */ + if (!(GetByte(0x10) & 0x01)) + PutByte(0x10, 0x11); /* unmask master-int bit */ + } + + if (full) + printk(KERN_INFO "%s: media %s, silicon revision %d\n", + dev->name, if_names[dev->if_port], local->silicon); + /* We should switch back to page 0 to avoid a bug in revision 0 + * where regs with offset below 8 can't be read after an access + * to the MAC registers */ + SelectPage(0); +} + +/**************** + * Initialize the Media-Independent-Interface + * Returns: True if we have a good MII + */ +static int +init_mii(struct net_device *dev) +{ + local_info_t *local = dev->priv; + ioaddr_t ioaddr = dev->base_addr; + unsigned control, status, linkpartner; + int i; + + if (if_port == 4 || if_port == 1) { /* force 100BaseT or 10BaseT */ + dev->if_port = if_port; + local->probe_port = 0; + return 1; + } + + status = mii_rd(ioaddr, 0, 1); + if ((status & 0xff00) != 0x7800) + return 0; /* No MII */ + + local->new_mii = (mii_rd(ioaddr, 0, 2) != 0xffff); + + if (local->probe_port) + control = 0x1000; /* auto neg */ + else if (dev->if_port == 4) + control = 0x2000; /* no auto neg, 100mbs mode */ + else + control = 0x0000; /* no auto neg, 10mbs mode */ + mii_wr(ioaddr, 0, 0, control, 16); + udelay(100); + control = mii_rd(ioaddr, 0, 0); + + if (control & 0x0400) { + printk(KERN_NOTICE "%s can't take PHY out of isolation mode\n", + dev->name); + local->probe_port = 0; + return 0; + } + + if (local->probe_port) { + /* according to the DP83840A specs the auto negotiation process + * may take up to 3.5 sec, so we use this also for our ML6692 + * Fixme: Better to use a timer here! + */ + for (i=0; i < 35; i++) { + busy_loop(HZ/10); /* wait 100 msec */ + status = mii_rd(ioaddr, 0, 1); + if ((status & 0x0020) && (status & 0x0004)) + break; + } + + if (!(status & 0x0020)) { + printk(KERN_INFO "%s: autonegotiation failed;" + " using 10mbs\n", dev->name); + if (!local->new_mii) { + control = 0x0000; + mii_wr(ioaddr, 0, 0, control, 16); + udelay(100); + SelectPage(0); + dev->if_port = (GetByte(XIRCREG_ESR) & MediaSelect) ? 1 : 2; + } + } else { + linkpartner = mii_rd(ioaddr, 0, 5); + printk(KERN_INFO "%s: MII link partner: %04x\n", + dev->name, linkpartner); + if (linkpartner & 0x0080) { + dev->if_port = 4; + } else + dev->if_port = 1; + } + } + + return 1; +} + +static void +do_powerdown(struct net_device *dev) +{ + + ioaddr_t ioaddr = dev->base_addr; + + DEBUG(0, "do_powerdown(%p)\n", dev); + + SelectPage(4); + PutByte(XIRCREG4_GPR1, 0); /* clear bit 0: power down */ + SelectPage(0); +} + +static int +do_stop(struct net_device *dev) +{ + ioaddr_t ioaddr = dev->base_addr; + local_info_t *lp = dev->priv; + dev_link_t *link = &lp->link; + + DEBUG(0, "do_stop(%p)\n", dev); + + if (!link) + return -ENODEV; + + netif_stop_queue(dev); + netif_mark_down(dev); + + SelectPage(0); + PutByte(XIRCREG_CR, 0); /* disable interrupts */ + SelectPage(0x01); + PutByte(XIRCREG1_IMR0, 0x00); /* forbid all ints */ + SelectPage(4); + PutByte(XIRCREG4_GPR1, 0); /* clear bit 0: power down */ + SelectPage(0); + + link->open--; + if (link->state & DEV_STALE_CONFIG) + mod_timer(&link->release, jiffies + HZ/20); + + MOD_DEC_USE_COUNT; + + return 0; +} + +static int __init +init_xirc2ps_cs(void) +{ + servinfo_t serv; + + printk(KERN_INFO "%s\n", version); + if (lockup_hack) + printk(KINF_XIRC "lockup hack is enabled\n"); + CardServices(GetCardServicesInfo, &serv); + if (serv.Revision != CS_RELEASE_CODE) { + printk(KNOT_XIRC "Card Services release does not match!\n"); + return -EINVAL; + } + DEBUG(0, "pc_debug=%d\n", pc_debug); + register_pccard_driver(&dev_info, &xirc2ps_attach, &xirc2ps_detach); + return 0; +} + +static void __exit +exit_xirc2ps_cs(void) +{ + DEBUG(0, "unloading\n"); + unregister_pccard_driver(&dev_info); + while (dev_list) { + if (dev_list->state & DEV_CONFIG) + xirc2ps_release((u_long)dev_list); + if (dev_list) /* xirc2ps_release() might already have detached... */ + xirc2ps_detach(dev_list); + } +} + +module_init(init_xirc2ps_cs); +module_exit(exit_xirc2ps_cs); + diff --git a/linux/pcmcia-cs/include/linux/crc32.h b/linux/pcmcia-cs/include/linux/crc32.h new file mode 100644 index 0000000..008a2da --- /dev/null +++ b/linux/pcmcia-cs/include/linux/crc32.h @@ -0,0 +1,49 @@ +#ifndef _COMPAT_CRC32_H +#define _COMPAT_CRC32_H + +#include <linux/version.h> + +#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,4,18)) + +#include_next <linux/crc32.h> + +#else + +static inline u_int ether_crc(int length, u_char *data) +{ + static const u_int ethernet_polynomial = 0x04c11db7U; + int crc = 0xffffffff; /* Initial value. */ + + while (--length >= 0) { + u_char current_octet = *data++; + int bit; + for (bit = 0; bit < 8; bit++, current_octet >>= 1) { + crc = (crc << 1) ^ + ((crc < 0) ^ (current_octet & 1) ? ethernet_polynomial : 0); + } + } + return crc; +} + +static inline unsigned ether_crc_le(int length, unsigned char *data) +{ + static unsigned const ethernet_polynomial_le = 0xedb88320U; + unsigned int crc = 0xffffffff; /* Initial value. */ + while(--length >= 0) { + unsigned char current_octet = *data++; + int bit; + for (bit = 8; --bit >= 0; current_octet >>= 1) { + if ((crc ^ current_octet) & 1) { + crc >>= 1; + crc ^= ethernet_polynomial_le; + } else + crc >>= 1; + } + } + return crc; +} + +#endif + +#endif /* _COMPAT_CRC32_H */ + diff --git a/linux/pcmcia-cs/include/linux/init.h b/linux/pcmcia-cs/include/linux/init.h new file mode 100644 index 0000000..dae55c2 --- /dev/null +++ b/linux/pcmcia-cs/include/linux/init.h @@ -0,0 +1,25 @@ +#ifndef _COMPAT_INIT_H +#define _COMPAT_INIT_H + +#include <linux/version.h> + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,3,0)) && defined(MODULE) +#define __init +#define __initdata +#define __exit +#define __exitdata +#define __devinit +#define __devinitdata +#define __devexit +#define __devexitdata +#define module_init(x) int init_module(void) { return x(); } +#define module_exit(x) void cleanup_module(void) { x(); } +#else +#include_next <linux/init.h> +#endif + +#ifndef __devexit_p +#define __devexit_p(x) (x) +#endif + +#endif /* _COMPAT_INIT_H */ diff --git a/linux/pcmcia-cs/include/linux/slab.h b/linux/pcmcia-cs/include/linux/slab.h new file mode 100644 index 0000000..960d9c5 --- /dev/null +++ b/linux/pcmcia-cs/include/linux/slab.h @@ -0,0 +1,12 @@ +#ifndef _COMPAT_SLAB_H +#define _COMPAT_SLAB_H + +#include <linux/version.h> + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,2,0)) +#include_next <linux/malloc.h> +#else +#include_next <linux/slab.h> +#endif + +#endif /* _COMPAT_SLAB_H */ diff --git a/linux/pcmcia-cs/include/pcmcia/bulkmem.h b/linux/pcmcia-cs/include/pcmcia/bulkmem.h new file mode 100644 index 0000000..7748d44 --- /dev/null +++ b/linux/pcmcia-cs/include/pcmcia/bulkmem.h @@ -0,0 +1,195 @@ +/* + * Definitions for bulk memory services + * + * bulkmem.h 1.13 2001/08/24 12:16:12 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in + * which case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use + * your version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + * bulkmem.h 1.3 1995/05/27 04:49:49 + */ + +#ifndef _LINUX_BULKMEM_H +#define _LINUX_BULKMEM_H + +/* For GetFirstRegion and GetNextRegion */ +typedef struct region_info_t { + u_int Attributes; + u_int CardOffset; + u_int RegionSize; + u_int AccessSpeed; + u_int BlockSize; + u_int PartMultiple; + u_char JedecMfr, JedecInfo; + memory_handle_t next; +} region_info_t; + +#define REGION_TYPE 0x0001 +#define REGION_TYPE_CM 0x0000 +#define REGION_TYPE_AM 0x0001 +#define REGION_PREFETCH 0x0008 +#define REGION_CACHEABLE 0x0010 +#define REGION_BAR_MASK 0xe000 +#define REGION_BAR_SHIFT 13 + +/* For OpenMemory */ +typedef struct open_mem_t { + u_int Attributes; + u_int Offset; +} open_mem_t; + +/* Attributes for OpenMemory */ +#define MEMORY_TYPE 0x0001 +#define MEMORY_TYPE_CM 0x0000 +#define MEMORY_TYPE_AM 0x0001 +#define MEMORY_EXCLUSIVE 0x0002 +#define MEMORY_PREFETCH 0x0008 +#define MEMORY_CACHEABLE 0x0010 +#define MEMORY_BAR_MASK 0xe000 +#define MEMORY_BAR_SHIFT 13 + +typedef struct eraseq_entry_t { + memory_handle_t Handle; + u_char State; + u_int Size; + u_int Offset; + void *Optional; +} eraseq_entry_t; + +typedef struct eraseq_hdr_t { + int QueueEntryCnt; + eraseq_entry_t *QueueEntryArray; +} eraseq_hdr_t; + +#define ERASE_QUEUED 0x00 +#define ERASE_IN_PROGRESS(n) (((n) > 0) && ((n) < 0x80)) +#define ERASE_IDLE 0xff +#define ERASE_PASSED 0xe0 +#define ERASE_FAILED 0xe1 + +#define ERASE_MISSING 0x80 +#define ERASE_MEDIA_WRPROT 0x84 +#define ERASE_NOT_ERASABLE 0x85 +#define ERASE_BAD_OFFSET 0xc1 +#define ERASE_BAD_TECH 0xc2 +#define ERASE_BAD_SOCKET 0xc3 +#define ERASE_BAD_VCC 0xc4 +#define ERASE_BAD_VPP 0xc5 +#define ERASE_BAD_SIZE 0xc6 + +/* For CopyMemory */ +typedef struct copy_op_t { + u_int Attributes; + u_int SourceOffset; + u_int DestOffset; + u_int Count; +} copy_op_t; + +/* For ReadMemory and WriteMemory */ +typedef struct mem_op_t { + u_int Attributes; + u_int Offset; + u_int Count; +} mem_op_t; + +#define MEM_OP_BUFFER 0x01 +#define MEM_OP_BUFFER_USER 0x00 +#define MEM_OP_BUFFER_KERNEL 0x01 +#define MEM_OP_DISABLE_ERASE 0x02 +#define MEM_OP_VERIFY 0x04 + +/* For RegisterMTD */ +typedef struct mtd_reg_t { + u_int Attributes; + u_int Offset; + u_long MediaID; +} mtd_reg_t; + +/* + * Definitions for MTD requests + */ + +typedef struct mtd_request_t { + u_int SrcCardOffset; + u_int DestCardOffset; + u_int TransferLength; + u_int Function; + u_long MediaID; + u_int Status; + u_int Timeout; +} mtd_request_t; + +/* Fields in MTD Function */ +#define MTD_REQ_ACTION 0x003 +#define MTD_REQ_ERASE 0x000 +#define MTD_REQ_READ 0x001 +#define MTD_REQ_WRITE 0x002 +#define MTD_REQ_COPY 0x003 +#define MTD_REQ_NOERASE 0x004 +#define MTD_REQ_VERIFY 0x008 +#define MTD_REQ_READY 0x010 +#define MTD_REQ_TIMEOUT 0x020 +#define MTD_REQ_LAST 0x040 +#define MTD_REQ_FIRST 0x080 +#define MTD_REQ_KERNEL 0x100 + +/* Status codes */ +#define MTD_WAITREQ 0x00 +#define MTD_WAITTIMER 0x01 +#define MTD_WAITRDY 0x02 +#define MTD_WAITPOWER 0x03 + +/* + * Definitions for MTD helper functions + */ + +/* For MTDModifyWindow */ +typedef struct mtd_mod_win_t { + u_int Attributes; + u_int AccessSpeed; + u_int CardOffset; +} mtd_mod_win_t; + +/* For MTDSetVpp */ +typedef struct mtd_vpp_req_t { + u_char Vpp1, Vpp2; +} mtd_vpp_req_t; + +/* For MTDRDYMask */ +typedef struct mtd_rdy_req_t { + u_int Mask; +} mtd_rdy_req_t; + +enum mtd_helper { + MTDRequestWindow, MTDModifyWindow, MTDReleaseWindow, + MTDSetVpp, MTDRDYMask +}; + +#ifdef IN_CARD_SERVICES +extern int MTDHelperEntry(int func, void *a1, void *a2); +#else +extern int MTDHelperEntry(int func, ...); +#endif + +#endif /* _LINUX_BULKMEM_H */ diff --git a/linux/pcmcia-cs/include/pcmcia/bus_ops.h b/linux/pcmcia-cs/include/pcmcia/bus_ops.h new file mode 100644 index 0000000..d5f362a --- /dev/null +++ b/linux/pcmcia-cs/include/pcmcia/bus_ops.h @@ -0,0 +1,157 @@ +/* + * bus_ops.h 1.12 2001/08/24 12:16:12 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in + * which case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use + * your version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + */ + +#ifndef _LINUX_BUS_OPS_H +#define _LINUX_BUS_OPS_H + +#include <linux/config.h> + +#ifdef CONFIG_VIRTUAL_BUS + +typedef struct bus_operations { + void *priv; + u32 (*b_in)(void *bus, u32 port, s32 sz); + void (*b_ins)(void *bus, u32 port, void *buf, + u32 count, s32 sz); + void (*b_out)(void *bus, u32 val, u32 port, s32 sz); + void (*b_outs)(void *bus, u32 port, void *buf, + u32 count, s32 sz); + void *(*b_ioremap)(void *bus, u_long ofs, u_long sz); + void (*b_iounmap)(void *bus, void *addr); + u32 (*b_read)(void *bus, void *addr, s32 sz); + void (*b_write)(void *bus, u32 val, void *addr, s32 sz); + void (*b_copy_from)(void *bus, void *d, void *s, u32 count); + void (*b_copy_to)(void *bus, void *d, void *s, u32 count); + int (*b_request_irq)(void *bus, u_int irq, + void (*handler)(int, void *, + struct pt_regs *), + u_long flags, const char *device, + void *dev_id); + void (*b_free_irq)(void *bus, u_int irq, void *dev_id); +} bus_operations; + +#define bus_inb(b,p) (b)->b_in((b),(p),0) +#define bus_inw(b,p) (b)->b_in((b),(p),1) +#define bus_inl(b,p) (b)->b_in((b),(p),2) +#define bus_inw_ns(b,p) (b)->b_in((b),(p),-1) +#define bus_inl_ns(b,p) (b)->b_in((b),(p),-2) + +#define bus_insb(b,p,a,c) (b)->b_ins((b),(p),(a),(c),0) +#define bus_insw(b,p,a,c) (b)->b_ins((b),(p),(a),(c),1) +#define bus_insl(b,p,a,c) (b)->b_ins((b),(p),(a),(c),2) +#define bus_insw_ns(b,p,a,c) (b)->b_ins((b),(p),(a),(c),-1) +#define bus_insl_ns(b,p,a,c) (b)->b_ins((b),(p),(a),(c),-2) + +#define bus_outb(b,v,p) (b)->b_out((b),(v),(p),0) +#define bus_outw(b,v,p) (b)->b_out((b),(v),(p),1) +#define bus_outl(b,v,p) (b)->b_out((b),(v),(p),2) +#define bus_outw_ns(b,v,p) (b)->b_out((b),(v),(p),-1) +#define bus_outl_ns(b,v,p) (b)->b_out((b),(v),(p),-2) + +#define bus_outsb(b,p,a,c) (b)->b_outs((b),(p),(a),(c),0) +#define bus_outsw(b,p,a,c) (b)->b_outs((b),(p),(a),(c),1) +#define bus_outsl(b,p,a,c) (b)->b_outs((b),(p),(a),(c),2) +#define bus_outsw_ns(b,p,a,c) (b)->b_outs((b),(p),(a),(c),-1) +#define bus_outsl_ns(b,p,a,c) (b)->b_outs((b),(p),(a),(c),-2) + +#define bus_readb(b,a) (b)->b_read((b),(a),0) +#define bus_readw(b,a) (b)->b_read((b),(a),1) +#define bus_readl(b,a) (b)->b_read((b),(a),2) +#define bus_readw_ns(b,a) (b)->b_read((b),(a),-1) +#define bus_readl_ns(b,a) (b)->b_read((b),(a),-2) + +#define bus_writeb(b,v,a) (b)->b_write((b),(v),(a),0) +#define bus_writew(b,v,a) (b)->b_write((b),(v),(a),1) +#define bus_writel(b,v,a) (b)->b_write((b),(v),(a),2) +#define bus_writew_ns(b,v,a) (b)->b_write((b),(v),(a),-1) +#define bus_writel_ns(b,v,a) (b)->b_write((b),(v),(a),-2) + +#define bus_ioremap(b,s,n) (b)->b_ioremap((b),(s),(n)) +#define bus_iounmap(b,a) (b)->b_iounmap((b),(a)) +#define bus_memcpy_fromio(b,d,s,n) (b)->b_copy_from((b),(d),(s),(n)) +#define bus_memcpy_toio(b,d,s,n) (b)->b_copy_to((b),(d),(s),(n)) + +#define bus_request_irq(b,i,h,f,n,d) \ + (b)->b_request_irq((b),(i),(h),(f),(n),(d)) +#define bus_free_irq(b,i,d) (b)->b_free_irq((b),(i),(d)) + +#else + +#define bus_inb(b,p) inb(p) +#define bus_inw(b,p) inw(p) +#define bus_inl(b,p) inl(p) +#define bus_inw_ns(b,p) inw_ns(p) +#define bus_inl_ns(b,p) inl_ns(p) + +#define bus_insb(b,p,a,c) insb(p,a,c) +#define bus_insw(b,p,a,c) insw(p,a,c) +#define bus_insl(b,p,a,c) insl(p,a,c) +#define bus_insw_ns(b,p,a,c) insw_ns(p,a,c) +#define bus_insl_ns(b,p,a,c) insl_ns(p,a,c) + +#define bus_outb(b,v,p) outb(b,v,p) +#define bus_outw(b,v,p) outw(b,v,p) +#define bus_outl(b,v,p) outl(b,v,p) +#define bus_outw_ns(b,v,p) outw_ns(b,v,p) +#define bus_outl_ns(b,v,p) outl_ns(b,v,p) + +#define bus_outsb(b,p,a,c) outsb(p,a,c) +#define bus_outsw(b,p,a,c) outsw(p,a,c) +#define bus_outsl(b,p,a,c) outsl(p,a,c) +#define bus_outsw_ns(b,p,a,c) outsw_ns(p,a,c) +#define bus_outsl_ns(b,p,a,c) outsl_ns(p,a,c) + +#define bus_readb(b,a) readb(a) +#define bus_readw(b,a) readw(a) +#define bus_readl(b,a) readl(a) +#define bus_readw_ns(b,a) readw_ns(a) +#define bus_readl_ns(b,a) readl_ns(a) + +#define bus_writeb(b,v,a) writeb(v,a) +#define bus_writew(b,v,a) writew(v,a) +#define bus_writel(b,v,a) writel(v,a) +#define bus_writew_ns(b,v,a) writew_ns(v,a) +#define bus_writel_ns(b,v,a) writel_ns(v,a) + +#define bus_ioremap(b,s,n) ioremap(s,n) +#define bus_iounmap(b,a) iounmap(a) +#define bus_memcpy_fromio(b,d,s,n) memcpy_fromio(d,s,n) +#define bus_memcpy_toio(b,d,s,n) memcpy_toio(d,s,n) + +#ifdef CONFIG_8xx +#define bus_request_irq(b,i,h,f,n,d) request_8xxirq((i),(h),(f),(n),(d)) +#else +#define bus_request_irq(b,i,h,f,n,d) request_irq((i),(h),(f),(n),(d)) +#endif + +#define bus_free_irq(b,i,d) free_irq((i),(d)) + +#endif /* CONFIG_VIRTUAL_BUS */ + +#endif /* _LINUX_BUS_OPS_H */ diff --git a/linux/pcmcia-cs/include/pcmcia/ciscode.h b/linux/pcmcia-cs/include/pcmcia/ciscode.h new file mode 100644 index 0000000..e6bacef --- /dev/null +++ b/linux/pcmcia-cs/include/pcmcia/ciscode.h @@ -0,0 +1,138 @@ +/* + * ciscode.h 1.57 2002/11/03 20:38:14 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in + * which case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use + * your version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + */ + +#ifndef _LINUX_CISCODE_H +#define _LINUX_CISCODE_H + +/* Manufacturer and Product ID codes */ + +#define MANFID_3COM 0x0101 +#define PRODID_3COM_3CXEM556 0x0035 +#define PRODID_3COM_3CCFEM556 0x0556 +#define PRODID_3COM_3C562 0x0562 + +#define MANFID_ACCTON 0x01bf +#define PRODID_ACCTON_EN2226 0x010a + +#define MANFID_ADAPTEC 0x012f +#define PRODID_ADAPTEC_SCSI 0x0001 + +#define MANFID_ATT 0xffff +#define PRODID_ATT_KIT 0x0100 + +#define MANFID_CONTEC 0xc001 + +#define MANFID_FUJITSU 0x0004 +#define PRODID_FUJITSU_MBH10302 0x0004 +#define PRODID_FUJITSU_MBH10304 0x1003 +#define PRODID_FUJITSU_LA501 0x2000 + +#define MANFID_IBM 0x00a4 +#define PRODID_IBM_HOME_AND_AWAY 0x002e + +#define MANFID_INTEL 0x0089 +#define PRODID_INTEL_DUAL_RS232 0x0301 +#define PRODID_INTEL_2PLUS 0x8422 + +#define MANFID_KME 0x0032 +#define PRODID_KME_KXLC005_A 0x0704 +#define PRODID_KME_KXLC005_B 0x2904 + +#define MANFID_LINKSYS 0x0143 +#define PRODID_LINKSYS_PCMLM28 0xc0ab +#define PRODID_LINKSYS_3400 0x3341 + +#define MANFID_MEGAHERTZ 0x0102 +#define PRODID_MEGAHERTZ_VARIOUS 0x0000 +#define PRODID_MEGAHERTZ_EM3288 0x0006 + +#define MANFID_MACNICA 0xc00b + +#define MANFID_MOTOROLA 0x0109 +#define PRODID_MOTOROLA_MARINER 0x0501 + +#define MANFID_NATINST 0x010b +#define PRODID_NATINST_QUAD_RS232 0xd180 + +#define MANFID_NEW_MEDIA 0x0057 + +#define MANFID_NOKIA 0x0124 +#define PRODID_NOKIA_CARDPHONE 0x0900 + +#define MANFID_OLICOM 0x0121 +#define PRODID_OLICOM_OC2231 0x3122 +#define PRODID_OLICOM_OC2232 0x3222 + +#define MANFID_OMEGA 0x0137 +#define PRODID_OMEGA_QSP_100 0x0025 + +#define MANFID_OSITECH 0x0140 +#define PRODID_OSITECH_JACK_144 0x0001 +#define PRODID_OSITECH_JACK_288 0x0002 +#define PRODID_OSITECH_JACK_336 0x0007 +#define PRODID_OSITECH_SEVEN 0x0008 + +#define MANFID_OXSEMI 0x0279 + +#define MANFID_PIONEER 0x000b + +#define MANFID_PSION 0x016c +#define PRODID_PSION_NET100 0x0023 + +#define MANFID_QUATECH 0x0137 +#define PRODID_QUATECH_SPP100 0x0003 +#define PRODID_QUATECH_DUAL_RS232 0x0012 +#define PRODID_QUATECH_DUAL_RS232_D1 0x0007 +#define PRODID_QUATECH_DUAL_RS232_D2 0x0052 +#define PRODID_QUATECH_QUAD_RS232 0x001b +#define PRODID_QUATECH_DUAL_RS422 0x000e +#define PRODID_QUATECH_QUAD_RS422 0x0045 + +#define MANFID_SMC 0x0108 +#define PRODID_SMC_ETHER 0x0105 + +#define MANFID_SOCKET 0x0104 +#define PRODID_SOCKET_DUAL_RS232 0x0006 +#define PRODID_SOCKET_EIO 0x000a +#define PRODID_SOCKET_LPE 0x000d +#define PRODID_SOCKET_LPE_CF 0x0075 + +#define MANFID_SUNDISK 0x0045 + +#define MANFID_TDK 0x0105 +#define PRODID_TDK_CF010 0x0900 +#define PRODID_TDK_GN3410 0x4815 + +#define MANFID_TOSHIBA 0x0098 + +#define MANFID_UNGERMANN 0x02c0 + +#define MANFID_XIRCOM 0x0105 + +#endif /* _LINUX_CISCODE_H */ diff --git a/linux/pcmcia-cs/include/pcmcia/cisreg.h b/linux/pcmcia-cs/include/pcmcia/cisreg.h new file mode 100644 index 0000000..cb9fe39 --- /dev/null +++ b/linux/pcmcia-cs/include/pcmcia/cisreg.h @@ -0,0 +1,135 @@ +/* + * cisreg.h 1.18 2001/08/24 12:16:12 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in + * which case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use + * your version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + */ + +#ifndef _LINUX_CISREG_H +#define _LINUX_CISREG_H + +/* + * Offsets from ConfigBase for CIS registers + */ +#define CISREG_COR 0x00 +#define CISREG_CCSR 0x02 +#define CISREG_PRR 0x04 +#define CISREG_SCR 0x06 +#define CISREG_ESR 0x08 +#define CISREG_IOBASE_0 0x0a +#define CISREG_IOBASE_1 0x0c +#define CISREG_IOBASE_2 0x0e +#define CISREG_IOBASE_3 0x10 +#define CISREG_IOSIZE 0x12 + +/* + * Configuration Option Register + */ +#define COR_CONFIG_MASK 0x3f +#define COR_MFC_CONFIG_MASK 0x38 +#define COR_FUNC_ENA 0x01 +#define COR_ADDR_DECODE 0x02 +#define COR_IREQ_ENA 0x04 +#define COR_LEVEL_REQ 0x40 +#define COR_SOFT_RESET 0x80 + +/* + * Card Configuration and Status Register + */ +#define CCSR_INTR_ACK 0x01 +#define CCSR_INTR_PENDING 0x02 +#define CCSR_POWER_DOWN 0x04 +#define CCSR_AUDIO_ENA 0x08 +#define CCSR_IOIS8 0x20 +#define CCSR_SIGCHG_ENA 0x40 +#define CCSR_CHANGED 0x80 + +/* + * Pin Replacement Register + */ +#define PRR_WP_STATUS 0x01 +#define PRR_READY_STATUS 0x02 +#define PRR_BVD2_STATUS 0x04 +#define PRR_BVD1_STATUS 0x08 +#define PRR_WP_EVENT 0x10 +#define PRR_READY_EVENT 0x20 +#define PRR_BVD2_EVENT 0x40 +#define PRR_BVD1_EVENT 0x80 + +/* + * Socket and Copy Register + */ +#define SCR_SOCKET_NUM 0x0f +#define SCR_COPY_NUM 0x70 + +/* + * Extended Status Register + */ +#define ESR_REQ_ATTN_ENA 0x01 +#define ESR_REQ_ATTN 0x10 + +/* + * CardBus Function Status Registers + */ +#define CBFN_EVENT 0x00 +#define CBFN_MASK 0x04 +#define CBFN_STATE 0x08 +#define CBFN_FORCE 0x0c + +/* + * These apply to all the CardBus function registers + */ +#define CBFN_WP 0x0001 +#define CBFN_READY 0x0002 +#define CBFN_BVD2 0x0004 +#define CBFN_BVD1 0x0008 +#define CBFN_GWAKE 0x0010 +#define CBFN_INTR 0x8000 + +/* + * Extra bits in the Function Event Mask Register + */ +#define FEMR_BAM_ENA 0x0020 +#define FEMR_PWM_ENA 0x0040 +#define FEMR_WKUP_MASK 0x4000 + +/* + * Indirect Addressing Registers for Zoomed Video: these are addresses + * in common memory space + */ +#define CISREG_ICTRL0 0x02 /* control registers */ +#define CISREG_ICTRL1 0x03 +#define CISREG_IADDR0 0x04 /* address registers */ +#define CISREG_IADDR1 0x05 +#define CISREG_IADDR2 0x06 +#define CISREG_IADDR3 0x07 +#define CISREG_IDATA0 0x08 /* data registers */ +#define CISREG_IDATA1 0x09 + +#define ICTRL0_COMMON 0x01 +#define ICTRL0_AUTOINC 0x02 +#define ICTRL0_BYTEGRAN 0x04 + +#endif /* _LINUX_CISREG_H */ diff --git a/linux/pcmcia-cs/include/pcmcia/cistpl.h b/linux/pcmcia-cs/include/pcmcia/cistpl.h new file mode 100644 index 0000000..1d4cac2 --- /dev/null +++ b/linux/pcmcia-cs/include/pcmcia/cistpl.h @@ -0,0 +1,604 @@ +/* + * cistpl.h 1.35 2001/08/24 12:16:12 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in + * which case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use + * your version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + */ + +#ifndef _LINUX_CISTPL_H +#define _LINUX_CISTPL_H + +#define CISTPL_NULL 0x00 +#define CISTPL_DEVICE 0x01 +#define CISTPL_LONGLINK_CB 0x02 +#define CISTPL_INDIRECT 0x03 +#define CISTPL_CONFIG_CB 0x04 +#define CISTPL_CFTABLE_ENTRY_CB 0x05 +#define CISTPL_LONGLINK_MFC 0x06 +#define CISTPL_BAR 0x07 +#define CISTPL_PWR_MGMNT 0x08 +#define CISTPL_EXTDEVICE 0x09 +#define CISTPL_CHECKSUM 0x10 +#define CISTPL_LONGLINK_A 0x11 +#define CISTPL_LONGLINK_C 0x12 +#define CISTPL_LINKTARGET 0x13 +#define CISTPL_NO_LINK 0x14 +#define CISTPL_VERS_1 0x15 +#define CISTPL_ALTSTR 0x16 +#define CISTPL_DEVICE_A 0x17 +#define CISTPL_JEDEC_C 0x18 +#define CISTPL_JEDEC_A 0x19 +#define CISTPL_CONFIG 0x1a +#define CISTPL_CFTABLE_ENTRY 0x1b +#define CISTPL_DEVICE_OC 0x1c +#define CISTPL_DEVICE_OA 0x1d +#define CISTPL_DEVICE_GEO 0x1e +#define CISTPL_DEVICE_GEO_A 0x1f +#define CISTPL_MANFID 0x20 +#define CISTPL_FUNCID 0x21 +#define CISTPL_FUNCE 0x22 +#define CISTPL_SWIL 0x23 +#define CISTPL_END 0xff +/* Layer 2 tuples */ +#define CISTPL_VERS_2 0x40 +#define CISTPL_FORMAT 0x41 +#define CISTPL_GEOMETRY 0x42 +#define CISTPL_BYTEORDER 0x43 +#define CISTPL_DATE 0x44 +#define CISTPL_BATTERY 0x45 +#define CISTPL_FORMAT_A 0x47 +/* Layer 3 tuples */ +#define CISTPL_ORG 0x46 +#define CISTPL_SPCL 0x90 + +typedef struct cistpl_longlink_t { + u_int addr; +} cistpl_longlink_t; + +typedef struct cistpl_checksum_t { + u_short addr; + u_short len; + u_char sum; +} cistpl_checksum_t; + +#define CISTPL_MAX_FUNCTIONS 8 +#define CISTPL_MFC_ATTR 0x00 +#define CISTPL_MFC_COMMON 0x01 + +typedef struct cistpl_longlink_mfc_t { + u_char nfn; + struct { + u_char space; + u_int addr; + } fn[CISTPL_MAX_FUNCTIONS]; +} cistpl_longlink_mfc_t; + +#define CISTPL_MAX_ALTSTR_STRINGS 4 + +typedef struct cistpl_altstr_t { + u_char ns; + u_char ofs[CISTPL_MAX_ALTSTR_STRINGS]; + char str[254]; +} cistpl_altstr_t; + +#define CISTPL_DTYPE_NULL 0x00 +#define CISTPL_DTYPE_ROM 0x01 +#define CISTPL_DTYPE_OTPROM 0x02 +#define CISTPL_DTYPE_EPROM 0x03 +#define CISTPL_DTYPE_EEPROM 0x04 +#define CISTPL_DTYPE_FLASH 0x05 +#define CISTPL_DTYPE_SRAM 0x06 +#define CISTPL_DTYPE_DRAM 0x07 +#define CISTPL_DTYPE_FUNCSPEC 0x0d +#define CISTPL_DTYPE_EXTEND 0x0e + +#define CISTPL_MAX_DEVICES 4 + +typedef struct cistpl_device_t { + u_char ndev; + struct { + u_char type; + u_char wp; + u_int speed; + u_int size; + } dev[CISTPL_MAX_DEVICES]; +} cistpl_device_t; + +#define CISTPL_DEVICE_MWAIT 0x01 +#define CISTPL_DEVICE_3VCC 0x02 + +typedef struct cistpl_device_o_t { + u_char flags; + cistpl_device_t device; +} cistpl_device_o_t; + +#define CISTPL_VERS_1_MAX_PROD_STRINGS 4 + +typedef struct cistpl_vers_1_t { + u_char major; + u_char minor; + u_char ns; + u_char ofs[CISTPL_VERS_1_MAX_PROD_STRINGS]; + char str[254]; +} cistpl_vers_1_t; + +typedef struct cistpl_jedec_t { + u_char nid; + struct { + u_char mfr; + u_char info; + } id[CISTPL_MAX_DEVICES]; +} cistpl_jedec_t; + +typedef struct cistpl_manfid_t { + u_short manf; + u_short card; +} cistpl_manfid_t; + +#define CISTPL_FUNCID_MULTI 0x00 +#define CISTPL_FUNCID_MEMORY 0x01 +#define CISTPL_FUNCID_SERIAL 0x02 +#define CISTPL_FUNCID_PARALLEL 0x03 +#define CISTPL_FUNCID_FIXED 0x04 +#define CISTPL_FUNCID_VIDEO 0x05 +#define CISTPL_FUNCID_NETWORK 0x06 +#define CISTPL_FUNCID_AIMS 0x07 +#define CISTPL_FUNCID_SCSI 0x08 + +#define CISTPL_SYSINIT_POST 0x01 +#define CISTPL_SYSINIT_ROM 0x02 + +typedef struct cistpl_funcid_t { + u_char func; + u_char sysinit; +} cistpl_funcid_t; + +typedef struct cistpl_funce_t { + u_char type; + u_char data[0]; +} cistpl_funce_t; + +/*====================================================================== + + Modem Function Extension Tuples + +======================================================================*/ + +#define CISTPL_FUNCE_SERIAL_IF 0x00 +#define CISTPL_FUNCE_SERIAL_CAP 0x01 +#define CISTPL_FUNCE_SERIAL_SERV_DATA 0x02 +#define CISTPL_FUNCE_SERIAL_SERV_FAX 0x03 +#define CISTPL_FUNCE_SERIAL_SERV_VOICE 0x04 +#define CISTPL_FUNCE_SERIAL_CAP_DATA 0x05 +#define CISTPL_FUNCE_SERIAL_CAP_FAX 0x06 +#define CISTPL_FUNCE_SERIAL_CAP_VOICE 0x07 +#define CISTPL_FUNCE_SERIAL_IF_DATA 0x08 +#define CISTPL_FUNCE_SERIAL_IF_FAX 0x09 +#define CISTPL_FUNCE_SERIAL_IF_VOICE 0x0a + +/* UART identification */ +#define CISTPL_SERIAL_UART_8250 0x00 +#define CISTPL_SERIAL_UART_16450 0x01 +#define CISTPL_SERIAL_UART_16550 0x02 +#define CISTPL_SERIAL_UART_8251 0x03 +#define CISTPL_SERIAL_UART_8530 0x04 +#define CISTPL_SERIAL_UART_85230 0x05 + +/* UART capabilities */ +#define CISTPL_SERIAL_UART_SPACE 0x01 +#define CISTPL_SERIAL_UART_MARK 0x02 +#define CISTPL_SERIAL_UART_ODD 0x04 +#define CISTPL_SERIAL_UART_EVEN 0x08 +#define CISTPL_SERIAL_UART_5BIT 0x01 +#define CISTPL_SERIAL_UART_6BIT 0x02 +#define CISTPL_SERIAL_UART_7BIT 0x04 +#define CISTPL_SERIAL_UART_8BIT 0x08 +#define CISTPL_SERIAL_UART_1STOP 0x10 +#define CISTPL_SERIAL_UART_MSTOP 0x20 +#define CISTPL_SERIAL_UART_2STOP 0x40 + +typedef struct cistpl_serial_t { + u_char uart_type; + u_char uart_cap_0; + u_char uart_cap_1; +} cistpl_serial_t; + +typedef struct cistpl_modem_cap_t { + u_char flow; + u_char cmd_buf; + u_char rcv_buf_0, rcv_buf_1, rcv_buf_2; + u_char xmit_buf_0, xmit_buf_1, xmit_buf_2; +} cistpl_modem_cap_t; + +#define CISTPL_SERIAL_MOD_103 0x01 +#define CISTPL_SERIAL_MOD_V21 0x02 +#define CISTPL_SERIAL_MOD_V23 0x04 +#define CISTPL_SERIAL_MOD_V22 0x08 +#define CISTPL_SERIAL_MOD_212A 0x10 +#define CISTPL_SERIAL_MOD_V22BIS 0x20 +#define CISTPL_SERIAL_MOD_V26 0x40 +#define CISTPL_SERIAL_MOD_V26BIS 0x80 +#define CISTPL_SERIAL_MOD_V27BIS 0x01 +#define CISTPL_SERIAL_MOD_V29 0x02 +#define CISTPL_SERIAL_MOD_V32 0x04 +#define CISTPL_SERIAL_MOD_V32BIS 0x08 +#define CISTPL_SERIAL_MOD_V34 0x10 + +#define CISTPL_SERIAL_ERR_MNP2_4 0x01 +#define CISTPL_SERIAL_ERR_V42_LAPM 0x02 + +#define CISTPL_SERIAL_CMPR_V42BIS 0x01 +#define CISTPL_SERIAL_CMPR_MNP5 0x02 + +#define CISTPL_SERIAL_CMD_AT1 0x01 +#define CISTPL_SERIAL_CMD_AT2 0x02 +#define CISTPL_SERIAL_CMD_AT3 0x04 +#define CISTPL_SERIAL_CMD_MNP_AT 0x08 +#define CISTPL_SERIAL_CMD_V25BIS 0x10 +#define CISTPL_SERIAL_CMD_V25A 0x20 +#define CISTPL_SERIAL_CMD_DMCL 0x40 + +typedef struct cistpl_data_serv_t { + u_char max_data_0; + u_char max_data_1; + u_char modulation_0; + u_char modulation_1; + u_char error_control; + u_char compression; + u_char cmd_protocol; + u_char escape; + u_char encrypt; + u_char misc_features; + u_char ccitt_code[0]; +} cistpl_data_serv_t; + +typedef struct cistpl_fax_serv_t { + u_char max_data_0; + u_char max_data_1; + u_char modulation; + u_char encrypt; + u_char features_0; + u_char features_1; + u_char ccitt_code[0]; +} cistpl_fax_serv_t; + +typedef struct cistpl_voice_serv_t { + u_char max_data_0; + u_char max_data_1; +} cistpl_voice_serv_t; + +/*====================================================================== + + LAN Function Extension Tuples + +======================================================================*/ + +#define CISTPL_FUNCE_LAN_TECH 0x01 +#define CISTPL_FUNCE_LAN_SPEED 0x02 +#define CISTPL_FUNCE_LAN_MEDIA 0x03 +#define CISTPL_FUNCE_LAN_NODE_ID 0x04 +#define CISTPL_FUNCE_LAN_CONNECTOR 0x05 + +/* LAN technologies */ +#define CISTPL_LAN_TECH_ARCNET 0x01 +#define CISTPL_LAN_TECH_ETHERNET 0x02 +#define CISTPL_LAN_TECH_TOKENRING 0x03 +#define CISTPL_LAN_TECH_LOCALTALK 0x04 +#define CISTPL_LAN_TECH_FDDI 0x05 +#define CISTPL_LAN_TECH_ATM 0x06 +#define CISTPL_LAN_TECH_WIRELESS 0x07 + +typedef struct cistpl_lan_tech_t { + u_char tech; +} cistpl_lan_tech_t; + +typedef struct cistpl_lan_speed_t { + u_int speed; +} cistpl_lan_speed_t; + +/* LAN media definitions */ +#define CISTPL_LAN_MEDIA_UTP 0x01 +#define CISTPL_LAN_MEDIA_STP 0x02 +#define CISTPL_LAN_MEDIA_THIN_COAX 0x03 +#define CISTPL_LAN_MEDIA_THICK_COAX 0x04 +#define CISTPL_LAN_MEDIA_FIBER 0x05 +#define CISTPL_LAN_MEDIA_900MHZ 0x06 +#define CISTPL_LAN_MEDIA_2GHZ 0x07 +#define CISTPL_LAN_MEDIA_5GHZ 0x08 +#define CISTPL_LAN_MEDIA_DIFF_IR 0x09 +#define CISTPL_LAN_MEDIA_PTP_IR 0x0a + +typedef struct cistpl_lan_media_t { + u_char media; +} cistpl_lan_media_t; + +typedef struct cistpl_lan_node_id_t { + u_char nb; + u_char id[16]; +} cistpl_lan_node_id_t; + +typedef struct cistpl_lan_connector_t { + u_char code; +} cistpl_lan_connector_t; + +/*====================================================================== + + IDE Function Extension Tuples + +======================================================================*/ + +#define CISTPL_IDE_INTERFACE 0x01 + +typedef struct cistpl_ide_interface_t { + u_char interface; +} cistpl_ide_interface_t; + +/* First feature byte */ +#define CISTPL_IDE_SILICON 0x04 +#define CISTPL_IDE_UNIQUE 0x08 +#define CISTPL_IDE_DUAL 0x10 + +/* Second feature byte */ +#define CISTPL_IDE_HAS_SLEEP 0x01 +#define CISTPL_IDE_HAS_STANDBY 0x02 +#define CISTPL_IDE_HAS_IDLE 0x04 +#define CISTPL_IDE_LOW_POWER 0x08 +#define CISTPL_IDE_REG_INHIBIT 0x10 +#define CISTPL_IDE_HAS_INDEX 0x20 +#define CISTPL_IDE_IOIS16 0x40 + +typedef struct cistpl_ide_feature_t { + u_char feature1; + u_char feature2; +} cistpl_ide_feature_t; + +#define CISTPL_FUNCE_IDE_IFACE 0x01 +#define CISTPL_FUNCE_IDE_MASTER 0x02 +#define CISTPL_FUNCE_IDE_SLAVE 0x03 + +/*====================================================================== + + Configuration Table Entries + +======================================================================*/ + +#define CISTPL_BAR_SPACE 0x07 +#define CISTPL_BAR_SPACE_IO 0x10 +#define CISTPL_BAR_PREFETCH 0x20 +#define CISTPL_BAR_CACHEABLE 0x40 +#define CISTPL_BAR_1MEG_MAP 0x80 + +typedef struct cistpl_bar_t { + u_char attr; + u_int size; +} cistpl_bar_t; + +typedef struct cistpl_config_t { + u_char last_idx; + u_int base; + u_int rmask[4]; + u_char subtuples; +} cistpl_config_t; + +/* These are bits in the 'present' field, and indices in 'param' */ +#define CISTPL_POWER_VNOM 0 +#define CISTPL_POWER_VMIN 1 +#define CISTPL_POWER_VMAX 2 +#define CISTPL_POWER_ISTATIC 3 +#define CISTPL_POWER_IAVG 4 +#define CISTPL_POWER_IPEAK 5 +#define CISTPL_POWER_IDOWN 6 + +#define CISTPL_POWER_HIGHZ_OK 0x01 +#define CISTPL_POWER_HIGHZ_REQ 0x02 + +typedef struct cistpl_power_t { + u_char present; + u_char flags; + u_int param[7]; +} cistpl_power_t; + +typedef struct cistpl_timing_t { + u_int wait, waitscale; + u_int ready, rdyscale; + u_int reserved, rsvscale; +} cistpl_timing_t; + +#define CISTPL_IO_LINES_MASK 0x1f +#define CISTPL_IO_8BIT 0x20 +#define CISTPL_IO_16BIT 0x40 +#define CISTPL_IO_RANGE 0x80 + +#define CISTPL_IO_MAX_WIN 16 + +typedef struct cistpl_io_t { + u_char flags; + u_char nwin; + struct { + u_int base; + u_int len; + } win[CISTPL_IO_MAX_WIN]; +} cistpl_io_t; + +typedef struct cistpl_irq_t { + u_int IRQInfo1; + u_int IRQInfo2; +} cistpl_irq_t; + +#define CISTPL_MEM_MAX_WIN 8 + +typedef struct cistpl_mem_t { + u_char flags; + u_char nwin; + struct { + u_int len; + u_int card_addr; + u_int host_addr; + } win[CISTPL_MEM_MAX_WIN]; +} cistpl_mem_t; + +#define CISTPL_CFTABLE_DEFAULT 0x0001 +#define CISTPL_CFTABLE_BVDS 0x0002 +#define CISTPL_CFTABLE_WP 0x0004 +#define CISTPL_CFTABLE_RDYBSY 0x0008 +#define CISTPL_CFTABLE_MWAIT 0x0010 +#define CISTPL_CFTABLE_AUDIO 0x0800 +#define CISTPL_CFTABLE_READONLY 0x1000 +#define CISTPL_CFTABLE_PWRDOWN 0x2000 + +typedef struct cistpl_cftable_entry_t { + u_char index; + u_short flags; + u_char interface; + cistpl_power_t vcc, vpp1, vpp2; + cistpl_timing_t timing; + cistpl_io_t io; + cistpl_irq_t irq; + cistpl_mem_t mem; + u_char subtuples; +} cistpl_cftable_entry_t; + +#define CISTPL_CFTABLE_MASTER 0x000100 +#define CISTPL_CFTABLE_INVALIDATE 0x000200 +#define CISTPL_CFTABLE_VGA_PALETTE 0x000400 +#define CISTPL_CFTABLE_PARITY 0x000800 +#define CISTPL_CFTABLE_WAIT 0x001000 +#define CISTPL_CFTABLE_SERR 0x002000 +#define CISTPL_CFTABLE_FAST_BACK 0x004000 +#define CISTPL_CFTABLE_BINARY_AUDIO 0x010000 +#define CISTPL_CFTABLE_PWM_AUDIO 0x020000 + +typedef struct cistpl_cftable_entry_cb_t { + u_char index; + u_int flags; + cistpl_power_t vcc, vpp1, vpp2; + u_char io; + cistpl_irq_t irq; + u_char mem; + u_char subtuples; +} cistpl_cftable_entry_cb_t; + +typedef struct cistpl_device_geo_t { + u_char ngeo; + struct { + u_char buswidth; + u_int erase_block; + u_int read_block; + u_int write_block; + u_int partition; + u_int interleave; + } geo[CISTPL_MAX_DEVICES]; +} cistpl_device_geo_t; + +typedef struct cistpl_vers_2_t { + u_char vers; + u_char comply; + u_short dindex; + u_char vspec8, vspec9; + u_char nhdr; + u_char vendor, info; + char str[244]; +} cistpl_vers_2_t; + +typedef struct cistpl_org_t { + u_char data_org; + char desc[30]; +} cistpl_org_t; + +#define CISTPL_ORG_FS 0x00 +#define CISTPL_ORG_APPSPEC 0x01 +#define CISTPL_ORG_XIP 0x02 + +typedef struct cistpl_format_t { + u_char type; + u_char edc; + u_int offset; + u_int length; +} cistpl_format_t; + +#define CISTPL_FORMAT_DISK 0x00 +#define CISTPL_FORMAT_MEM 0x01 + +#define CISTPL_EDC_NONE 0x00 +#define CISTPL_EDC_CKSUM 0x01 +#define CISTPL_EDC_CRC 0x02 +#define CISTPL_EDC_PCC 0x03 + +typedef union cisparse_t { + cistpl_device_t device; + cistpl_checksum_t checksum; + cistpl_longlink_t longlink; + cistpl_longlink_mfc_t longlink_mfc; + cistpl_vers_1_t version_1; + cistpl_altstr_t altstr; + cistpl_jedec_t jedec; + cistpl_manfid_t manfid; + cistpl_funcid_t funcid; + cistpl_funce_t funce; + cistpl_bar_t bar; + cistpl_config_t config; + cistpl_cftable_entry_t cftable_entry; + cistpl_cftable_entry_cb_t cftable_entry_cb; + cistpl_device_geo_t device_geo; + cistpl_vers_2_t vers_2; + cistpl_org_t org; + cistpl_format_t format; +} cisparse_t; + +typedef struct tuple_t { + u_int Attributes; + cisdata_t DesiredTuple; + u_int Flags; /* internal use */ + u_int LinkOffset; /* internal use */ + u_int CISOffset; /* internal use */ + cisdata_t TupleCode; + cisdata_t TupleLink; + cisdata_t TupleOffset; + cisdata_t TupleDataMax; + cisdata_t TupleDataLen; + cisdata_t *TupleData; +} tuple_t; + +/* Special cisdata_t value */ +#define RETURN_FIRST_TUPLE 0xff + +/* Attributes for tuple calls */ +#define TUPLE_RETURN_LINK 0x01 +#define TUPLE_RETURN_COMMON 0x02 + +/* For ValidateCIS */ +typedef struct cisinfo_t { + u_int Chains; +} cisinfo_t; + +#define CISTPL_MAX_CIS_SIZE 0x200 + +/* For ReplaceCIS */ +typedef struct cisdump_t { + u_int Length; + cisdata_t Data[CISTPL_MAX_CIS_SIZE]; +} cisdump_t; + +#endif /* LINUX_CISTPL_H */ diff --git a/linux/pcmcia-cs/include/pcmcia/cs.h b/linux/pcmcia-cs/include/pcmcia/cs.h new file mode 100644 index 0000000..8e202c6 --- /dev/null +++ b/linux/pcmcia-cs/include/pcmcia/cs.h @@ -0,0 +1,441 @@ +/* + * cs.h 1.74 2001/10/04 03:15:22 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in + * which case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use + * your version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + */ + +#ifndef _LINUX_CS_H +#define _LINUX_CS_H + +/* For AccessConfigurationRegister */ +typedef struct conf_reg_t { + u_char Function; + u_int Action; + off_t Offset; + u_int Value; +} conf_reg_t; + +/* Actions */ +#define CS_READ 1 +#define CS_WRITE 2 + +/* for AdjustResourceInfo */ +typedef struct adjust_t { + u_int Action; + u_int Resource; + u_int Attributes; + union { + struct memory { + u_long Base; + u_long Size; + } memory; + struct io { + ioaddr_t BasePort; + ioaddr_t NumPorts; + u_int IOAddrLines; + } io; + struct irq { + u_int IRQ; + } irq; + } resource; +} adjust_t; + +/* Action field */ +#define REMOVE_MANAGED_RESOURCE 1 +#define ADD_MANAGED_RESOURCE 2 +#define GET_FIRST_MANAGED_RESOURCE 3 +#define GET_NEXT_MANAGED_RESOURCE 4 +/* Resource field */ +#define RES_MEMORY_RANGE 1 +#define RES_IO_RANGE 2 +#define RES_IRQ 3 +/* Attribute field */ +#define RES_IRQ_TYPE 0x03 +#define RES_IRQ_TYPE_EXCLUSIVE 0 +#define RES_IRQ_TYPE_TIME 1 +#define RES_IRQ_TYPE_DYNAMIC 2 +#define RES_IRQ_CSC 0x04 +#define RES_SHARED 0x08 +#define RES_RESERVED 0x10 +#define RES_ALLOCATED 0x20 +#define RES_REMOVED 0x40 + +typedef struct servinfo_t { + char Signature[2]; + u_int Count; + u_int Revision; + u_int CSLevel; + char *VendorString; +} servinfo_t; + +typedef struct event_callback_args_t { + client_handle_t client_handle; + void *info; + void *mtdrequest; + void *buffer; + void *misc; + void *client_data; + struct bus_operations *bus; +} event_callback_args_t; + +/* for GetConfigurationInfo */ +typedef struct config_info_t { + u_char Function; + u_int Attributes; + u_int Vcc, Vpp1, Vpp2; + u_int IntType; + u_int ConfigBase; + u_char Status, Pin, Copy, Option, ExtStatus; + u_int Present; + u_int CardValues; + u_int AssignedIRQ; + u_int IRQAttributes; + ioaddr_t BasePort1; + ioaddr_t NumPorts1; + u_int Attributes1; + ioaddr_t BasePort2; + ioaddr_t NumPorts2; + u_int Attributes2; + u_int IOAddrLines; +} config_info_t; + +/* For CardValues field */ +#define CV_OPTION_VALUE 0x01 +#define CV_STATUS_VALUE 0x02 +#define CV_PIN_REPLACEMENT 0x04 +#define CV_COPY_VALUE 0x08 +#define CV_EXT_STATUS 0x10 + +/* For GetFirst/NextClient */ +typedef struct client_req_t { + socket_t Socket; + u_int Attributes; +} client_req_t; + +#define CLIENT_THIS_SOCKET 0x01 + +/* For RegisterClient */ +typedef struct client_reg_t { + dev_info_t *dev_info; + u_int Attributes; + u_int EventMask; + int (*event_handler)(event_t event, int priority, + event_callback_args_t *); + event_callback_args_t event_callback_args; + u_int Version; +} client_reg_t; + +/* ModifyConfiguration */ +typedef struct modconf_t { + u_int Attributes; + u_int Vcc, Vpp1, Vpp2; +} modconf_t; + +/* Attributes for ModifyConfiguration */ +#define CONF_IRQ_CHANGE_VALID 0x100 +#define CONF_VCC_CHANGE_VALID 0x200 +#define CONF_VPP1_CHANGE_VALID 0x400 +#define CONF_VPP2_CHANGE_VALID 0x800 + +/* For RequestConfiguration */ +typedef struct config_req_t { + u_int Attributes; + u_int Vcc, Vpp1, Vpp2; + u_int IntType; + u_int ConfigBase; + u_char Status, Pin, Copy, ExtStatus; + u_char ConfigIndex; + u_int Present; +} config_req_t; + +/* Attributes for RequestConfiguration */ +#define CONF_ENABLE_IRQ 0x01 +#define CONF_ENABLE_DMA 0x02 +#define CONF_ENABLE_SPKR 0x04 +#define CONF_VALID_CLIENT 0x100 + +/* IntType field */ +#define INT_MEMORY 0x01 +#define INT_MEMORY_AND_IO 0x02 +#define INT_CARDBUS 0x04 +#define INT_ZOOMED_VIDEO 0x08 + +/* For RequestIO and ReleaseIO */ +typedef struct io_req_t { + ioaddr_t BasePort1; + ioaddr_t NumPorts1; + u_int Attributes1; + ioaddr_t BasePort2; + ioaddr_t NumPorts2; + u_int Attributes2; + u_int IOAddrLines; +} io_req_t; + +/* Attributes for RequestIO and ReleaseIO */ +#define IO_SHARED 0x01 +#define IO_FIRST_SHARED 0x02 +#define IO_FORCE_ALIAS_ACCESS 0x04 +#define IO_DATA_PATH_WIDTH 0x18 +#define IO_DATA_PATH_WIDTH_8 0x00 +#define IO_DATA_PATH_WIDTH_16 0x08 +#define IO_DATA_PATH_WIDTH_AUTO 0x10 + +/* For RequestIRQ and ReleaseIRQ */ +typedef struct irq_req_t { + u_int Attributes; + u_int AssignedIRQ; + u_int IRQInfo1, IRQInfo2; + void *Handler; + void *Instance; +} irq_req_t; + +/* Attributes for RequestIRQ and ReleaseIRQ */ +#define IRQ_TYPE 0x03 +#define IRQ_TYPE_EXCLUSIVE 0x00 +#define IRQ_TYPE_TIME 0x01 +#define IRQ_TYPE_DYNAMIC_SHARING 0x02 +#define IRQ_FORCED_PULSE 0x04 +#define IRQ_FIRST_SHARED 0x08 +#define IRQ_HANDLE_PRESENT 0x10 +#define IRQ_PULSE_ALLOCATED 0x100 + +/* Bits in IRQInfo1 field */ +#define IRQ_MASK 0x0f +#define IRQ_NMI_ID 0x01 +#define IRQ_IOCK_ID 0x02 +#define IRQ_BERR_ID 0x04 +#define IRQ_VEND_ID 0x08 +#define IRQ_INFO2_VALID 0x10 +#define IRQ_LEVEL_ID 0x20 +#define IRQ_PULSE_ID 0x40 +#define IRQ_SHARE_ID 0x80 + +typedef struct eventmask_t { + u_int Attributes; + u_int EventMask; +} eventmask_t; + +#define CONF_EVENT_MASK_VALID 0x01 + +/* Configuration registers present */ +#define PRESENT_OPTION 0x001 +#define PRESENT_STATUS 0x002 +#define PRESENT_PIN_REPLACE 0x004 +#define PRESENT_COPY 0x008 +#define PRESENT_EXT_STATUS 0x010 +#define PRESENT_IOBASE_0 0x020 +#define PRESENT_IOBASE_1 0x040 +#define PRESENT_IOBASE_2 0x080 +#define PRESENT_IOBASE_3 0x100 +#define PRESENT_IOSIZE 0x200 + +/* For GetMemPage, MapMemPage */ +typedef struct memreq_t { + u_int CardOffset; + page_t Page; +} memreq_t; + +/* For ModifyWindow */ +typedef struct modwin_t { + u_int Attributes; + u_int AccessSpeed; +} modwin_t; + +/* For RequestWindow */ +typedef struct win_req_t { + u_int Attributes; + u_long Base; + u_int Size; + u_int AccessSpeed; +} win_req_t; + +/* Attributes for RequestWindow */ +#define WIN_ADDR_SPACE 0x0001 +#define WIN_ADDR_SPACE_MEM 0x0000 +#define WIN_ADDR_SPACE_IO 0x0001 +#define WIN_MEMORY_TYPE 0x0002 +#define WIN_MEMORY_TYPE_CM 0x0000 +#define WIN_MEMORY_TYPE_AM 0x0002 +#define WIN_ENABLE 0x0004 +#define WIN_DATA_WIDTH 0x0018 +#define WIN_DATA_WIDTH_8 0x0000 +#define WIN_DATA_WIDTH_16 0x0008 +#define WIN_DATA_WIDTH_32 0x0010 +#define WIN_PAGED 0x0020 +#define WIN_SHARED 0x0040 +#define WIN_FIRST_SHARED 0x0080 +#define WIN_USE_WAIT 0x0100 +#define WIN_STRICT_ALIGN 0x0200 +#define WIN_MAP_BELOW_1MB 0x0400 +#define WIN_PREFETCH 0x0800 +#define WIN_CACHEABLE 0x1000 +#define WIN_BAR_MASK 0xe000 +#define WIN_BAR_SHIFT 13 + +/* Attributes for RegisterClient */ +#define INFO_MASTER_CLIENT 0x01 +#define INFO_IO_CLIENT 0x02 +#define INFO_MTD_CLIENT 0x04 +#define INFO_MEM_CLIENT 0x08 +#define MAX_NUM_CLIENTS 3 + +#define INFO_CARD_SHARE 0x10 +#define INFO_CARD_EXCL 0x20 + +typedef struct cs_status_t { + u_char Function; + event_t CardState; + event_t SocketState; +} cs_status_t; + +typedef struct error_info_t { + int func; + int retcode; +} error_info_t; + +/* Special stuff for binding drivers to sockets */ +typedef struct bind_req_t { + socket_t Socket; + u_char Function; + dev_info_t *dev_info; +} bind_req_t; + +/* Flag to bind to all functions */ +#define BIND_FN_ALL 0xff + +typedef struct mtd_bind_t { + socket_t Socket; + u_int Attributes; + u_int CardOffset; + dev_info_t *dev_info; +} mtd_bind_t; + +/* Events */ +#define CS_EVENT_PRI_LOW 0 +#define CS_EVENT_PRI_HIGH 1 + +#define CS_EVENT_WRITE_PROTECT 0x000001 +#define CS_EVENT_CARD_LOCK 0x000002 +#define CS_EVENT_CARD_INSERTION 0x000004 +#define CS_EVENT_CARD_REMOVAL 0x000008 +#define CS_EVENT_BATTERY_DEAD 0x000010 +#define CS_EVENT_BATTERY_LOW 0x000020 +#define CS_EVENT_READY_CHANGE 0x000040 +#define CS_EVENT_CARD_DETECT 0x000080 +#define CS_EVENT_RESET_REQUEST 0x000100 +#define CS_EVENT_RESET_PHYSICAL 0x000200 +#define CS_EVENT_CARD_RESET 0x000400 +#define CS_EVENT_REGISTRATION_COMPLETE 0x000800 +#define CS_EVENT_RESET_COMPLETE 0x001000 +#define CS_EVENT_PM_SUSPEND 0x002000 +#define CS_EVENT_PM_RESUME 0x004000 +#define CS_EVENT_INSERTION_REQUEST 0x008000 +#define CS_EVENT_EJECTION_REQUEST 0x010000 +#define CS_EVENT_MTD_REQUEST 0x020000 +#define CS_EVENT_ERASE_COMPLETE 0x040000 +#define CS_EVENT_REQUEST_ATTENTION 0x080000 +#define CS_EVENT_CB_DETECT 0x100000 +#define CS_EVENT_3VCARD 0x200000 +#define CS_EVENT_XVCARD 0x400000 + +/* Return codes */ +#define CS_SUCCESS 0x00 +#define CS_BAD_ADAPTER 0x01 +#define CS_BAD_ATTRIBUTE 0x02 +#define CS_BAD_BASE 0x03 +#define CS_BAD_EDC 0x04 +#define CS_BAD_IRQ 0x06 +#define CS_BAD_OFFSET 0x07 +#define CS_BAD_PAGE 0x08 +#define CS_READ_FAILURE 0x09 +#define CS_BAD_SIZE 0x0a +#define CS_BAD_SOCKET 0x0b +#define CS_BAD_TYPE 0x0d +#define CS_BAD_VCC 0x0e +#define CS_BAD_VPP 0x0f +#define CS_BAD_WINDOW 0x11 +#define CS_WRITE_FAILURE 0x12 +#define CS_NO_CARD 0x14 +#define CS_UNSUPPORTED_FUNCTION 0x15 +#define CS_UNSUPPORTED_MODE 0x16 +#define CS_BAD_SPEED 0x17 +#define CS_BUSY 0x18 +#define CS_GENERAL_FAILURE 0x19 +#define CS_WRITE_PROTECTED 0x1a +#define CS_BAD_ARG_LENGTH 0x1b +#define CS_BAD_ARGS 0x1c +#define CS_CONFIGURATION_LOCKED 0x1d +#define CS_IN_USE 0x1e +#define CS_NO_MORE_ITEMS 0x1f +#define CS_OUT_OF_RESOURCE 0x20 +#define CS_BAD_HANDLE 0x21 + +#define CS_BAD_TUPLE 0x40 + +#ifdef __KERNEL__ + +/* + * Calls to set up low-level "Socket Services" drivers + */ + +typedef int (*ss_entry_t)(u_int sock, u_int cmd, void *arg); +extern int register_ss_entry(int nsock, ss_entry_t entry); +extern void unregister_ss_entry(ss_entry_t entry); + +/* + * The main Card Services entry point + */ + +enum service { + AccessConfigurationRegister, AddSocketServices, + AdjustResourceInfo, CheckEraseQueue, CloseMemory, CopyMemory, + DeregisterClient, DeregisterEraseQueue, GetCardServicesInfo, + GetClientInfo, GetConfigurationInfo, GetEventMask, + GetFirstClient, GetFirstPartion, GetFirstRegion, GetFirstTuple, + GetNextClient, GetNextPartition, GetNextRegion, GetNextTuple, + GetStatus, GetTupleData, MapLogSocket, MapLogWindow, MapMemPage, + MapPhySocket, MapPhyWindow, ModifyConfiguration, ModifyWindow, + OpenMemory, ParseTuple, ReadMemory, RegisterClient, + RegisterEraseQueue, RegisterMTD, RegisterTimer, + ReleaseConfiguration, ReleaseExclusive, ReleaseIO, ReleaseIRQ, + ReleaseSocketMask, ReleaseWindow, ReplaceSocketServices, + RequestConfiguration, RequestExclusive, RequestIO, RequestIRQ, + RequestSocketMask, RequestWindow, ResetCard, ReturnSSEntry, + SetEventMask, SetRegion, ValidateCIS, VendorSpecific, + WriteMemory, BindDevice, BindMTD, ReportError, + SuspendCard, ResumeCard, EjectCard, InsertCard, ReplaceCIS, + GetFirstWindow, GetNextWindow, GetMemPage +}; + +#ifdef IN_CARD_SERVICES +extern int CardServices(int func, void *a1, void *a2, void *a3); +#else +extern int CardServices(int func, ...); +#endif + +#endif /* __KERNEL__ */ + +#endif /* _LINUX_CS_H */ diff --git a/linux/pcmcia-cs/include/pcmcia/cs_types.h b/linux/pcmcia-cs/include/pcmcia/cs_types.h new file mode 100644 index 0000000..88471f9 --- /dev/null +++ b/linux/pcmcia-cs/include/pcmcia/cs_types.h @@ -0,0 +1,70 @@ +/* + * cs_types.h 1.20 2002/04/17 02:52:39 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in + * which case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use + * your version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + */ + +#ifndef _LINUX_CS_TYPES_H +#define _LINUX_CS_TYPES_H + +#ifdef __linux__ +#ifdef __KERNEL__ +#include <linux/types.h> +#else +#include <sys/types.h> +#endif +#endif + +#ifdef __arm__ +typedef u_int ioaddr_t; +#else +typedef u_short ioaddr_t; +#endif + +typedef u_short socket_t; +typedef u_int event_t; +typedef u_char cisdata_t; +typedef u_short page_t; + +struct client_t; +typedef struct client_t *client_handle_t; + +struct window_t; +typedef struct window_t *window_handle_t; + +struct region_t; +typedef struct region_t *memory_handle_t; + +struct eraseq_t; +typedef struct eraseq_t *eraseq_handle_t; + +#ifndef DEV_NAME_LEN +#define DEV_NAME_LEN 32 +#endif + +typedef char dev_info_t[DEV_NAME_LEN]; + +#endif /* _LINUX_CS_TYPES_H */ diff --git a/linux/pcmcia-cs/include/pcmcia/driver_ops.h b/linux/pcmcia-cs/include/pcmcia/driver_ops.h new file mode 100644 index 0000000..9903e5b --- /dev/null +++ b/linux/pcmcia-cs/include/pcmcia/driver_ops.h @@ -0,0 +1,73 @@ +/* + * driver_ops.h 1.17 2001/10/04 03:15:22 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in + * which case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use + * your version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + */ + +#ifndef _LINUX_DRIVER_OPS_H +#define _LINUX_DRIVER_OPS_H + +#ifndef DEV_NAME_LEN +#define DEV_NAME_LEN 32 +#endif + +#ifdef __KERNEL__ + +typedef struct dev_node_t { + char dev_name[DEV_NAME_LEN]; + u_short major, minor; + struct dev_node_t *next; +} dev_node_t; + +typedef struct dev_locator_t { + enum { LOC_ISA, LOC_PCI } bus; + union { + struct { + u_short io_base_1, io_base_2; + u_long mem_base; + u_char irq, dma; + } isa; + struct { + u_char bus; + u_char devfn; + } pci; + } b; +} dev_locator_t; + +typedef struct driver_operations { + char *name; + dev_node_t *(*attach) (dev_locator_t *loc); + void (*suspend) (dev_node_t *dev); + void (*resume) (dev_node_t *dev); + void (*detach) (dev_node_t *dev); +} driver_operations; + +int register_driver(struct driver_operations *ops); +void unregister_driver(struct driver_operations *ops); + +#endif /* __KERNEL__ */ + +#endif /* _LINUX_DRIVER_OPS_H */ diff --git a/linux/pcmcia-cs/include/pcmcia/ds.h b/linux/pcmcia-cs/include/pcmcia/ds.h new file mode 100644 index 0000000..b372e59 --- /dev/null +++ b/linux/pcmcia-cs/include/pcmcia/ds.h @@ -0,0 +1,148 @@ +/* + * ds.h 1.58 2001/10/04 03:15:22 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in + * which case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use + * your version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + */ + +#ifndef _LINUX_DS_H +#define _LINUX_DS_H + +#include <pcmcia/driver_ops.h> +#include <pcmcia/bulkmem.h> + +typedef struct tuple_parse_t { + tuple_t tuple; + cisdata_t data[255]; + cisparse_t parse; +} tuple_parse_t; + +typedef struct win_info_t { + window_handle_t handle; + win_req_t window; + memreq_t map; +} win_info_t; + +typedef struct bind_info_t { + dev_info_t dev_info; + u_char function; + struct dev_link_t *instance; + char name[DEV_NAME_LEN]; + u_short major, minor; + void *next; +} bind_info_t; + +typedef struct mtd_info_t { + dev_info_t dev_info; + u_int Attributes; + u_int CardOffset; +} mtd_info_t; + +typedef union ds_ioctl_arg_t { + servinfo_t servinfo; + adjust_t adjust; + config_info_t config; + tuple_t tuple; + tuple_parse_t tuple_parse; + client_req_t client_req; + cs_status_t status; + conf_reg_t conf_reg; + cisinfo_t cisinfo; + region_info_t region; + bind_info_t bind_info; + mtd_info_t mtd_info; + win_info_t win_info; + cisdump_t cisdump; +} ds_ioctl_arg_t; + +#define DS_GET_CARD_SERVICES_INFO _IOR ('d', 1, servinfo_t) +#define DS_ADJUST_RESOURCE_INFO _IOWR('d', 2, adjust_t) +#define DS_GET_CONFIGURATION_INFO _IOWR('d', 3, config_info_t) +#define DS_GET_FIRST_TUPLE _IOWR('d', 4, tuple_t) +#define DS_GET_NEXT_TUPLE _IOWR('d', 5, tuple_t) +#define DS_GET_TUPLE_DATA _IOWR('d', 6, tuple_parse_t) +#define DS_PARSE_TUPLE _IOWR('d', 7, tuple_parse_t) +#define DS_RESET_CARD _IO ('d', 8) +#define DS_GET_STATUS _IOWR('d', 9, cs_status_t) +#define DS_ACCESS_CONFIGURATION_REGISTER _IOWR('d', 10, conf_reg_t) +#define DS_VALIDATE_CIS _IOR ('d', 11, cisinfo_t) +#define DS_SUSPEND_CARD _IO ('d', 12) +#define DS_RESUME_CARD _IO ('d', 13) +#define DS_EJECT_CARD _IO ('d', 14) +#define DS_INSERT_CARD _IO ('d', 15) +#define DS_GET_FIRST_REGION _IOWR('d', 16, region_info_t) +#define DS_GET_NEXT_REGION _IOWR('d', 17, region_info_t) +#define DS_REPLACE_CIS _IOWR('d', 18, cisdump_t) +#define DS_GET_FIRST_WINDOW _IOR ('d', 19, win_info_t) +#define DS_GET_NEXT_WINDOW _IOWR('d', 20, win_info_t) +#define DS_GET_MEM_PAGE _IOWR('d', 21, win_info_t) + +#define DS_BIND_REQUEST _IOWR('d', 60, bind_info_t) +#define DS_GET_DEVICE_INFO _IOWR('d', 61, bind_info_t) +#define DS_GET_NEXT_DEVICE _IOWR('d', 62, bind_info_t) +#define DS_UNBIND_REQUEST _IOW ('d', 63, bind_info_t) +#define DS_BIND_MTD _IOWR('d', 64, mtd_info_t) + +#ifdef __KERNEL__ + +typedef struct dev_link_t { + dev_node_t *dev; + u_int state, open; + wait_queue_head_t pending; + struct timer_list release; + client_handle_t handle; + io_req_t io; + irq_req_t irq; + config_req_t conf; + window_handle_t win; + void *priv; + struct dev_link_t *next; +} dev_link_t; + +/* Flags for device state */ +#define DEV_PRESENT 0x01 +#define DEV_CONFIG 0x02 +#define DEV_STALE_CONFIG 0x04 /* release on close */ +#define DEV_STALE_LINK 0x08 /* detach on release */ +#define DEV_CONFIG_PENDING 0x10 +#define DEV_RELEASE_PENDING 0x20 +#define DEV_SUSPEND 0x40 +#define DEV_BUSY 0x80 + +#define DEV_OK(l) \ + ((l) && ((l->state & ~DEV_BUSY) == (DEV_CONFIG|DEV_PRESENT))) + +int register_pccard_driver(dev_info_t *dev_info, + dev_link_t *(*attach)(void), + void (*detach)(dev_link_t *)); + +int unregister_pccard_driver(dev_info_t *dev_info); + +#define register_pcmcia_driver register_pccard_driver +#define unregister_pcmcia_driver unregister_pccard_driver + +#endif /* __KERNEL__ */ + +#endif /* _LINUX_DS_H */ diff --git a/linux/pcmcia-cs/include/pcmcia/mem_op.h b/linux/pcmcia-cs/include/pcmcia/mem_op.h new file mode 100644 index 0000000..6e7f795 --- /dev/null +++ b/linux/pcmcia-cs/include/pcmcia/mem_op.h @@ -0,0 +1,133 @@ +/* + * mem_op.h 1.14 2001/08/24 12:16:13 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in + * which case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use + * your version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + */ + +#ifndef _LINUX_MEM_OP_H +#define _LINUX_MEM_OP_H + +#include <asm/uaccess.h> + +/* + If UNSAFE_MEMCPY is defined, we use the (optimized) system routines + to copy between a card and kernel memory. These routines do 32-bit + operations which may not work with all PCMCIA controllers. The + safe versions defined here will do only 8-bit and 16-bit accesses. +*/ + +#ifdef UNSAFE_MEMCPY + +#define copy_from_pc memcpy_fromio +#define copy_to_pc memcpy_toio + +static inline void copy_pc_to_user(void *to, const void *from, size_t n) +{ + size_t odd = (n & 3); + n -= odd; + while (n) { + put_user(readl_ns(from), (int *)to); + (char *)from += 4; (char *)to += 4; n -= 4; + } + while (odd--) + put_user(readb((char *)from++), (char *)to++); +} + +static inline void copy_user_to_pc(void *to, const void *from, size_t n) +{ + int l; + char c; + size_t odd = (n & 3); + n -= odd; + while (n) { + get_user(l, (int *)from); + writel_ns(l, to); + (char *)to += 4; (char *)from += 4; n -= 4; + } + while (odd--) { + get_user(c, (char *)from++); + writeb(c, (char *)to++); + } +} + +#else /* UNSAFE_MEMCPY */ + +static inline void copy_from_pc(void *to, const void *from, size_t n) +{ + size_t odd = (n & 1); + n -= odd; + while (n) { + *(u_short *)to = readw_ns(from); + (char *)to += 2; (char *)from += 2; n -= 2; + } + if (odd) + *(u_char *)to = readb(from); +} + +static inline void copy_to_pc(void *to, const void *from, size_t n) +{ + size_t odd = (n & 1); + n -= odd; + while (n) { + writew_ns(*(u_short *)from, to); + (char *)to += 2; (char *)from += 2; n -= 2; + } + if (odd) + writeb(*(u_char *)from, to); +} + +static inline void copy_pc_to_user(void *to, const void *from, size_t n) +{ + size_t odd = (n & 1); + n -= odd; + while (n) { + put_user(readw_ns(from), (short *)to); + (char *)to += 2; (char *)from += 2; n -= 2; + } + if (odd) + put_user(readb(from), (char *)to); +} + +static inline void copy_user_to_pc(void *to, const void *from, size_t n) +{ + short s; + char c; + size_t odd = (n & 1); + n -= odd; + while (n) { + get_user(s, (short *)from); + writew_ns(s, to); + (char *)to += 2; (char *)from += 2; n -= 2; + } + if (odd) { + get_user(c, (char *)from); + writeb(c, to); + } +} + +#endif /* UNSAFE_MEMCPY */ + +#endif /* _LINUX_MEM_OP_H */ diff --git a/linux/pcmcia-cs/include/pcmcia/ss.h b/linux/pcmcia-cs/include/pcmcia/ss.h new file mode 100644 index 0000000..d197e42 --- /dev/null +++ b/linux/pcmcia-cs/include/pcmcia/ss.h @@ -0,0 +1,133 @@ +/* + * ss.h 1.31 2001/08/24 12:16:13 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in + * which case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use + * your version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + */ + +#ifndef _LINUX_SS_H +#define _LINUX_SS_H + +/* For RegisterCallback */ +typedef struct ss_callback_t { + void (*handler)(void *info, u_int events); + void *info; +} ss_callback_t; + +/* Definitions for card status flags for GetStatus */ +#define SS_WRPROT 0x0001 +#define SS_CARDLOCK 0x0002 +#define SS_EJECTION 0x0004 +#define SS_INSERTION 0x0008 +#define SS_BATDEAD 0x0010 +#define SS_BATWARN 0x0020 +#define SS_READY 0x0040 +#define SS_DETECT 0x0080 +#define SS_POWERON 0x0100 +#define SS_GPI 0x0200 +#define SS_STSCHG 0x0400 +#define SS_CARDBUS 0x0800 +#define SS_3VCARD 0x1000 +#define SS_XVCARD 0x2000 +#define SS_PENDING 0x4000 + +/* for InquireSocket */ +typedef struct socket_cap_t { + u_int features; + u_int irq_mask; + u_int map_size; + u_char pci_irq; + u_char cardbus; + struct pci_bus *cb_bus; + struct bus_operations *bus; +} socket_cap_t; + +/* InquireSocket capabilities */ +#define SS_CAP_PAGE_REGS 0x0001 +#define SS_CAP_VIRTUAL_BUS 0x0002 +#define SS_CAP_MEM_ALIGN 0x0004 +#define SS_CAP_STATIC_MAP 0x0008 +#define SS_CAP_PCCARD 0x4000 +#define SS_CAP_CARDBUS 0x8000 + +/* for GetSocket, SetSocket */ +typedef struct socket_state_t { + u_int flags; + u_int csc_mask; + u_char Vcc, Vpp; + u_char io_irq; +} socket_state_t; + +/* Socket configuration flags */ +#define SS_PWR_AUTO 0x0010 +#define SS_IOCARD 0x0020 +#define SS_RESET 0x0040 +#define SS_DMA_MODE 0x0080 +#define SS_SPKR_ENA 0x0100 +#define SS_OUTPUT_ENA 0x0200 +#define SS_ZVCARD 0x0400 + +/* Flags for I/O port and memory windows */ +#define MAP_ACTIVE 0x01 +#define MAP_16BIT 0x02 +#define MAP_AUTOSZ 0x04 +#define MAP_0WS 0x08 +#define MAP_WRPROT 0x10 +#define MAP_ATTRIB 0x20 +#define MAP_USE_WAIT 0x40 +#define MAP_PREFETCH 0x80 + +/* Use this just for bridge windows */ +#define MAP_IOSPACE 0x20 + +typedef struct pccard_io_map { + u_char map; + u_char flags; + u_short speed; + u_short start, stop; +} pccard_io_map; + +typedef struct pccard_mem_map { + u_char map; + u_char flags; + u_short speed; + u_long sys_start, sys_stop; + u_int card_start; +} pccard_mem_map; + +typedef struct cb_bridge_map { + u_char map; + u_char flags; + u_int start, stop; +} cb_bridge_map; + +enum ss_service { + SS_RegisterCallback, SS_InquireSocket, + SS_GetStatus, SS_GetSocket, SS_SetSocket, + SS_GetIOMap, SS_SetIOMap, SS_GetMemMap, SS_SetMemMap, + SS_GetBridge, SS_SetBridge, SS_ProcSetup +}; + +#endif /* _LINUX_SS_H */ diff --git a/linux/pcmcia-cs/include/pcmcia/version.h b/linux/pcmcia-cs/include/pcmcia/version.h new file mode 100644 index 0000000..7f67ee7 --- /dev/null +++ b/linux/pcmcia-cs/include/pcmcia/version.h @@ -0,0 +1,13 @@ +/* version.h 1.118 2003/12/20 07:16:36 (David Hinds) */ + +#define CS_PKG_RELEASE "3.2.8" +#define CS_PKG_RELEASE_CODE 0x3208 + +#define VERSION(v,p,s) (((v)<<16)+(p<<8)+s) + +#ifdef CONFIG_PCMCIA +#include_next <pcmcia/version.h> +#else +#define CS_RELEASE CS_PKG_RELEASE +#define CS_RELEASE_CODE CS_PKG_RELEASE_CODE +#endif diff --git a/linux/pcmcia-cs/modules/bulkmem.c b/linux/pcmcia-cs/modules/bulkmem.c new file mode 100644 index 0000000..558e6d9 --- /dev/null +++ b/linux/pcmcia-cs/modules/bulkmem.c @@ -0,0 +1,626 @@ +/*====================================================================== + + PCMCIA Bulk Memory Services + + bulkmem.c 1.44 2002/06/29 06:23:09 + + The contents of this file are subject to the Mozilla Public + License Version 1.1 (the "License"); you may not use this file + except in compliance with the License. You may obtain a copy of + the License at http://www.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS + IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + implied. See the License for the specific language governing + rights and limitations under the License. + + The initial developer of the original code is David A. Hinds + <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + + Alternatively, the contents of this file may be used under the + terms of the GNU General Public License version 2 (the "GPL"), in + which case the provisions of the GPL are applicable instead of the + above. If you wish to allow the use of your version of this file + only under the terms of the GPL and not to allow others to use + your version of this file under the MPL, indicate your decision + by deleting the provisions above and replace them with the notice + and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this + file under either the MPL or the GPL. + +======================================================================*/ + +#define __NO_VERSION__ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/sched.h> +#include <linux/timer.h> + +#define IN_CARD_SERVICES +#include <pcmcia/cs_types.h> +#include <pcmcia/ss.h> +#include <pcmcia/cs.h> +#include <pcmcia/bulkmem.h> +#include <pcmcia/cistpl.h> +#include "cs_internal.h" + +/*====================================================================== + + This function handles submitting an MTD request, and retrying + requests when an MTD is busy. + + An MTD request should never block. + +======================================================================*/ + +static int do_mtd_request(memory_handle_t handle, mtd_request_t *req, + caddr_t buf) +{ + int ret, tries; + client_t *mtd; + socket_info_t *s; + + mtd = handle->mtd; + if (mtd == NULL) + return CS_GENERAL_FAILURE; + s = SOCKET(mtd); + for (ret = tries = 0; tries < 100; tries++) { + mtd->event_callback_args.mtdrequest = req; + mtd->event_callback_args.buffer = buf; + ret = EVENT(mtd, CS_EVENT_MTD_REQUEST, CS_EVENT_PRI_LOW); + if (ret != CS_BUSY) + break; + switch (req->Status) { + case MTD_WAITREQ: + /* Not that we should ever need this... */ + interruptible_sleep_on_timeout(&mtd->mtd_req, HZ); + break; + case MTD_WAITTIMER: + case MTD_WAITRDY: + interruptible_sleep_on_timeout(&mtd->mtd_req, + req->Timeout*HZ/1000); + req->Function |= MTD_REQ_TIMEOUT; + break; + case MTD_WAITPOWER: + interruptible_sleep_on(&mtd->mtd_req); + break; + } + if (signal_pending(current)) + printk(KERN_NOTICE "cs: do_mtd_request interrupted!\n"); + } + if (tries == 20) { + printk(KERN_NOTICE "cs: MTD request timed out!\n"); + ret = CS_GENERAL_FAILURE; + } + wake_up_interruptible(&mtd->mtd_req); + retry_erase_list(&mtd->erase_busy, 0); + return ret; +} /* do_mtd_request */ + +/*====================================================================== + + This stuff is all for handling asynchronous erase requests. It + is complicated because all the retry stuff has to be dealt with + in timer interrupts or in the card status event handler. + +======================================================================*/ + +static void insert_queue(erase_busy_t *head, erase_busy_t *entry) +{ + DEBUG(2, "cs: adding 0x%p to queue 0x%p\n", entry, head); + entry->next = head; + entry->prev = head->prev; + head->prev->next = entry; + head->prev = entry; +} + +static void remove_queue(erase_busy_t *entry) +{ + DEBUG(2, "cs: unqueueing 0x%p\n", entry); + entry->next->prev = entry->prev; + entry->prev->next = entry->next; +} + +static void retry_erase(erase_busy_t *busy, u_int cause) +{ + eraseq_entry_t *erase = busy->erase; + mtd_request_t req; + client_t *mtd; + socket_info_t *s; + int ret; + + DEBUG(2, "cs: trying erase request 0x%p...\n", busy); + if (busy->next) + remove_queue(busy); + req.Function = MTD_REQ_ERASE | cause; + req.TransferLength = erase->Size; + req.DestCardOffset = erase->Offset + erase->Handle->info.CardOffset; + req.MediaID = erase->Handle->MediaID; + mtd = erase->Handle->mtd; + s = SOCKET(mtd); + mtd->event_callback_args.mtdrequest = &req; + ret = EVENT(mtd, CS_EVENT_MTD_REQUEST, CS_EVENT_PRI_LOW); + if (ret == CS_BUSY) { + DEBUG(2, " Status = %d, requeueing.\n", req.Status); + switch (req.Status) { + case MTD_WAITREQ: + case MTD_WAITPOWER: + insert_queue(&mtd->erase_busy, busy); + break; + case MTD_WAITTIMER: + case MTD_WAITRDY: + if (req.Status == MTD_WAITRDY) + insert_queue(&s->erase_busy, busy); + mod_timer(&busy->timeout, jiffies + req.Timeout*HZ/1000); + break; + } + } else { + /* update erase queue status */ + DEBUG(2, " Ret = %d\n", ret); + switch (ret) { + case CS_SUCCESS: + erase->State = ERASE_PASSED; break; + case CS_WRITE_PROTECTED: + erase->State = ERASE_MEDIA_WRPROT; break; + case CS_BAD_OFFSET: + erase->State = ERASE_BAD_OFFSET; break; + case CS_BAD_SIZE: + erase->State = ERASE_BAD_SIZE; break; + case CS_NO_CARD: + erase->State = ERASE_BAD_SOCKET; break; + default: + erase->State = ERASE_FAILED; break; + } + busy->client->event_callback_args.info = erase; + EVENT(busy->client, CS_EVENT_ERASE_COMPLETE, CS_EVENT_PRI_LOW); + kfree(busy); + /* Resubmit anything waiting for a request to finish */ + wake_up_interruptible(&mtd->mtd_req); + retry_erase_list(&mtd->erase_busy, 0); + } +} /* retry_erase */ + +void retry_erase_list(erase_busy_t *list, u_int cause) +{ + erase_busy_t tmp = *list; + + DEBUG(2, "cs: rescanning erase queue list 0x%p\n", list); + if (list->next == list) + return; + /* First, truncate the original list */ + list->prev->next = &tmp; + list->next->prev = &tmp; + list->prev = list->next = list; + tmp.prev->next = &tmp; + tmp.next->prev = &tmp; + + /* Now, retry each request, in order. */ + while (tmp.next != &tmp) + retry_erase(tmp.next, cause); +} /* retry_erase_list */ + +static void handle_erase_timeout(u_long arg) +{ + DEBUG(0, "cs: erase timeout for entry 0x%lx\n", arg); + retry_erase((erase_busy_t *)arg, MTD_REQ_TIMEOUT); +} + +static void setup_erase_request(client_handle_t handle, eraseq_entry_t *erase) +{ + erase_busy_t *busy; + region_info_t *info; + + if (CHECK_REGION(erase->Handle)) + erase->State = ERASE_BAD_SOCKET; + else { + info = &erase->Handle->info; + if ((erase->Offset >= info->RegionSize) || + (erase->Offset & (info->BlockSize-1))) + erase->State = ERASE_BAD_OFFSET; + else if ((erase->Offset+erase->Size > info->RegionSize) || + (erase->Size & (info->BlockSize-1))) + erase->State = ERASE_BAD_SIZE; + else { + erase->State = 1; + busy = kmalloc(sizeof(erase_busy_t), GFP_KERNEL); + busy->erase = erase; + busy->client = handle; + init_timer(&busy->timeout); + busy->timeout.data = (u_long)busy; + busy->timeout.function = &handle_erase_timeout; + busy->prev = busy->next = NULL; + retry_erase(busy, 0); + } + } +} /* setup_erase_request */ + +/*====================================================================== + + MTD helper functions + +======================================================================*/ + +static int mtd_modify_window(window_handle_t win, mtd_mod_win_t *req) +{ + if ((win == NULL) || (win->magic != WINDOW_MAGIC)) + return CS_BAD_HANDLE; + win->ctl.flags = MAP_16BIT | MAP_ACTIVE; + if (req->Attributes & WIN_USE_WAIT) + win->ctl.flags |= MAP_USE_WAIT; + if (req->Attributes & WIN_MEMORY_TYPE) + win->ctl.flags |= MAP_ATTRIB; + win->ctl.speed = req->AccessSpeed; + win->ctl.card_start = req->CardOffset; + win->sock->ss_entry(win->sock->sock, SS_SetMemMap, &win->ctl); + return CS_SUCCESS; +} + +static int mtd_set_vpp(client_handle_t handle, mtd_vpp_req_t *req) +{ + socket_info_t *s; + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + if (req->Vpp1 != req->Vpp2) + return CS_BAD_VPP; + s = SOCKET(handle); + s->socket.Vpp = req->Vpp1; + if (s->ss_entry(s->sock, SS_SetSocket, &s->socket)) + return CS_BAD_VPP; + return CS_SUCCESS; +} + +static int mtd_rdy_mask(client_handle_t handle, mtd_rdy_req_t *req) +{ + socket_info_t *s; + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + s = SOCKET(handle); + if (req->Mask & CS_EVENT_READY_CHANGE) + s->socket.csc_mask |= SS_READY; + else + s->socket.csc_mask &= ~SS_READY; + if (s->ss_entry(s->sock, SS_SetSocket, &s->socket)) + return CS_GENERAL_FAILURE; + return CS_SUCCESS; +} + +int MTDHelperEntry(int func, void *a1, void *a2) +{ + switch (func) { + case MTDRequestWindow: + return CardServices(RequestWindow, a1, a2, NULL); + case MTDReleaseWindow: + return CardServices(ReleaseWindow, a1, NULL, NULL); + case MTDModifyWindow: + return mtd_modify_window(a1, a2); break; + case MTDSetVpp: + return mtd_set_vpp(a1, a2); break; + case MTDRDYMask: + return mtd_rdy_mask(a1, a2); break; + default: + return CS_UNSUPPORTED_FUNCTION; break; + } +} /* MTDHelperEntry */ + +/*====================================================================== + + This stuff is used by Card Services to initialize the table of + region info used for subsequent calls to GetFirstRegion and + GetNextRegion. + +======================================================================*/ + +static void setup_regions(client_handle_t handle, int attr, + memory_handle_t *list) +{ + int i, code, has_jedec, has_geo; + u_int offset; + cistpl_device_t device; + cistpl_jedec_t jedec; + cistpl_device_geo_t geo; + memory_handle_t r; + + DEBUG(1, "cs: setup_regions(0x%p, %d, 0x%p)\n", + handle, attr, list); + + code = (attr) ? CISTPL_DEVICE_A : CISTPL_DEVICE; + if (read_tuple(handle, code, &device) != CS_SUCCESS) + return; + code = (attr) ? CISTPL_JEDEC_A : CISTPL_JEDEC_C; + has_jedec = (read_tuple(handle, code, &jedec) == CS_SUCCESS); + if (has_jedec && (device.ndev != jedec.nid)) { +#ifdef PCMCIA_DEBUG + printk(KERN_DEBUG "cs: Device info does not match JEDEC info.\n"); +#endif + has_jedec = 0; + } + code = (attr) ? CISTPL_DEVICE_GEO_A : CISTPL_DEVICE_GEO; + has_geo = (read_tuple(handle, code, &geo) == CS_SUCCESS); + if (has_geo && (device.ndev != geo.ngeo)) { +#ifdef PCMCIA_DEBUG + printk(KERN_DEBUG "cs: Device info does not match geometry tuple.\n"); +#endif + has_geo = 0; + } + + offset = 0; + for (i = 0; i < device.ndev; i++) { + if ((device.dev[i].type != CISTPL_DTYPE_NULL) && + (device.dev[i].size != 0)) { + r = kmalloc(sizeof(*r), GFP_KERNEL); + r->region_magic = REGION_MAGIC; + r->state = 0; + r->dev_info[0] = '\0'; + r->mtd = NULL; + r->info.Attributes = (attr) ? REGION_TYPE_AM : 0; + r->info.CardOffset = offset; + r->info.RegionSize = device.dev[i].size; + r->info.AccessSpeed = device.dev[i].speed; + if (has_jedec) { + r->info.JedecMfr = jedec.id[i].mfr; + r->info.JedecInfo = jedec.id[i].info; + } else + r->info.JedecMfr = r->info.JedecInfo = 0; + if (has_geo) { + r->info.BlockSize = geo.geo[i].buswidth * + geo.geo[i].erase_block * geo.geo[i].interleave; + r->info.PartMultiple = + r->info.BlockSize * geo.geo[i].partition; + } else + r->info.BlockSize = r->info.PartMultiple = 1; + r->info.next = *list; *list = r; + } + offset += device.dev[i].size; + } +} /* setup_regions */ + +/*====================================================================== + + This is tricky. When get_first_region() is called by Driver + Services, we initialize the region info table in the socket + structure. When it is called by an MTD, we can just scan the + table for matching entries. + +======================================================================*/ + +static int match_region(client_handle_t handle, memory_handle_t list, + region_info_t *match) +{ + while (list != NULL) { + if (!(handle->Attributes & INFO_MTD_CLIENT) || + (strcmp(handle->dev_info, list->dev_info) == 0)) { + *match = list->info; + return CS_SUCCESS; + } + list = list->info.next; + } + return CS_NO_MORE_ITEMS; +} /* match_region */ + +int get_first_region(client_handle_t handle, region_info_t *rgn) +{ + socket_info_t *s = SOCKET(handle); + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + + if ((handle->Attributes & INFO_MASTER_CLIENT) && + (!(s->state & SOCKET_REGION_INFO))) { + setup_regions(handle, 0, &s->c_region); + setup_regions(handle, 1, &s->a_region); + s->state |= SOCKET_REGION_INFO; + } + + if (rgn->Attributes & REGION_TYPE_AM) + return match_region(handle, s->a_region, rgn); + else + return match_region(handle, s->c_region, rgn); +} /* get_first_region */ + +int get_next_region(client_handle_t handle, region_info_t *rgn) +{ + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + return match_region(handle, rgn->next, rgn); +} /* get_next_region */ + +/*====================================================================== + + Connect an MTD with a memory region. + +======================================================================*/ + +int register_mtd(client_handle_t handle, mtd_reg_t *reg) +{ + memory_handle_t list; + socket_info_t *s; + + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + s = SOCKET(handle); + if (reg->Attributes & REGION_TYPE_AM) + list = s->a_region; + else + list = s->c_region; + DEBUG(1, "cs: register_mtd(0x%p, '%s', 0x%x)\n", + handle, handle->dev_info, reg->Offset); + while (list) { + if (list->info.CardOffset == reg->Offset) break; + list = list->info.next; + } + if (list && (list->mtd == NULL) && + (strcmp(handle->dev_info, list->dev_info) == 0)) { + list->info.Attributes = reg->Attributes; + list->MediaID = reg->MediaID; + list->mtd = handle; + handle->mtd_count++; + return CS_SUCCESS; + } else + return CS_BAD_OFFSET; +} /* register_mtd */ + +/*====================================================================== + + Erase queue management functions + +======================================================================*/ + +int register_erase_queue(client_handle_t *handle, eraseq_hdr_t *header) +{ + eraseq_t *queue; + + if ((handle == NULL) || CHECK_HANDLE(*handle)) + return CS_BAD_HANDLE; + queue = kmalloc(sizeof(*queue), GFP_KERNEL); + if (!queue) return CS_OUT_OF_RESOURCE; + queue->eraseq_magic = ERASEQ_MAGIC; + queue->handle = *handle; + queue->count = header->QueueEntryCnt; + queue->entry = header->QueueEntryArray; + *handle = (client_handle_t)queue; + return CS_SUCCESS; +} /* register_erase_queue */ + +int deregister_erase_queue(eraseq_handle_t eraseq) +{ + int i; + if (CHECK_ERASEQ(eraseq)) + return CS_BAD_HANDLE; + for (i = 0; i < eraseq->count; i++) + if (ERASE_IN_PROGRESS(eraseq->entry[i].State)) break; + if (i < eraseq->count) + return CS_BUSY; + eraseq->eraseq_magic = 0; + kfree(eraseq); + return CS_SUCCESS; +} /* deregister_erase_queue */ + +int check_erase_queue(eraseq_handle_t eraseq) +{ + int i; + if (CHECK_ERASEQ(eraseq)) + return CS_BAD_HANDLE; + for (i = 0; i < eraseq->count; i++) + if (eraseq->entry[i].State == ERASE_QUEUED) + setup_erase_request(eraseq->handle, &eraseq->entry[i]); + return CS_SUCCESS; +} /* check_erase_queue */ + +/*====================================================================== + + Look up the memory region matching the request, and return a + memory handle. + +======================================================================*/ + +int open_memory(client_handle_t *handle, open_mem_t *open) +{ + socket_info_t *s; + memory_handle_t region; + + if ((handle == NULL) || CHECK_HANDLE(*handle)) + return CS_BAD_HANDLE; + s = SOCKET(*handle); + if (open->Attributes & MEMORY_TYPE_AM) + region = s->a_region; + else + region = s->c_region; + while (region) { + if (region->info.CardOffset == open->Offset) break; + region = region->info.next; + } + if (region && region->mtd) { + *handle = (client_handle_t)region; + DEBUG(1, "cs: open_memory(0x%p, 0x%x) = 0x%p\n", + handle, open->Offset, region); + return CS_SUCCESS; + } else + return CS_BAD_OFFSET; +} /* open_memory */ + +/*====================================================================== + + Close a memory handle from an earlier call to OpenMemory. + + For the moment, I don't think this needs to do anything. + +======================================================================*/ + +int close_memory(memory_handle_t handle) +{ + DEBUG(1, "cs: close_memory(0x%p)\n", handle); + if (CHECK_REGION(handle)) + return CS_BAD_HANDLE; + return CS_SUCCESS; +} /* close_memory */ + +/*====================================================================== + + Read from a memory device, using a handle previously returned + by a call to OpenMemory. + +======================================================================*/ + +int read_memory(memory_handle_t handle, mem_op_t *req, caddr_t buf) +{ + mtd_request_t mtd; + if (CHECK_REGION(handle)) + return CS_BAD_HANDLE; + if (req->Offset >= handle->info.RegionSize) + return CS_BAD_OFFSET; + if (req->Offset+req->Count > handle->info.RegionSize) + return CS_BAD_SIZE; + + mtd.SrcCardOffset = req->Offset + handle->info.CardOffset; + mtd.TransferLength = req->Count; + mtd.MediaID = handle->MediaID; + mtd.Function = MTD_REQ_READ; + if (req->Attributes & MEM_OP_BUFFER_KERNEL) + mtd.Function |= MTD_REQ_KERNEL; + return do_mtd_request(handle, &mtd, buf); +} /* read_memory */ + +/*====================================================================== + + Write to a memory device, using a handle previously returned by + a call to OpenMemory. + +======================================================================*/ + +int write_memory(memory_handle_t handle, mem_op_t *req, caddr_t buf) +{ + mtd_request_t mtd; + if (CHECK_REGION(handle)) + return CS_BAD_HANDLE; + if (req->Offset >= handle->info.RegionSize) + return CS_BAD_OFFSET; + if (req->Offset+req->Count > handle->info.RegionSize) + return CS_BAD_SIZE; + + mtd.DestCardOffset = req->Offset + handle->info.CardOffset; + mtd.TransferLength = req->Count; + mtd.MediaID = handle->MediaID; + mtd.Function = MTD_REQ_WRITE; + if (req->Attributes & MEM_OP_BUFFER_KERNEL) + mtd.Function |= MTD_REQ_KERNEL; + return do_mtd_request(handle, &mtd, buf); +} /* write_memory */ + +/*====================================================================== + + This isn't needed for anything I could think of. + +======================================================================*/ + +int copy_memory(memory_handle_t handle, copy_op_t *req) +{ + if (CHECK_REGION(handle)) + return CS_BAD_HANDLE; + return CS_UNSUPPORTED_FUNCTION; +} + diff --git a/linux/pcmcia-cs/modules/cirrus.h b/linux/pcmcia-cs/modules/cirrus.h new file mode 100644 index 0000000..e3bb255 --- /dev/null +++ b/linux/pcmcia-cs/modules/cirrus.h @@ -0,0 +1,188 @@ +/* + * cirrus.h 1.11 2003/09/09 07:05:40 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in + * which case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use + * your version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + */ + +#ifndef _LINUX_CIRRUS_H +#define _LINUX_CIRRUS_H + +#ifndef PCI_VENDOR_ID_CIRRUS +#define PCI_VENDOR_ID_CIRRUS 0x1013 +#endif +#ifndef PCI_DEVICE_ID_CIRRUS_6729 +#define PCI_DEVICE_ID_CIRRUS_6729 0x1100 +#endif +#ifndef PCI_DEVICE_ID_CIRRUS_6832 +#define PCI_DEVICE_ID_CIRRUS_6832 0x1110 +#endif + +#define PD67_MISC_CTL_1 0x16 /* Misc control 1 */ +#define PD67_FIFO_CTL 0x17 /* FIFO control */ +#define PD67_MISC_CTL_2 0x1E /* Misc control 2 */ +#define PD67_CHIP_INFO 0x1f /* Chip information */ +#define PD67_ATA_CTL 0x026 /* 6730: ATA control */ +#define PD67_EXT_INDEX 0x2e /* Extension index */ +#define PD67_EXT_DATA 0x2f /* Extension data */ + +#define pd67_ext_get(s, r) \ + (i365_set(s, PD67_EXT_INDEX, r), i365_get(s, PD67_EXT_DATA)) +#define pd67_ext_set(s, r, v) \ + (i365_set(s, PD67_EXT_INDEX, r), i365_set(s, PD67_EXT_DATA, v)) + +/* PD6722 extension registers -- indexed in PD67_EXT_INDEX */ +#define PD67_DATA_MASK0 0x01 /* Data mask 0 */ +#define PD67_DATA_MASK1 0x02 /* Data mask 1 */ +#define PD67_DMA_CTL 0x03 /* DMA control */ + +/* PD6730 extension registers -- indexed in PD67_EXT_INDEX */ +#define PD67_EXT_CTL_1 0x03 /* Extension control 1 */ +#define PD67_MEM_PAGE(n) ((n)+5) /* PCI window bits 31:24 */ +#define PD67_EXTERN_DATA 0x0a +#define PD67_EXT_CTL_2 0x0b +#define PD67_MISC_CTL_3 0x25 +#define PD67_SMB_PWR_CTL 0x26 + +/* I/O window address offset */ +#define PD67_IO_OFF(w) (0x36+((w)<<1)) + +/* Timing register sets */ +#define PD67_TIME_SETUP(n) (0x3a + 3*(n)) +#define PD67_TIME_CMD(n) (0x3b + 3*(n)) +#define PD67_TIME_RECOV(n) (0x3c + 3*(n)) + +/* Flags for PD67_MISC_CTL_1 */ +#define PD67_MC1_5V_DET 0x01 /* 5v detect */ +#define PD67_MC1_MEDIA_ENA 0x01 /* 6730: Multimedia enable */ +#define PD67_MC1_VCC_3V 0x02 /* 3.3v Vcc */ +#define PD67_MC1_PULSE_MGMT 0x04 +#define PD67_MC1_PULSE_IRQ 0x08 +#define PD67_MC1_SPKR_ENA 0x10 +#define PD67_MC1_INPACK_ENA 0x80 + +/* Flags for PD67_FIFO_CTL */ +#define PD67_FIFO_EMPTY 0x80 + +/* Flags for PD67_MISC_CTL_2 */ +#define PD67_MC2_FREQ_BYPASS 0x01 +#define PD67_MC2_DYNAMIC_MODE 0x02 +#define PD67_MC2_SUSPEND 0x04 +#define PD67_MC2_5V_CORE 0x08 +#define PD67_MC2_LED_ENA 0x10 /* IRQ 12 is LED enable */ +#define PD67_MC2_FAST_PCI 0x10 /* 6729: PCI bus > 25 MHz */ +#define PD67_MC2_3STATE_BIT7 0x20 /* Floppy change bit */ +#define PD67_MC2_DMA_MODE 0x40 +#define PD67_MC2_IRQ15_RI 0x80 /* IRQ 15 is ring enable */ + +/* Flags for PD67_CHIP_INFO */ +#define PD67_INFO_SLOTS 0x20 /* 0 = 1 slot, 1 = 2 slots */ +#define PD67_INFO_CHIP_ID 0xc0 +#define PD67_INFO_REV 0x1c + +/* Fields in PD67_TIME_* registers */ +#define PD67_TIME_SCALE 0xc0 +#define PD67_TIME_SCALE_1 0x00 +#define PD67_TIME_SCALE_16 0x40 +#define PD67_TIME_SCALE_256 0x80 +#define PD67_TIME_SCALE_4096 0xc0 +#define PD67_TIME_MULT 0x3f + +/* Fields in PD67_DMA_CTL */ +#define PD67_DMA_MODE 0xc0 +#define PD67_DMA_OFF 0x00 +#define PD67_DMA_DREQ_INPACK 0x40 +#define PD67_DMA_DREQ_WP 0x80 +#define PD67_DMA_DREQ_BVD2 0xc0 +#define PD67_DMA_PULLUP 0x20 /* Disable socket pullups? */ + +/* Fields in PD67_EXT_CTL_1 */ +#define PD67_EC1_VCC_PWR_LOCK 0x01 +#define PD67_EC1_AUTO_PWR_CLEAR 0x02 +#define PD67_EC1_LED_ENA 0x04 +#define PD67_EC1_INV_CARD_IRQ 0x08 +#define PD67_EC1_INV_MGMT_IRQ 0x10 +#define PD67_EC1_PULLUP_CTL 0x20 + +/* Fields in PD67_EXTERN_DATA */ +#define PD67_EXD_VS1(s) (0x01 << ((s)<<1)) +#define PD67_EXD_VS2(s) (0x02 << ((s)<<1)) + +/* Fields in PD67_EXT_CTL_2 */ +#define PD67_EC2_GPSTB_TOTEM 0x04 +#define PD67_EC2_GPSTB_IOR 0x08 +#define PD67_EC2_GPSTB_IOW 0x10 +#define PD67_EC2_GPSTB_HIGH 0x20 + +/* Fields in PD67_MISC_CTL_3 */ +#define PD67_MC3_IRQ_MASK 0x03 +#define PD67_MC3_IRQ_PCPCI 0x00 +#define PD67_MC3_IRQ_EXTERN 0x01 +#define PD67_MC3_IRQ_PCIWAY 0x02 +#define PD67_MC3_IRQ_PCI 0x03 +#define PD67_MC3_PWR_MASK 0x0c +#define PD67_MC3_PWR_SERIAL 0x00 +#define PD67_MC3_PWR_TI2202 0x08 +#define PD67_MC3_PWR_SMB 0x0c + +/* Register definitions for Cirrus PD6832 PCI-to-CardBus bridge */ + +/* PD6832 extension registers -- indexed in PD67_EXT_INDEX */ +#define PD68_PCI_SPACE 0x22 +#define PD68_PCCARD_SPACE 0x23 +#define PD68_WINDOW_TYPE 0x24 +#define PD68_EXT_CSC 0x2e +#define PD68_MISC_CTL_4 0x2f +#define PD68_MISC_CTL_5 0x30 +#define PD68_MISC_CTL_6 0x31 + +/* Extra flags in PD67_MISC_CTL_3 */ +#define PD68_MC3_HW_SUSP 0x10 +#define PD68_MC3_MM_EXPAND 0x40 +#define PD68_MC3_MM_ARM 0x80 + +/* Bridge Control Register */ +#define PD6832_BCR_MGMT_IRQ_ENA 0x0800 + +/* Socket Number Register */ +#define PD6832_SOCKET_NUMBER 0x004c /* 8 bit */ + +/* Data structure for tracking vendor-specific state */ +typedef struct cirrus_state_t { + u_char misc1; /* PD67_MISC_CTL_1 */ + u_char misc2; /* PD67_MISC_CTL_2 */ + u_char ectl1; /* PD67_EXT_CTL_1 */ + u_char timer[6]; /* PD67_TIME_* */ +} cirrus_state_t; + +#define CIRRUS_PCIC_ID \ + IS_PD6729, IS_PD6730, IS_PD6832 + +#define CIRRUS_PCIC_INFO \ + { "Cirrus PD6729", IS_CIRRUS|IS_PCI, ID(CIRRUS, 6729) }, \ + { "Cirrus PD6730", IS_CIRRUS|IS_PCI, PCI_VENDOR_ID_CIRRUS, -1 }, \ + { "Cirrus PD6832", IS_CIRRUS|IS_CARDBUS, ID(CIRRUS, 6832) } + +#endif /* _LINUX_CIRRUS_H */ diff --git a/linux/pcmcia-cs/modules/cistpl.c b/linux/pcmcia-cs/modules/cistpl.c new file mode 100644 index 0000000..404b8e4 --- /dev/null +++ b/linux/pcmcia-cs/modules/cistpl.c @@ -0,0 +1,1502 @@ +/*====================================================================== + + PCMCIA Card Information Structure parser + + cistpl.c 1.101 2003/12/15 03:58:03 + + The contents of this file are subject to the Mozilla Public + License Version 1.1 (the "License"); you may not use this file + except in compliance with the License. You may obtain a copy of + the License at http://www.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS + IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + implied. See the License for the specific language governing + rights and limitations under the License. + + The initial developer of the original code is David A. Hinds + <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + + Alternatively, the contents of this file may be used under the + terms of the GNU General Public License version 2 (the "GPL"), in + which case the provisions of the GPL are applicable instead of the + above. If you wish to allow the use of your version of this file + only under the terms of the GPL and not to allow others to use + your version of this file under the MPL, indicate your decision + by deleting the provisions above and replace them with the notice + and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this + file under either the MPL or the GPL. + +======================================================================*/ + +#define __NO_VERSION__ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/major.h> +#include <linux/errno.h> +#include <linux/timer.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/sched.h> +#include <linux/pci.h> +#include <linux/ioport.h> +#include <asm/io.h> +#include <asm/byteorder.h> + +#include <pcmcia/cs_types.h> +#include <pcmcia/bus_ops.h> +#include <pcmcia/ss.h> +#include <pcmcia/cs.h> +#include <pcmcia/bulkmem.h> +#include <pcmcia/cisreg.h> +#include <pcmcia/cistpl.h> +#include "cs_internal.h" + +static const u_char mantissa[] = { + 10, 12, 13, 15, 20, 25, 30, 35, + 40, 45, 50, 55, 60, 70, 80, 90 +}; + +static const u_int exponent[] = { + 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000 +}; + +/* Convert an extended speed byte to a time in nanoseconds */ +#define SPEED_CVT(v) \ + (mantissa[(((v)>>3)&15)-1] * exponent[(v)&7] / 10) +/* Convert a power byte to a current in 0.1 microamps */ +#define POWER_CVT(v) \ + (mantissa[((v)>>3)&15] * exponent[(v)&7] / 10) +#define POWER_SCALE(v) (exponent[(v)&7]) + +/* Upper limit on reasonable # of tuples */ +#define MAX_TUPLES 200 + +/*====================================================================*/ + +/* Parameters that can be set with 'insmod' */ + +#define INT_MODULE_PARM(n, v) static int n = v; MODULE_PARM(n, "i") + +INT_MODULE_PARM(cis_width, 0); /* 16-bit CIS? */ + +/*====================================================================== + + Low-level functions to read and write CIS memory. I think the + write routine is only useful for writing one-byte registers. + +======================================================================*/ + +/* Bits in attr field */ +#define IS_ATTR 1 +#define IS_INDIRECT 8 + +static int setup_cis_mem(socket_info_t *s); + +static void set_cis_map(socket_info_t *s, pccard_mem_map *mem) +{ + s->ss_entry(s->sock, SS_SetMemMap, mem); + if (s->cap.features & SS_CAP_STATIC_MAP) { + if (s->cis_virt) + bus_iounmap(s->cap.bus, s->cis_virt); + s->cis_virt = bus_ioremap(s->cap.bus, mem->sys_start, + s->cap.map_size); + } +} + +int read_cis_mem(socket_info_t *s, int attr, u_int addr, + u_int len, void *ptr) +{ + pccard_mem_map *mem = &s->cis_mem; + u_char *sys, *buf = ptr; + + DEBUG(3, "cs: read_cis_mem(%d, %#x, %u)\n", attr, addr, len); + if (setup_cis_mem(s) != 0) { + memset(ptr, 0xff, len); + return -1; + } + mem->flags = MAP_ACTIVE | ((cis_width) ? MAP_16BIT : 0); + + if (attr & IS_INDIRECT) { + /* Indirect accesses use a bunch of special registers at fixed + locations in common memory */ + u_char flags = ICTRL0_COMMON|ICTRL0_AUTOINC|ICTRL0_BYTEGRAN; + if (attr & IS_ATTR) { addr *= 2; flags = ICTRL0_AUTOINC; } + mem->card_start = 0; mem->flags = MAP_ACTIVE; + set_cis_map(s, mem); + sys = s->cis_virt; + bus_writeb(s->cap.bus, flags, sys+CISREG_ICTRL0); + bus_writeb(s->cap.bus, addr & 0xff, sys+CISREG_IADDR0); + bus_writeb(s->cap.bus, (addr>>8) & 0xff, sys+CISREG_IADDR1); + bus_writeb(s->cap.bus, (addr>>16) & 0xff, sys+CISREG_IADDR2); + bus_writeb(s->cap.bus, (addr>>24) & 0xff, sys+CISREG_IADDR3); + for ( ; len > 0; len--, buf++) + *buf = bus_readb(s->cap.bus, sys+CISREG_IDATA0); + } else { + u_int inc = 1; + if (attr) { mem->flags |= MAP_ATTRIB; inc++; addr *= 2; } + sys += (addr & (s->cap.map_size-1)); + mem->card_start = addr & ~(s->cap.map_size-1); + while (len) { + set_cis_map(s, mem); + sys = s->cis_virt + (addr & (s->cap.map_size-1)); + for ( ; len > 0; len--, buf++, sys += inc) { + if (sys == s->cis_virt+s->cap.map_size) break; + *buf = bus_readb(s->cap.bus, sys); + } + mem->card_start += s->cap.map_size; + addr = 0; + } + } + DEBUG(3, "cs: %#2.2x %#2.2x %#2.2x %#2.2x ...\n", + *(u_char *)(ptr+0), *(u_char *)(ptr+1), + *(u_char *)(ptr+2), *(u_char *)(ptr+3)); + return 0; +} + +void write_cis_mem(socket_info_t *s, int attr, u_int addr, + u_int len, void *ptr) +{ + pccard_mem_map *mem = &s->cis_mem; + u_char *sys, *buf = ptr; + + DEBUG(3, "cs: write_cis_mem(%d, %#x, %u)\n", attr, addr, len); + if (setup_cis_mem(s) != 0) return; + mem->flags = MAP_ACTIVE | ((cis_width) ? MAP_16BIT : 0); + + if (attr & IS_INDIRECT) { + /* Indirect accesses use a bunch of special registers at fixed + locations in common memory */ + u_char flags = ICTRL0_COMMON|ICTRL0_AUTOINC|ICTRL0_BYTEGRAN; + if (attr & IS_ATTR) { addr *= 2; flags = ICTRL0_AUTOINC; } + mem->card_start = 0; mem->flags = MAP_ACTIVE; + set_cis_map(s, mem); + sys = s->cis_virt; + bus_writeb(s->cap.bus, flags, sys+CISREG_ICTRL0); + bus_writeb(s->cap.bus, addr & 0xff, sys+CISREG_IADDR0); + bus_writeb(s->cap.bus, (addr>>8) & 0xff, sys+CISREG_IADDR1); + bus_writeb(s->cap.bus, (addr>>16) & 0xff, sys+CISREG_IADDR2); + bus_writeb(s->cap.bus, (addr>>24) & 0xff, sys+CISREG_IADDR3); + for ( ; len > 0; len--, buf++) + bus_writeb(s->cap.bus, *buf, sys+CISREG_IDATA0); + } else { + int inc = 1; + if (attr & IS_ATTR) { mem->flags |= MAP_ATTRIB; inc++; addr *= 2; } + mem->card_start = addr & ~(s->cap.map_size-1); + while (len) { + set_cis_map(s, mem); + sys = s->cis_virt + (addr & (s->cap.map_size-1)); + for ( ; len > 0; len--, buf++, sys += inc) { + if (sys == s->cis_virt+s->cap.map_size) break; + bus_writeb(s->cap.bus, *buf, sys); + } + mem->card_start += s->cap.map_size; + addr = 0; + } + } +} + +/*====================================================================== + + This is tricky... when we set up CIS memory, we try to validate + the memory window space allocations. + +======================================================================*/ + +/* Scratch pointer to the socket we use for validation */ +static socket_info_t *vs = NULL; + +/* Validation function for cards with a valid CIS */ +static int cis_readable(u_long base) +{ + cisinfo_t info1, info2; + int ret; + vs->cis_mem.sys_start = base; + vs->cis_mem.sys_stop = base+vs->cap.map_size-1; + vs->cis_virt = bus_ioremap(vs->cap.bus, base, vs->cap.map_size); + ret = validate_cis(vs->clients, &info1); + /* invalidate mapping and CIS cache */ + bus_iounmap(vs->cap.bus, vs->cis_virt); vs->cis_used = 0; + if ((ret != 0) || (info1.Chains == 0)) + return 0; + vs->cis_mem.sys_start = base+vs->cap.map_size; + vs->cis_mem.sys_stop = base+2*vs->cap.map_size-1; + vs->cis_virt = bus_ioremap(vs->cap.bus, base+vs->cap.map_size, + vs->cap.map_size); + ret = validate_cis(vs->clients, &info2); + bus_iounmap(vs->cap.bus, vs->cis_virt); vs->cis_used = 0; + return ((ret == 0) && (info1.Chains == info2.Chains)); +} + +/* Validation function for simple memory cards */ +static int checksum(u_long base) +{ + int i, a, b, d; + vs->cis_mem.sys_start = base; + vs->cis_mem.sys_stop = base+vs->cap.map_size-1; + vs->cis_virt = bus_ioremap(vs->cap.bus, base, vs->cap.map_size); + vs->cis_mem.card_start = 0; + vs->cis_mem.flags = MAP_ACTIVE; + vs->ss_entry(vs->sock, SS_SetMemMap, &vs->cis_mem); + /* Don't bother checking every word... */ + a = 0; b = -1; + for (i = 0; i < vs->cap.map_size; i += 44) { + d = bus_readl(vs->cap.bus, vs->cis_virt+i); + a += d; b &= d; + } + bus_iounmap(vs->cap.bus, vs->cis_virt); + return (b == -1) ? -1 : (a>>1); +} + +static int checksum_match(u_long base) +{ + int a = checksum(base), b = checksum(base+vs->cap.map_size); + return ((a == b) && (a >= 0)); +} + +static int setup_cis_mem(socket_info_t *s) +{ + if (!(s->cap.features & SS_CAP_STATIC_MAP) && + (s->cis_mem.sys_start == 0)) { + int low = !(s->cap.features & SS_CAP_PAGE_REGS); + vs = s; + validate_mem(cis_readable, checksum_match, low); + s->cis_mem.sys_start = 0; + vs = NULL; + if (find_mem_region(&s->cis_mem.sys_start, s->cap.map_size, + s->cap.map_size, low, "card services")) { + printk(KERN_NOTICE "cs: unable to map card memory!\n"); + return -1; + } + s->cis_mem.sys_stop = s->cis_mem.sys_start+s->cap.map_size-1; + s->cis_virt = bus_ioremap(s->cap.bus, s->cis_mem.sys_start, + s->cap.map_size); + } + return 0; +} + +void release_cis_mem(socket_info_t *s) +{ + if (s->cis_mem.sys_start != 0) { + s->cis_mem.flags &= ~MAP_ACTIVE; + s->ss_entry(s->sock, SS_SetMemMap, &s->cis_mem); + if (!(s->cap.features & SS_CAP_STATIC_MAP)) + release_mem_region(s->cis_mem.sys_start, s->cap.map_size); + bus_iounmap(s->cap.bus, s->cis_virt); + s->cis_mem.sys_start = 0; + s->cis_virt = NULL; + } +} + +/*====================================================================== + + This is a wrapper around read_cis_mem, with the same interface, + but which caches information, for cards whose CIS may not be + readable all the time. + +======================================================================*/ + +static void read_cis_cache(socket_info_t *s, int attr, u_int addr, + u_int len, void *ptr) +{ + int i, ret; + char *caddr; + + if (s->fake_cis) { + if (s->fake_cis_len > addr+len) + memcpy(ptr, s->fake_cis+addr, len); + else + memset(ptr, 0xff, len); + return; + } + caddr = s->cis_cache; + for (i = 0; i < s->cis_used; i++) { + if ((s->cis_table[i].addr == addr) && + (s->cis_table[i].len == len) && + (s->cis_table[i].attr == attr)) break; + caddr += s->cis_table[i].len; + } + if (i < s->cis_used) { + memcpy(ptr, caddr, len); + return; + } +#ifdef CONFIG_CARDBUS + if (s->state & SOCKET_CARDBUS) + ret = read_cb_mem(s, 0, attr, addr, len, ptr); + else +#endif + ret = read_cis_mem(s, attr, addr, len, ptr); + /* Copy data into the cache, if there is room */ + if ((ret == 0) && (i < MAX_CIS_TABLE) && + (caddr+len < s->cis_cache+MAX_CIS_DATA)) { + s->cis_table[i].addr = addr; + s->cis_table[i].len = len; + s->cis_table[i].attr = attr; + s->cis_used++; + memcpy(caddr, ptr, len); + } +} + +/*====================================================================== + + This verifies if the CIS of a card matches what is in the CIS + cache. + +======================================================================*/ + +int verify_cis_cache(socket_info_t *s) +{ + char *buf, *caddr; + int i; + + buf = kmalloc(256, GFP_KERNEL); + if (buf == NULL) + return -1; + caddr = s->cis_cache; + for (i = 0; i < s->cis_used; i++) { +#ifdef CONFIG_CARDBUS + if (s->state & SOCKET_CARDBUS) + read_cb_mem(s, 0, s->cis_table[i].attr, s->cis_table[i].addr, + s->cis_table[i].len, buf); + else +#endif + read_cis_mem(s, s->cis_table[i].attr, s->cis_table[i].addr, + s->cis_table[i].len, buf); + if (memcmp(buf, caddr, s->cis_table[i].len) != 0) + break; + caddr += s->cis_table[i].len; + } + kfree(buf); + return (i < s->cis_used); +} + +/*====================================================================== + + For really bad cards, we provide a facility for uploading a + replacement CIS. + +======================================================================*/ + +int replace_cis(client_handle_t handle, cisdump_t *cis) +{ + socket_info_t *s; + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + s = SOCKET(handle); + if (s->fake_cis != NULL) { + kfree(s->fake_cis); + s->fake_cis = NULL; + } + if (cis->Length > CISTPL_MAX_CIS_SIZE) + return CS_BAD_SIZE; + s->fake_cis = kmalloc(cis->Length, GFP_KERNEL); + if (s->fake_cis == NULL) + return CS_OUT_OF_RESOURCE; + s->fake_cis_len = cis->Length; + memcpy(s->fake_cis, cis->Data, cis->Length); + return CS_SUCCESS; +} + +/*====================================================================== + + The high-level CIS tuple services + +======================================================================*/ + +typedef struct tuple_flags { + u_int link_space:4; + u_int has_link:1; + u_int mfc_fn:3; + u_int space:4; +} tuple_flags; + +#define LINK_SPACE(f) (((tuple_flags *)(&(f)))->link_space) +#define HAS_LINK(f) (((tuple_flags *)(&(f)))->has_link) +#define MFC_FN(f) (((tuple_flags *)(&(f)))->mfc_fn) +#define SPACE(f) (((tuple_flags *)(&(f)))->space) + +int get_next_tuple(client_handle_t handle, tuple_t *tuple); + +int get_first_tuple(client_handle_t handle, tuple_t *tuple) +{ + socket_info_t *s; + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + s = SOCKET(handle); + if (!(s->state & SOCKET_PRESENT)) + return CS_NO_CARD; + tuple->TupleLink = tuple->Flags = 0; +#ifdef CONFIG_CARDBUS + if (s->state & SOCKET_CARDBUS) { + u_int ptr; + pcibios_read_config_dword(s->cap.cardbus, 0, 0x28, &ptr); + tuple->CISOffset = ptr & ~7; + SPACE(tuple->Flags) = (ptr & 7); + } else +#endif + { + /* Assume presence of a LONGLINK_C to address 0 */ + tuple->CISOffset = tuple->LinkOffset = 0; + SPACE(tuple->Flags) = HAS_LINK(tuple->Flags) = 1; + } + if (!(s->state & SOCKET_CARDBUS) && (s->functions > 1) && + !(tuple->Attributes & TUPLE_RETURN_COMMON)) { + cisdata_t req = tuple->DesiredTuple; + tuple->DesiredTuple = CISTPL_LONGLINK_MFC; + if (get_next_tuple(handle, tuple) == CS_SUCCESS) { + tuple->DesiredTuple = CISTPL_LINKTARGET; + if (get_next_tuple(handle, tuple) != CS_SUCCESS) + return CS_NO_MORE_ITEMS; + } else + tuple->CISOffset = tuple->TupleLink = 0; + tuple->DesiredTuple = req; + } + return get_next_tuple(handle, tuple); +} + +static int follow_link(socket_info_t *s, tuple_t *tuple) +{ + u_char link[5]; + u_int ofs; + + if (MFC_FN(tuple->Flags)) { + /* Get indirect link from the MFC tuple */ + read_cis_cache(s, LINK_SPACE(tuple->Flags), + tuple->LinkOffset, 5, link); + ofs = le32_to_cpu(*(u_int *)(link+1)); + SPACE(tuple->Flags) = (link[0] == CISTPL_MFC_ATTR); + /* Move to the next indirect link */ + tuple->LinkOffset += 5; + MFC_FN(tuple->Flags)--; + } else if (HAS_LINK(tuple->Flags)) { + ofs = tuple->LinkOffset; + SPACE(tuple->Flags) = LINK_SPACE(tuple->Flags); + HAS_LINK(tuple->Flags) = 0; + } else { + return -1; + } + if (!(s->state & SOCKET_CARDBUS) && SPACE(tuple->Flags)) { + /* This is ugly, but a common CIS error is to code the long + link offset incorrectly, so we check the right spot... */ + read_cis_cache(s, SPACE(tuple->Flags), ofs, 5, link); + if ((link[0] == CISTPL_LINKTARGET) && (link[1] >= 3) && + (strncmp(link+2, "CIS", 3) == 0)) + return ofs; + /* Then, we try the wrong spot... */ + ofs = ofs >> 1; + } + read_cis_cache(s, SPACE(tuple->Flags), ofs, 5, link); + if ((link[0] != CISTPL_LINKTARGET) || (link[1] < 3) || + (strncmp(link+2, "CIS", 3) != 0)) + return -1; + return ofs; +} + +int get_next_tuple(client_handle_t handle, tuple_t *tuple) +{ + socket_info_t *s; + u_char link[2], tmp; + int ofs, i, attr; + + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + s = SOCKET(handle); + if (!(s->state & SOCKET_PRESENT)) + return CS_NO_CARD; + + link[1] = tuple->TupleLink; + ofs = tuple->CISOffset + tuple->TupleLink; + attr = SPACE(tuple->Flags); + + for (i = 0; i < MAX_TUPLES; i++) { + if (link[1] == 0xff) { + link[0] = CISTPL_END; + } else { + read_cis_cache(s, attr, ofs, 2, link); + if (link[0] == CISTPL_NULL) { + ofs++; continue; + } + } + + /* End of chain? Follow long link if possible */ + if (link[0] == CISTPL_END) { + if ((ofs = follow_link(s, tuple)) < 0) + return CS_NO_MORE_ITEMS; + attr = SPACE(tuple->Flags); + read_cis_cache(s, attr, ofs, 2, link); + } + + /* Is this a link tuple? Make a note of it */ + if ((link[0] == CISTPL_LONGLINK_A) || + (link[0] == CISTPL_LONGLINK_C) || + (link[0] == CISTPL_LONGLINK_MFC) || + (link[0] == CISTPL_LINKTARGET) || + (link[0] == CISTPL_INDIRECT) || + (link[0] == CISTPL_NO_LINK)) { + switch (link[0]) { + case CISTPL_LONGLINK_A: + HAS_LINK(tuple->Flags) = 1; + LINK_SPACE(tuple->Flags) = attr | IS_ATTR; + read_cis_cache(s, attr, ofs+2, 4, &tuple->LinkOffset); + break; + case CISTPL_LONGLINK_C: + HAS_LINK(tuple->Flags) = 1; + LINK_SPACE(tuple->Flags) = attr & ~IS_ATTR; + read_cis_cache(s, attr, ofs+2, 4, &tuple->LinkOffset); + break; + case CISTPL_INDIRECT: + HAS_LINK(tuple->Flags) = 1; + LINK_SPACE(tuple->Flags) = IS_ATTR | IS_INDIRECT; + tuple->LinkOffset = 0; + break; + case CISTPL_LONGLINK_MFC: + tuple->LinkOffset = ofs + 3; + LINK_SPACE(tuple->Flags) = attr; + if (handle->Function == BIND_FN_ALL) { + /* Follow all the MFC links */ + read_cis_cache(s, attr, ofs+2, 1, &tmp); + MFC_FN(tuple->Flags) = tmp; + } else { + /* Follow exactly one of the links */ + MFC_FN(tuple->Flags) = 1; + tuple->LinkOffset += handle->Function * 5; + } + break; + case CISTPL_NO_LINK: + HAS_LINK(tuple->Flags) = 0; + break; + } + if ((tuple->Attributes & TUPLE_RETURN_LINK) && + (tuple->DesiredTuple == RETURN_FIRST_TUPLE)) + break; + } else + if (tuple->DesiredTuple == RETURN_FIRST_TUPLE) + break; + + if (link[0] == tuple->DesiredTuple) + break; + ofs += link[1] + 2; + } + if (i == MAX_TUPLES) { + DEBUG(1, "cs: overrun in get_next_tuple for socket %d\n", + handle->Socket); + return CS_NO_MORE_ITEMS; + } + + tuple->TupleCode = link[0]; + tuple->TupleLink = link[1]; + tuple->CISOffset = ofs + 2; + return CS_SUCCESS; +} + +/*====================================================================*/ + +#define _MIN(a, b) (((a) < (b)) ? (a) : (b)) + +int get_tuple_data(client_handle_t handle, tuple_t *tuple) +{ + socket_info_t *s; + u_int len; + + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + + s = SOCKET(handle); + + if (tuple->TupleLink < tuple->TupleOffset) + return CS_NO_MORE_ITEMS; + len = tuple->TupleLink - tuple->TupleOffset; + tuple->TupleDataLen = tuple->TupleLink; + if (len == 0) + return CS_SUCCESS; + read_cis_cache(s, SPACE(tuple->Flags), + tuple->CISOffset + tuple->TupleOffset, + _MIN(len, tuple->TupleDataMax), tuple->TupleData); + return CS_SUCCESS; +} + +/*====================================================================== + + Parsing routines for individual tuples + +======================================================================*/ + +static int parse_device(tuple_t *tuple, cistpl_device_t *device) +{ + int i; + u_char scale; + u_char *p, *q; + + p = (u_char *)tuple->TupleData; + q = p + tuple->TupleDataLen; + + device->ndev = 0; + for (i = 0; i < CISTPL_MAX_DEVICES; i++) { + + if (*p == 0xff) break; + device->dev[i].type = (*p >> 4); + device->dev[i].wp = (*p & 0x08) ? 1 : 0; + switch (*p & 0x07) { + case 0: device->dev[i].speed = 0; break; + case 1: device->dev[i].speed = 250; break; + case 2: device->dev[i].speed = 200; break; + case 3: device->dev[i].speed = 150; break; + case 4: device->dev[i].speed = 100; break; + case 7: + if (++p == q) return CS_BAD_TUPLE; + device->dev[i].speed = SPEED_CVT(*p); + while (*p & 0x80) + if (++p == q) return CS_BAD_TUPLE; + break; + default: + return CS_BAD_TUPLE; + } + + if (++p == q) return CS_BAD_TUPLE; + if (*p == 0xff) break; + scale = *p & 7; + if (scale == 7) return CS_BAD_TUPLE; + device->dev[i].size = ((*p >> 3) + 1) * (512 << (scale*2)); + device->ndev++; + if (++p == q) break; + } + + return CS_SUCCESS; +} + +/*====================================================================*/ + +static int parse_checksum(tuple_t *tuple, cistpl_checksum_t *csum) +{ + u_char *p; + if (tuple->TupleDataLen < 5) + return CS_BAD_TUPLE; + p = (u_char *)tuple->TupleData; + csum->addr = tuple->CISOffset+(short)le16_to_cpu(*(u_short *)p)-2; + csum->len = le16_to_cpu(*(u_short *)(p + 2)); + csum->sum = *(p+4); + return CS_SUCCESS; +} + +/*====================================================================*/ + +static int parse_longlink(tuple_t *tuple, cistpl_longlink_t *link) +{ + if (tuple->TupleDataLen < 4) + return CS_BAD_TUPLE; + link->addr = le32_to_cpu(*(u_int *)tuple->TupleData); + return CS_SUCCESS; +} + +/*====================================================================*/ + +static int parse_longlink_mfc(tuple_t *tuple, + cistpl_longlink_mfc_t *link) +{ + u_char *p; + int i; + + p = (u_char *)tuple->TupleData; + + link->nfn = *p; p++; + if (tuple->TupleDataLen <= link->nfn*5) + return CS_BAD_TUPLE; + for (i = 0; i < link->nfn; i++) { + link->fn[i].space = *p; p++; + link->fn[i].addr = le32_to_cpu(*(u_int *)p); p += 4; + } + return CS_SUCCESS; +} + +/*====================================================================*/ + +static int parse_strings(u_char *p, u_char *q, int max, + char *s, u_char *ofs, u_char *found) +{ + int i, j, ns; + + if (p == q) return CS_BAD_TUPLE; + ns = 0; j = 0; + for (i = 0; i < max; i++) { + if (*p == 0xff) break; + ofs[i] = j; + ns++; + for (;;) { + s[j++] = (*p == 0xff) ? '\0' : *p; + if ((*p == '\0') || (*p == 0xff)) break; + if (++p == q) return CS_BAD_TUPLE; + } + if ((*p == 0xff) || (++p == q)) break; + } + if (found) { + *found = ns; + return CS_SUCCESS; + } else { + return (ns == max) ? CS_SUCCESS : CS_BAD_TUPLE; + } +} + +/*====================================================================*/ + +static int parse_vers_1(tuple_t *tuple, cistpl_vers_1_t *vers_1) +{ + u_char *p, *q; + + p = (u_char *)tuple->TupleData; + q = p + tuple->TupleDataLen; + + vers_1->major = *p; p++; + vers_1->minor = *p; p++; + if (p >= q) return CS_BAD_TUPLE; + + return parse_strings(p, q, CISTPL_VERS_1_MAX_PROD_STRINGS, + vers_1->str, vers_1->ofs, &vers_1->ns); +} + +/*====================================================================*/ + +static int parse_altstr(tuple_t *tuple, cistpl_altstr_t *altstr) +{ + u_char *p, *q; + + p = (u_char *)tuple->TupleData; + q = p + tuple->TupleDataLen; + + return parse_strings(p, q, CISTPL_MAX_ALTSTR_STRINGS, + altstr->str, altstr->ofs, &altstr->ns); +} + +/*====================================================================*/ + +static int parse_jedec(tuple_t *tuple, cistpl_jedec_t *jedec) +{ + u_char *p, *q; + int nid; + + p = (u_char *)tuple->TupleData; + q = p + tuple->TupleDataLen; + + for (nid = 0; nid < CISTPL_MAX_DEVICES; nid++) { + if (p > q-2) break; + jedec->id[nid].mfr = p[0]; + jedec->id[nid].info = p[1]; + p += 2; + } + jedec->nid = nid; + return CS_SUCCESS; +} + +/*====================================================================*/ + +static int parse_manfid(tuple_t *tuple, cistpl_manfid_t *m) +{ + u_short *p; + if (tuple->TupleDataLen < 4) + return CS_BAD_TUPLE; + p = (u_short *)tuple->TupleData; + m->manf = le16_to_cpu(p[0]); + m->card = le16_to_cpu(p[1]); + return CS_SUCCESS; +} + +/*====================================================================*/ + +static int parse_funcid(tuple_t *tuple, cistpl_funcid_t *f) +{ + u_char *p; + if (tuple->TupleDataLen < 2) + return CS_BAD_TUPLE; + p = (u_char *)tuple->TupleData; + f->func = p[0]; + f->sysinit = p[1]; + return CS_SUCCESS; +} + +/*====================================================================*/ + +static int parse_funce(tuple_t *tuple, cistpl_funce_t *f) +{ + u_char *p; + int i; + if (tuple->TupleDataLen < 1) + return CS_BAD_TUPLE; + p = (u_char *)tuple->TupleData; + f->type = p[0]; + for (i = 1; i < tuple->TupleDataLen; i++) + f->data[i-1] = p[i]; + return CS_SUCCESS; +} + +/*====================================================================*/ + +static int parse_config(tuple_t *tuple, cistpl_config_t *config) +{ + int rasz, rmsz, i; + u_char *p; + + p = (u_char *)tuple->TupleData; + rasz = *p & 0x03; + rmsz = (*p & 0x3c) >> 2; + if (tuple->TupleDataLen < rasz+rmsz+4) + return CS_BAD_TUPLE; + config->last_idx = *(++p); + p++; + config->base = 0; + for (i = 0; i <= rasz; i++) + config->base += p[i] << (8*i); + p += rasz+1; + for (i = 0; i < 4; i++) + config->rmask[i] = 0; + for (i = 0; i <= rmsz; i++) + config->rmask[i>>2] += p[i] << (8*(i%4)); + config->subtuples = tuple->TupleDataLen - (rasz+rmsz+4); + return CS_SUCCESS; +} + +/*====================================================================== + + The following routines are all used to parse the nightmarish + config table entries. + +======================================================================*/ + +static u_char *parse_power(u_char *p, u_char *q, + cistpl_power_t *pwr) +{ + int i; + u_int scale; + + if (p == q) return NULL; + pwr->present = *p; + pwr->flags = 0; + p++; + for (i = 0; i < 7; i++) + if (pwr->present & (1<<i)) { + if (p == q) return NULL; + pwr->param[i] = POWER_CVT(*p); + scale = POWER_SCALE(*p); + while (*p & 0x80) { + if (++p == q) return NULL; + if ((*p & 0x7f) < 100) + pwr->param[i] += (*p & 0x7f) * scale / 100; + else if (*p == 0x7d) + pwr->flags |= CISTPL_POWER_HIGHZ_OK; + else if (*p == 0x7e) + pwr->param[i] = 0; + else if (*p == 0x7f) + pwr->flags |= CISTPL_POWER_HIGHZ_REQ; + else + return NULL; + } + p++; + } + return p; +} + +/*====================================================================*/ + +static u_char *parse_timing(u_char *p, u_char *q, + cistpl_timing_t *timing) +{ + u_char scale; + + if (p == q) return NULL; + scale = *p; + if ((scale & 3) != 3) { + if (++p == q) return NULL; + timing->wait = SPEED_CVT(*p); + timing->waitscale = exponent[scale & 3]; + } else + timing->wait = 0; + scale >>= 2; + if ((scale & 7) != 7) { + if (++p == q) return NULL; + timing->ready = SPEED_CVT(*p); + timing->rdyscale = exponent[scale & 7]; + } else + timing->ready = 0; + scale >>= 3; + if (scale != 7) { + if (++p == q) return NULL; + timing->reserved = SPEED_CVT(*p); + timing->rsvscale = exponent[scale]; + } else + timing->reserved = 0; + p++; + return p; +} + +/*====================================================================*/ + +static u_char *parse_io(u_char *p, u_char *q, cistpl_io_t *io) +{ + int i, j, bsz, lsz; + + if (p == q) return NULL; + io->flags = *p; + + if (!(*p & 0x80)) { + io->nwin = 1; + io->win[0].base = 0; + io->win[0].len = (1 << (io->flags & CISTPL_IO_LINES_MASK)); + return p+1; + } + + if (++p == q) return NULL; + io->nwin = (*p & 0x0f) + 1; + bsz = (*p & 0x30) >> 4; + if (bsz == 3) bsz++; + lsz = (*p & 0xc0) >> 6; + if (lsz == 3) lsz++; + p++; + + for (i = 0; i < io->nwin; i++) { + io->win[i].base = 0; + io->win[i].len = 1; + for (j = 0; j < bsz; j++, p++) { + if (p == q) return NULL; + io->win[i].base += *p << (j*8); + } + for (j = 0; j < lsz; j++, p++) { + if (p == q) return NULL; + io->win[i].len += *p << (j*8); + } + } + return p; +} + +/*====================================================================*/ + +static u_char *parse_mem(u_char *p, u_char *q, cistpl_mem_t *mem) +{ + int i, j, asz, lsz, has_ha; + u_int len, ca, ha; + + if (p == q) return NULL; + + mem->nwin = (*p & 0x07) + 1; + lsz = (*p & 0x18) >> 3; + asz = (*p & 0x60) >> 5; + has_ha = (*p & 0x80); + if (++p == q) return NULL; + + for (i = 0; i < mem->nwin; i++) { + len = ca = ha = 0; + for (j = 0; j < lsz; j++, p++) { + if (p == q) return NULL; + len += *p << (j*8); + } + for (j = 0; j < asz; j++, p++) { + if (p == q) return NULL; + ca += *p << (j*8); + } + if (has_ha) + for (j = 0; j < asz; j++, p++) { + if (p == q) return NULL; + ha += *p << (j*8); + } + mem->win[i].len = len << 8; + mem->win[i].card_addr = ca << 8; + mem->win[i].host_addr = ha << 8; + } + return p; +} + +/*====================================================================*/ + +static u_char *parse_irq(u_char *p, u_char *q, cistpl_irq_t *irq) +{ + if (p == q) return NULL; + irq->IRQInfo1 = *p; p++; + if (irq->IRQInfo1 & IRQ_INFO2_VALID) { + if (p+2 > q) return NULL; + irq->IRQInfo2 = (p[1]<<8) + p[0]; + p += 2; + } + return p; +} + +/*====================================================================*/ + +static int parse_cftable_entry(tuple_t *tuple, + cistpl_cftable_entry_t *entry) +{ + u_char *p, *q, features; + + p = tuple->TupleData; + q = p + tuple->TupleDataLen; + entry->index = *p & 0x3f; + entry->flags = 0; + if (*p & 0x40) + entry->flags |= CISTPL_CFTABLE_DEFAULT; + if (*p & 0x80) { + if (++p == q) return CS_BAD_TUPLE; + if (*p & 0x10) + entry->flags |= CISTPL_CFTABLE_BVDS; + if (*p & 0x20) + entry->flags |= CISTPL_CFTABLE_WP; + if (*p & 0x40) + entry->flags |= CISTPL_CFTABLE_RDYBSY; + if (*p & 0x80) + entry->flags |= CISTPL_CFTABLE_MWAIT; + entry->interface = *p & 0x0f; + } else + entry->interface = 0; + + /* Process optional features */ + if (++p == q) return CS_BAD_TUPLE; + features = *p; p++; + + /* Power options */ + if ((features & 3) > 0) { + p = parse_power(p, q, &entry->vcc); + if (p == NULL) return CS_BAD_TUPLE; + } else + entry->vcc.present = 0; + if ((features & 3) > 1) { + p = parse_power(p, q, &entry->vpp1); + if (p == NULL) return CS_BAD_TUPLE; + } else + entry->vpp1.present = 0; + if ((features & 3) > 2) { + p = parse_power(p, q, &entry->vpp2); + if (p == NULL) return CS_BAD_TUPLE; + } else + entry->vpp2.present = 0; + + /* Timing options */ + if (features & 0x04) { + p = parse_timing(p, q, &entry->timing); + if (p == NULL) return CS_BAD_TUPLE; + } else { + entry->timing.wait = 0; + entry->timing.ready = 0; + entry->timing.reserved = 0; + } + + /* I/O window options */ + if (features & 0x08) { + p = parse_io(p, q, &entry->io); + if (p == NULL) return CS_BAD_TUPLE; + } else + entry->io.nwin = 0; + + /* Interrupt options */ + if (features & 0x10) { + p = parse_irq(p, q, &entry->irq); + if (p == NULL) return CS_BAD_TUPLE; + } else + entry->irq.IRQInfo1 = 0; + + switch (features & 0x60) { + case 0x00: + entry->mem.nwin = 0; + break; + case 0x20: + entry->mem.nwin = 1; + entry->mem.win[0].len = le16_to_cpu(*(u_short *)p) << 8; + entry->mem.win[0].card_addr = 0; + entry->mem.win[0].host_addr = 0; + p += 2; + if (p > q) return CS_BAD_TUPLE; + break; + case 0x40: + entry->mem.nwin = 1; + entry->mem.win[0].len = le16_to_cpu(*(u_short *)p) << 8; + entry->mem.win[0].card_addr = + le16_to_cpu(*(u_short *)(p+2)) << 8; + entry->mem.win[0].host_addr = 0; + p += 4; + if (p > q) return CS_BAD_TUPLE; + break; + case 0x60: + p = parse_mem(p, q, &entry->mem); + if (p == NULL) return CS_BAD_TUPLE; + break; + } + + /* Misc features */ + if (features & 0x80) { + if (p == q) return CS_BAD_TUPLE; + entry->flags |= (*p << 8); + while (*p & 0x80) + if (++p == q) return CS_BAD_TUPLE; + p++; + } + + entry->subtuples = q-p; + + return CS_SUCCESS; +} + +/*====================================================================*/ + +#ifdef CONFIG_CARDBUS + +static int parse_bar(tuple_t *tuple, cistpl_bar_t *bar) +{ + u_char *p; + if (tuple->TupleDataLen < 6) + return CS_BAD_TUPLE; + p = (u_char *)tuple->TupleData; + bar->attr = *p; + p += 2; + bar->size = le32_to_cpu(*(u_int *)p); + return CS_SUCCESS; +} + +static int parse_config_cb(tuple_t *tuple, cistpl_config_t *config) +{ + u_char *p; + + p = (u_char *)tuple->TupleData; + if ((*p != 3) || (tuple->TupleDataLen < 6)) + return CS_BAD_TUPLE; + config->last_idx = *(++p); + p++; + config->base = le32_to_cpu(*(u_int *)p); + config->subtuples = tuple->TupleDataLen - 6; + return CS_SUCCESS; +} + +static int parse_cftable_entry_cb(tuple_t *tuple, + cistpl_cftable_entry_cb_t *entry) +{ + u_char *p, *q, features; + + p = tuple->TupleData; + q = p + tuple->TupleDataLen; + entry->index = *p & 0x3f; + entry->flags = 0; + if (*p & 0x40) + entry->flags |= CISTPL_CFTABLE_DEFAULT; + + /* Process optional features */ + if (++p == q) return CS_BAD_TUPLE; + features = *p; p++; + + /* Power options */ + if ((features & 3) > 0) { + p = parse_power(p, q, &entry->vcc); + if (p == NULL) return CS_BAD_TUPLE; + } else + entry->vcc.present = 0; + if ((features & 3) > 1) { + p = parse_power(p, q, &entry->vpp1); + if (p == NULL) return CS_BAD_TUPLE; + } else + entry->vpp1.present = 0; + if ((features & 3) > 2) { + p = parse_power(p, q, &entry->vpp2); + if (p == NULL) return CS_BAD_TUPLE; + } else + entry->vpp2.present = 0; + + /* I/O window options */ + if (features & 0x08) { + if (p == q) return CS_BAD_TUPLE; + entry->io = *p; p++; + } else + entry->io = 0; + + /* Interrupt options */ + if (features & 0x10) { + p = parse_irq(p, q, &entry->irq); + if (p == NULL) return CS_BAD_TUPLE; + } else + entry->irq.IRQInfo1 = 0; + + if (features & 0x20) { + if (p == q) return CS_BAD_TUPLE; + entry->mem = *p; p++; + } else + entry->mem = 0; + + /* Misc features */ + if (features & 0x80) { + if (p == q) return CS_BAD_TUPLE; + entry->flags |= (*p << 8); + if (*p & 0x80) { + if (++p == q) return CS_BAD_TUPLE; + entry->flags |= (*p << 16); + } + while (*p & 0x80) + if (++p == q) return CS_BAD_TUPLE; + p++; + } + + entry->subtuples = q-p; + + return CS_SUCCESS; +} + +#endif + +/*====================================================================*/ + +static int parse_device_geo(tuple_t *tuple, cistpl_device_geo_t *geo) +{ + u_char *p, *q; + int n; + + p = (u_char *)tuple->TupleData; + q = p + tuple->TupleDataLen; + + for (n = 0; n < CISTPL_MAX_DEVICES; n++) { + if (p > q-6) break; + geo->geo[n].buswidth = p[0]; + geo->geo[n].erase_block = 1 << (p[1]-1); + geo->geo[n].read_block = 1 << (p[2]-1); + geo->geo[n].write_block = 1 << (p[3]-1); + geo->geo[n].partition = 1 << (p[4]-1); + geo->geo[n].interleave = 1 << (p[5]-1); + p += 6; + } + geo->ngeo = n; + return CS_SUCCESS; +} + +/*====================================================================*/ + +static int parse_vers_2(tuple_t *tuple, cistpl_vers_2_t *v2) +{ + u_char *p, *q; + + if (tuple->TupleDataLen < 10) + return CS_BAD_TUPLE; + + p = tuple->TupleData; + q = p + tuple->TupleDataLen; + + v2->vers = p[0]; + v2->comply = p[1]; + v2->dindex = le16_to_cpu(*(u_short *)(p+2)); + v2->vspec8 = p[6]; + v2->vspec9 = p[7]; + v2->nhdr = p[8]; + p += 9; + return parse_strings(p, q, 2, v2->str, &v2->vendor, NULL); +} + +/*====================================================================*/ + +static int parse_org(tuple_t *tuple, cistpl_org_t *org) +{ + u_char *p, *q; + int i; + + p = tuple->TupleData; + q = p + tuple->TupleDataLen; + if (p == q) return CS_BAD_TUPLE; + org->data_org = *p; + if (++p == q) return CS_BAD_TUPLE; + for (i = 0; i < 30; i++) { + org->desc[i] = *p; + if (*p == '\0') break; + if (++p == q) return CS_BAD_TUPLE; + } + return CS_SUCCESS; +} + +/*====================================================================*/ + +static int parse_format(tuple_t *tuple, cistpl_format_t *fmt) +{ + u_char *p; + + if (tuple->TupleDataLen < 10) + return CS_BAD_TUPLE; + + p = tuple->TupleData; + + fmt->type = p[0]; + fmt->edc = p[1]; + fmt->offset = le32_to_cpu(*(u_int *)(p+2)); + fmt->length = le32_to_cpu(*(u_int *)(p+6)); + + return CS_SUCCESS; +} + +/*====================================================================*/ + +int parse_tuple(client_handle_t handle, tuple_t *tuple, cisparse_t *parse) +{ + int ret = CS_SUCCESS; + + if (tuple->TupleDataLen > tuple->TupleDataMax) + return CS_BAD_TUPLE; + switch (tuple->TupleCode) { + case CISTPL_DEVICE: + case CISTPL_DEVICE_A: + ret = parse_device(tuple, &parse->device); + break; +#ifdef CONFIG_CARDBUS + case CISTPL_BAR: + ret = parse_bar(tuple, &parse->bar); + break; + case CISTPL_CONFIG_CB: + ret = parse_config_cb(tuple, &parse->config); + break; + case CISTPL_CFTABLE_ENTRY_CB: + ret = parse_cftable_entry_cb(tuple, &parse->cftable_entry_cb); + break; +#endif + case CISTPL_CHECKSUM: + ret = parse_checksum(tuple, &parse->checksum); + break; + case CISTPL_LONGLINK_A: + case CISTPL_LONGLINK_C: + ret = parse_longlink(tuple, &parse->longlink); + break; + case CISTPL_LONGLINK_MFC: + ret = parse_longlink_mfc(tuple, &parse->longlink_mfc); + break; + case CISTPL_VERS_1: + ret = parse_vers_1(tuple, &parse->version_1); + break; + case CISTPL_ALTSTR: + ret = parse_altstr(tuple, &parse->altstr); + break; + case CISTPL_JEDEC_A: + case CISTPL_JEDEC_C: + ret = parse_jedec(tuple, &parse->jedec); + break; + case CISTPL_MANFID: + ret = parse_manfid(tuple, &parse->manfid); + break; + case CISTPL_FUNCID: + ret = parse_funcid(tuple, &parse->funcid); + break; + case CISTPL_FUNCE: + ret = parse_funce(tuple, &parse->funce); + break; + case CISTPL_CONFIG: + ret = parse_config(tuple, &parse->config); + break; + case CISTPL_CFTABLE_ENTRY: + ret = parse_cftable_entry(tuple, &parse->cftable_entry); + break; + case CISTPL_DEVICE_GEO: + case CISTPL_DEVICE_GEO_A: + ret = parse_device_geo(tuple, &parse->device_geo); + break; + case CISTPL_VERS_2: + ret = parse_vers_2(tuple, &parse->vers_2); + break; + case CISTPL_ORG: + ret = parse_org(tuple, &parse->org); + break; + case CISTPL_FORMAT: + case CISTPL_FORMAT_A: + ret = parse_format(tuple, &parse->format); + break; + case CISTPL_NO_LINK: + case CISTPL_LINKTARGET: + ret = CS_SUCCESS; + break; + default: + ret = CS_UNSUPPORTED_FUNCTION; + break; + } + return ret; +} + +/*====================================================================== + + This is used internally by Card Services to look up CIS stuff. + +======================================================================*/ + +int read_tuple(client_handle_t handle, cisdata_t code, void *parse) +{ + tuple_t tuple; + cisdata_t *buf; + int ret; + + buf = kmalloc(255, GFP_KERNEL); + if (buf == NULL) + return CS_OUT_OF_RESOURCE; + tuple.DesiredTuple = code; + tuple.Attributes = TUPLE_RETURN_COMMON; + ret = CardServices(GetFirstTuple, handle, &tuple, NULL); + if (ret != CS_SUCCESS) goto done; + tuple.TupleData = buf; + tuple.TupleOffset = 0; + tuple.TupleDataMax = 255; + ret = CardServices(GetTupleData, handle, &tuple, NULL); + if (ret != CS_SUCCESS) goto done; + ret = CardServices(ParseTuple, handle, &tuple, parse); +done: + kfree(buf); + return ret; +} + +/*====================================================================== + + This tries to determine if a card has a sensible CIS. It returns + the number of tuples in the CIS, or 0 if the CIS looks bad. The + checks include making sure several critical tuples are present and + valid; seeing if the total number of tuples is reasonable; and + looking for tuples that use reserved codes. + +======================================================================*/ + +int validate_cis(client_handle_t handle, cisinfo_t *info) +{ + tuple_t tuple; + cisparse_t *p; + int ret, reserved, dev_ok = 0, ident_ok = 0; + + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + p = kmalloc(sizeof(*p), GFP_KERNEL); + if (p == NULL) + return CS_OUT_OF_RESOURCE; + + info->Chains = reserved = 0; + tuple.DesiredTuple = RETURN_FIRST_TUPLE; + tuple.Attributes = TUPLE_RETURN_COMMON; + ret = get_first_tuple(handle, &tuple); + if (ret != CS_SUCCESS) + goto done; + + /* First tuple should be DEVICE; we should really have either that + or a CFTABLE_ENTRY of some sort */ + if ((tuple.TupleCode == CISTPL_DEVICE) || + (read_tuple(handle, CISTPL_CFTABLE_ENTRY, p) == CS_SUCCESS) || + (read_tuple(handle, CISTPL_CFTABLE_ENTRY_CB, p) == CS_SUCCESS)) + dev_ok++; + + /* All cards should have a MANFID tuple, and/or a VERS_1 or VERS_2 + tuple, for card identification. Certain old D-Link and Linksys + cards have only a broken VERS_2 tuple; hence the bogus test. */ + if ((read_tuple(handle, CISTPL_MANFID, p) == CS_SUCCESS) || + (read_tuple(handle, CISTPL_VERS_1, p) == CS_SUCCESS) || + (read_tuple(handle, CISTPL_VERS_2, p) != CS_NO_MORE_ITEMS)) + ident_ok++; + + if (!dev_ok && !ident_ok) + goto done; + + for (info->Chains = 1; info->Chains < MAX_TUPLES; info->Chains++) { + ret = get_next_tuple(handle, &tuple); + if (ret != CS_SUCCESS) break; + if (((tuple.TupleCode > 0x23) && (tuple.TupleCode < 0x40)) || + ((tuple.TupleCode > 0x47) && (tuple.TupleCode < 0x80)) || + ((tuple.TupleCode > 0x90) && (tuple.TupleCode < 0xff))) + reserved++; + } + if ((info->Chains == MAX_TUPLES) || (reserved > 5) || + ((!dev_ok || !ident_ok) && (info->Chains > 10))) + info->Chains = 0; + +done: + kfree(p); + return CS_SUCCESS; +} + diff --git a/linux/pcmcia-cs/modules/cs.c b/linux/pcmcia-cs/modules/cs.c new file mode 100644 index 0000000..99ee192 --- /dev/null +++ b/linux/pcmcia-cs/modules/cs.c @@ -0,0 +1,2399 @@ +/*====================================================================== + + PCMCIA Card Services -- core services + + cs.c 1.287 2004/04/09 03:54:25 + + The contents of this file are subject to the Mozilla Public + License Version 1.1 (the "License"); you may not use this file + except in compliance with the License. You may obtain a copy of + the License at http://www.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS + IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + implied. See the License for the specific language governing + rights and limitations under the License. + + The initial developer of the original code is David A. Hinds + <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + + Alternatively, the contents of this file may be used under the + terms of the GNU General Public License version 2 (the "GPL"), in + which case the provisions of the GPL are applicable instead of the + above. If you wish to allow the use of your version of this file + only under the terms of the GPL and not to allow others to use + your version of this file under the MPL, indicate your decision + by deleting the provisions above and replace them with the notice + and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this + file under either the MPL or the GPL. + +======================================================================*/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/config.h> +#include <linux/string.h> +#include <linux/major.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/sched.h> +#include <linux/timer.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/proc_fs.h> +#include <linux/pm.h> +#include <linux/pci.h> +#include <linux/spinlock.h> +#include <asm/system.h> +#include <asm/irq.h> + +#define IN_CARD_SERVICES +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/ss.h> +#include <pcmcia/cs.h> +#include <pcmcia/bulkmem.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/cisreg.h> +#include <pcmcia/bus_ops.h> +#include "cs_internal.h" + +#ifdef CONFIG_PCI +#define PCI_OPT " [pci]" +#else +#define PCI_OPT "" +#endif +#ifdef CONFIG_CARDBUS +#define CB_OPT " [cardbus]" +#else +#define CB_OPT "" +#endif +#ifdef CONFIG_PM +#define PM_OPT " [apm]" +#else +#define PM_OPT "" +#endif +#ifdef CONFIG_PNP_BIOS +#define PNP_OPT " [pnp]" +#else +#define PNP_OPT "" +#endif +#if !defined(CONFIG_CARDBUS) && !defined(CONFIG_PCI) && \ + !defined(CONFIG_PM) && !defined(CONFIG_PNP_BIOS) +#define OPTIONS " none" +#else +#define OPTIONS PCI_OPT CB_OPT PM_OPT PNP_OPT +#endif + +static const char *release = "Linux PCMCIA Card Services " CS_RELEASE; +#ifdef UTS_RELEASE +static const char *kernel = "kernel build: " UTS_RELEASE " " UTS_VERSION; +#endif +static const char *options = "options: " OPTIONS; + +/*====================================================================*/ + +/* Module parameters */ + +MODULE_AUTHOR("David Hinds <dahinds@users.sourceforge.net>"); +MODULE_DESCRIPTION("Linux PCMCIA Card Services " CS_RELEASE + "\n options:" OPTIONS); +MODULE_LICENSE("Dual MPL/GPL"); + +#define INT_MODULE_PARM(n, v) static int n = v; MODULE_PARM(n, "i") + +INT_MODULE_PARM(setup_delay, HZ/20); /* ticks */ +INT_MODULE_PARM(resume_delay, HZ/5); /* ticks */ +INT_MODULE_PARM(shutdown_delay, HZ/40); /* ticks */ +INT_MODULE_PARM(vcc_settle, HZ*4/10); /* ticks */ +INT_MODULE_PARM(reset_time, 10); /* usecs */ +INT_MODULE_PARM(unreset_delay, HZ/10); /* ticks */ +INT_MODULE_PARM(unreset_check, HZ/10); /* ticks */ +INT_MODULE_PARM(unreset_limit, 50); /* unreset_check's */ + +/* Access speed for attribute memory windows */ +INT_MODULE_PARM(cis_speed, 300); /* ns */ + +/* Access speed for IO windows */ +INT_MODULE_PARM(io_speed, 0); /* ns */ + +/* Optional features */ +#ifdef CONFIG_PM +INT_MODULE_PARM(do_apm, 1); +#endif +#ifdef CONFIG_PNP_BIOS +INT_MODULE_PARM(do_pnp, 1); +#endif + +#ifdef PCMCIA_DEBUG +int pc_debug=PCMCIA_DEBUG; +MODULE_PARM(pc_debug, "i"); +static const char *version = +"cs.c 1.287 2004/04/09 03:54:25 (David Hinds)"; +#endif + +/*====================================================================*/ + +static socket_state_t dead_socket = { + 0, SS_DETECT, 0, 0, 0 +}; + +/* Table of sockets */ +socket_t sockets = 0; +socket_info_t *socket_table[MAX_SOCK]; + +#ifdef HAS_PROC_BUS +struct proc_dir_entry *proc_pccard = NULL; +#endif + +/*====================================================================*/ + +/* String tables for error messages */ + +typedef struct lookup_t { + int key; + char *msg; +} lookup_t; + +static const lookup_t error_table[] = { + { CS_SUCCESS, "Operation succeeded" }, + { CS_BAD_ADAPTER, "Bad adapter" }, + { CS_BAD_ATTRIBUTE, "Bad attribute", }, + { CS_BAD_BASE, "Bad base address" }, + { CS_BAD_EDC, "Bad EDC" }, + { CS_BAD_IRQ, "Bad IRQ" }, + { CS_BAD_OFFSET, "Bad offset" }, + { CS_BAD_PAGE, "Bad page number" }, + { CS_READ_FAILURE, "Read failure" }, + { CS_BAD_SIZE, "Bad size" }, + { CS_BAD_SOCKET, "Bad socket" }, + { CS_BAD_TYPE, "Bad type" }, + { CS_BAD_VCC, "Bad Vcc" }, + { CS_BAD_VPP, "Bad Vpp" }, + { CS_BAD_WINDOW, "Bad window" }, + { CS_WRITE_FAILURE, "Write failure" }, + { CS_NO_CARD, "No card present" }, + { CS_UNSUPPORTED_FUNCTION, "Usupported function" }, + { CS_UNSUPPORTED_MODE, "Unsupported mode" }, + { CS_BAD_SPEED, "Bad speed" }, + { CS_BUSY, "Resource busy" }, + { CS_GENERAL_FAILURE, "General failure" }, + { CS_WRITE_PROTECTED, "Write protected" }, + { CS_BAD_ARG_LENGTH, "Bad argument length" }, + { CS_BAD_ARGS, "Bad arguments" }, + { CS_CONFIGURATION_LOCKED, "Configuration locked" }, + { CS_IN_USE, "Resource in use" }, + { CS_NO_MORE_ITEMS, "No more items" }, + { CS_OUT_OF_RESOURCE, "Out of resource" }, + { CS_BAD_HANDLE, "Bad handle" }, + { CS_BAD_TUPLE, "Bad CIS tuple" } +}; +#define ERROR_COUNT (sizeof(error_table)/sizeof(lookup_t)) + +static const lookup_t service_table[] = { + { AccessConfigurationRegister, "AccessConfigurationRegister" }, + { AddSocketServices, "AddSocketServices" }, + { AdjustResourceInfo, "AdjustResourceInfo" }, + { CheckEraseQueue, "CheckEraseQueue" }, + { CloseMemory, "CloseMemory" }, + { DeregisterClient, "DeregisterClient" }, + { DeregisterEraseQueue, "DeregisterEraseQueue" }, + { GetCardServicesInfo, "GetCardServicesInfo" }, + { GetClientInfo, "GetClientInfo" }, + { GetConfigurationInfo, "GetConfigurationInfo" }, + { GetEventMask, "GetEventMask" }, + { GetFirstClient, "GetFirstClient" }, + { GetFirstRegion, "GetFirstRegion" }, + { GetFirstTuple, "GetFirstTuple" }, + { GetNextClient, "GetNextClient" }, + { GetNextRegion, "GetNextRegion" }, + { GetNextTuple, "GetNextTuple" }, + { GetStatus, "GetStatus" }, + { GetTupleData, "GetTupleData" }, + { MapMemPage, "MapMemPage" }, + { ModifyConfiguration, "ModifyConfiguration" }, + { ModifyWindow, "ModifyWindow" }, + { OpenMemory, "OpenMemory" }, + { ParseTuple, "ParseTuple" }, + { ReadMemory, "ReadMemory" }, + { RegisterClient, "RegisterClient" }, + { RegisterEraseQueue, "RegisterEraseQueue" }, + { RegisterMTD, "RegisterMTD" }, + { ReleaseConfiguration, "ReleaseConfiguration" }, + { ReleaseIO, "ReleaseIO" }, + { ReleaseIRQ, "ReleaseIRQ" }, + { ReleaseWindow, "ReleaseWindow" }, + { RequestConfiguration, "RequestConfiguration" }, + { RequestIO, "RequestIO" }, + { RequestIRQ, "RequestIRQ" }, + { RequestSocketMask, "RequestSocketMask" }, + { RequestWindow, "RequestWindow" }, + { ResetCard, "ResetCard" }, + { SetEventMask, "SetEventMask" }, + { ValidateCIS, "ValidateCIS" }, + { WriteMemory, "WriteMemory" }, + { BindDevice, "BindDevice" }, + { BindMTD, "BindMTD" }, + { ReportError, "ReportError" }, + { SuspendCard, "SuspendCard" }, + { ResumeCard, "ResumeCard" }, + { EjectCard, "EjectCard" }, + { InsertCard, "InsertCard" }, + { ReplaceCIS, "ReplaceCIS" } +}; +#define SERVICE_COUNT (sizeof(service_table)/sizeof(lookup_t)) + +/*====================================================================== + + Reset a socket to the default state + +======================================================================*/ + +static void init_socket(socket_info_t *s) +{ + int i; + pccard_io_map io = { 0, 0, 0, 0, 1 }; + pccard_mem_map mem = { 0, 0, 0, 0, 0, 0 }; + + mem.sys_stop = s->cap.map_size; + s->socket = dead_socket; + s->ss_entry(s->sock, SS_SetSocket, &s->socket); + for (i = 0; i < 2; i++) { + io.map = i; + s->ss_entry(s->sock, SS_SetIOMap, &io); + } + for (i = 0; i < 5; i++) { + mem.map = i; + s->ss_entry(s->sock, SS_SetMemMap, &mem); + } +} + +/*====================================================================*/ + +#if defined(HAS_PROC_BUS) && defined(PCMCIA_DEBUG) +static int proc_read_clients(char *buf, char **start, off_t pos, + int count, int *eof, void *data) +{ + socket_info_t *s = data; + client_handle_t c; + char *p = buf; + + for (c = s->clients; c; c = c->next) + p += sprintf(p, "fn %x: '%s' [attr 0x%04x] [state 0x%04x]\n", + c->Function, c->dev_info, c->Attributes, c->state); + return (p - buf); +} +#endif + +/*====================================================================== + + Low-level PC Card interface drivers need to register with Card + Services using these calls. + +======================================================================*/ + +static void setup_socket(u_long i); +static void shutdown_socket(u_long i); +static void reset_socket(u_long i); +static void unreset_socket(u_long i); +static void parse_events(void *info, u_int events); + +int register_ss_entry(int nsock, ss_entry_t ss_entry) +{ + int i, ns; + socket_info_t *s; + + DEBUG(0, "cs: register_ss_entry(%d, 0x%p)\n", nsock, ss_entry); + + for (ns = 0; ns < nsock; ns++) { + s = kmalloc(sizeof(struct socket_info_t), GFP_KERNEL); + if (!s) { + printk(KERN_NOTICE "cs: memory allocation failure!\n"); + return (!ns); + } + memset(s, 0, sizeof(socket_info_t)); + + s->ss_entry = ss_entry; + s->sock = ns; + s->setup.data = sockets; + s->setup.function = &setup_socket; + s->shutdown.data = sockets; + s->shutdown.function = &shutdown_socket; + /* base address = 0, map = 0 */ + s->cis_mem.flags = 0; + s->cis_mem.speed = cis_speed; + s->erase_busy.next = s->erase_busy.prev = &s->erase_busy; + spin_lock_init(&s->lock); + + for (i = 0; i < sockets; i++) + if (socket_table[i] == NULL) break; + socket_table[i] = s; + if (i == sockets) sockets++; + + init_socket(s); + ss_entry(ns, SS_InquireSocket, &s->cap); +#ifdef HAS_PROC_BUS + if (proc_pccard) { + char name[3]; + sprintf(name, "%02d", i); + s->proc = proc_mkdir(name, proc_pccard); + if (s->proc) + ss_entry(ns, SS_ProcSetup, s->proc); +#ifdef PCMCIA_DEBUG + if (s->proc) + create_proc_read_entry("clients", 0, s->proc, + proc_read_clients, s); +#endif + } +#endif + } + + return 0; +} /* register_ss_entry */ + +/*====================================================================*/ + +void unregister_ss_entry(ss_entry_t ss_entry) +{ + int i, j; + socket_info_t *s = NULL; + client_t *client; + +#ifdef HAS_PROC_BUS + for (i = 0; i < sockets; i++) { + s = socket_table[i]; + if (s->ss_entry != ss_entry) continue; + if (proc_pccard) { + char name[3]; + sprintf(name, "%02d", i); +#ifdef PCMCIA_DEBUG + remove_proc_entry("clients", s->proc); +#endif + remove_proc_entry(name, proc_pccard); + } + } +#endif + + for (;;) { + for (i = 0; i < sockets; i++) { + s = socket_table[i]; + if (s->ss_entry == ss_entry) break; + } + if (i == sockets) + break; + shutdown_socket(i); + release_cis_mem(s); + while (s->clients) { + client = s->clients; + s->clients = s->clients->next; + kfree(client); + } + s->ss_entry = NULL; + kfree(s); + socket_table[i] = NULL; + for (j = i; j < sockets-1; j++) + socket_table[j] = socket_table[j+1]; + sockets--; + } + +} /* unregister_ss_entry */ + +/*====================================================================== + + Shutdown_Socket() and setup_socket() are scheduled using add_timer + calls by the main event handler when card insertion and removal + events are received. Shutdown_Socket() unconfigures a socket and + turns off socket power. Setup_socket() turns on socket power + and resets the socket, in two stages. + +======================================================================*/ + +static void free_regions(memory_handle_t *list) +{ + memory_handle_t tmp; + while (*list != NULL) { + tmp = *list; + *list = tmp->info.next; + tmp->region_magic = 0; + kfree(tmp); + } +} + +static int send_event(socket_info_t *s, event_t event, int priority); + +static void shutdown_socket(u_long i) +{ + socket_info_t *s = socket_table[i]; + client_t **c; + + DEBUG(1, "cs: shutdown_socket(%ld)\n", i); + + /* Blank out the socket state */ + s->state &= SOCKET_PRESENT|SOCKET_SETUP_PENDING; + init_socket(s); + s->irq.AssignedIRQ = s->irq.Config = 0; + s->lock_count = 0; + s->cis_used = 0; + if (s->fake_cis) { + kfree(s->fake_cis); + s->fake_cis = NULL; + } +#ifdef CONFIG_CARDBUS + cb_release_cis_mem(s); + cb_free(s); +#endif + s->functions = 0; + if (s->config) { + kfree(s->config); + s->config = NULL; + } + for (c = &s->clients; *c; ) { + if ((*c)->state & CLIENT_UNBOUND) { + client_t *d = *c; + *c = (*c)->next; + kfree(d); + } else { + c = &((*c)->next); + } + } + free_regions(&s->a_region); + free_regions(&s->c_region); +} /* shutdown_socket */ + +static void setup_socket(u_long i) +{ + int val; + socket_info_t *s = socket_table[i]; + + s->ss_entry(s->sock, SS_GetStatus, &val); + if (val & SS_PENDING) { + /* Does the socket need more time? */ + DEBUG(2, "cs: setup_socket(%ld): status pending\n", i); + if (++s->setup_timeout > 100) { + printk(KERN_NOTICE "cs: socket %ld voltage interrogation" + " timed out\n", i); + } else { + mod_timer(&s->setup, jiffies + HZ/10); + } + } else if (val & SS_DETECT) { + DEBUG(1, "cs: setup_socket(%ld): applying power\n", i); + s->state |= SOCKET_PRESENT; + s->socket.flags = 0; + if (val & SS_3VCARD) + s->socket.Vcc = s->socket.Vpp = 33; + else if (!(val & SS_XVCARD)) + s->socket.Vcc = s->socket.Vpp = 50; + else { + printk(KERN_NOTICE "cs: socket %ld: unsupported " + "voltage key\n", i); + s->socket.Vcc = 0; + } + if (val & SS_CARDBUS) { + s->state |= SOCKET_CARDBUS; +#ifndef CONFIG_CARDBUS + printk(KERN_NOTICE "cs: unsupported card type detected!\n"); +#endif + } + s->ss_entry(s->sock, SS_SetSocket, &s->socket); + s->setup.function = &reset_socket; + mod_timer(&s->setup, jiffies + vcc_settle); + } else + DEBUG(0, "cs: setup_socket(%ld): no card!\n", i); +} /* setup_socket */ + +/*====================================================================== + + Reset_socket() and unreset_socket() handle hard resets. Resets + have several causes: card insertion, a call to reset_socket, or + recovery from a suspend/resume cycle. Unreset_socket() sends + a CS event that matches the cause of the reset. + +======================================================================*/ + +static void reset_socket(u_long i) +{ + socket_info_t *s = socket_table[i]; + + DEBUG(1, "cs: resetting socket %ld\n", i); + s->socket.flags |= SS_OUTPUT_ENA | SS_RESET; + s->ss_entry(s->sock, SS_SetSocket, &s->socket); + udelay((long)reset_time); + s->socket.flags &= ~SS_RESET; + s->ss_entry(s->sock, SS_SetSocket, &s->socket); + s->setup_timeout = 0; + s->setup.function = &unreset_socket; + mod_timer(&s->setup, jiffies + unreset_delay); +} /* reset_socket */ + +#define EVENT_MASK \ +(SOCKET_SETUP_PENDING|SOCKET_SUSPEND|SOCKET_RESET_PENDING) + +static void unreset_socket(u_long i) +{ + socket_info_t *s = socket_table[i]; + int val; + + s->ss_entry(s->sock, SS_GetStatus, &val); + if (val & SS_READY) { + DEBUG(1, "cs: reset done on socket %ld\n", i); + if (s->state & SOCKET_SUSPEND) { + s->state &= ~EVENT_MASK; + if (verify_cis_cache(s) != 0) + parse_events(s, SS_DETECT); + else + send_event(s, CS_EVENT_PM_RESUME, CS_EVENT_PRI_LOW); + } else if (s->state & SOCKET_SETUP_PENDING) { +#ifdef CONFIG_CARDBUS + if (s->state & SOCKET_CARDBUS) + cb_alloc(s); +#endif + send_event(s, CS_EVENT_CARD_INSERTION, CS_EVENT_PRI_LOW); + s->state &= ~SOCKET_SETUP_PENDING; + } else { + send_event(s, CS_EVENT_CARD_RESET, CS_EVENT_PRI_LOW); + if (s->reset_handle) { + s->reset_handle->event_callback_args.info = NULL; + EVENT(s->reset_handle, CS_EVENT_RESET_COMPLETE, + CS_EVENT_PRI_LOW); + s->state &= ~EVENT_MASK; + } + } + } else { + DEBUG(2, "cs: socket %ld not ready yet\n", i); + if (++s->setup_timeout > unreset_limit) { + printk(KERN_NOTICE "cs: socket %ld timed out during" + " reset\n", i); + s->state &= ~EVENT_MASK; + } else { + mod_timer(&s->setup, jiffies + unreset_check); + } + } +} /* unreset_socket */ + +/*====================================================================== + + The central event handler. Send_event() sends an event to all + valid clients. Parse_events() interprets the event bits from + a card status change report. Do_shotdown() handles the high + priority stuff associated with a card removal. + +======================================================================*/ + +static int send_event(socket_info_t *s, event_t event, int priority) +{ + client_t *client = s->clients; + int ret; + DEBUG(1, "cs: send_event(sock %d, event %d, pri %d)\n", + s->sock, event, priority); + ret = 0; + for (; client; client = client->next) { + if (client->state & (CLIENT_UNBOUND|CLIENT_STALE)) + continue; + if (client->EventMask & event) { + ret = EVENT(client, event, priority); + if (ret != 0) + return ret; + } + } + return ret; +} /* send_event */ + +static void do_shutdown(socket_info_t *s) +{ + client_t *client; + if (s->state & SOCKET_SHUTDOWN_PENDING) + return; + s->state |= SOCKET_SHUTDOWN_PENDING; + send_event(s, CS_EVENT_CARD_REMOVAL, CS_EVENT_PRI_HIGH); + for (client = s->clients; client; client = client->next) + if (!(client->Attributes & INFO_MASTER_CLIENT)) + client->state |= CLIENT_STALE; + if (s->state & (SOCKET_SETUP_PENDING|SOCKET_RESET_PENDING)) { + DEBUG(0, "cs: flushing pending setup\n"); + del_timer(&s->setup); + s->state &= ~EVENT_MASK; + } + mod_timer(&s->shutdown, jiffies + shutdown_delay); + s->state &= ~SOCKET_PRESENT; +} + +static void parse_events(void *info, u_int events) +{ + socket_info_t *s = info; + if (events & SS_DETECT) { + int status; + u_long flags; + spin_lock_irqsave(&s->lock, flags); + s->ss_entry(s->sock, SS_GetStatus, &status); + if ((s->state & SOCKET_PRESENT) && + (!(s->state & SOCKET_SUSPEND) || + !(status & SS_DETECT))) + do_shutdown(s); + if (status & SS_DETECT) { + if (s->state & SOCKET_SETUP_PENDING) { + del_timer(&s->setup); + DEBUG(1, "cs: delaying pending setup\n"); + } + s->state |= SOCKET_SETUP_PENDING; + s->setup.function = &setup_socket; + s->setup_timeout = 0; + if (s->state & SOCKET_SUSPEND) + s->setup.expires = jiffies + resume_delay; + else + s->setup.expires = jiffies + setup_delay; + add_timer(&s->setup); + } + spin_unlock_irqrestore(&s->lock, flags); + } + if (events & SS_BATDEAD) + send_event(s, CS_EVENT_BATTERY_DEAD, CS_EVENT_PRI_LOW); + if (events & SS_BATWARN) + send_event(s, CS_EVENT_BATTERY_LOW, CS_EVENT_PRI_LOW); + if (events & SS_READY) { + if (!(s->state & SOCKET_RESET_PENDING)) + send_event(s, CS_EVENT_READY_CHANGE, CS_EVENT_PRI_LOW); + else DEBUG(1, "cs: ready change during reset\n"); + } +} /* parse_events */ + +/*====================================================================== + + Another event handler, for power management events. + + This does not comply with the latest PC Card spec for handling + power management events. + +======================================================================*/ + +#ifdef CONFIG_PM +#if (LINUX_VERSION_CODE < VERSION(2,3,43)) +static int handle_pm_event(apm_event_t rqst) +#else +static int handle_pm_event(struct pm_dev *dev, pm_request_t rqst, + void *data) +#endif +{ + int i, stat; + socket_info_t *s; + static int down = 0; + + /* <linux/pm.h> hides a hack so this works with old APM support */ + switch (rqst) { + case PM_SUSPEND: + DEBUG(1, "cs: received suspend notification\n"); + if (down) { + printk(KERN_DEBUG "cs: received extra suspend event\n"); + break; + } + down = 1; + for (i = 0; i < sockets; i++) { + s = socket_table[i]; + if ((s->state & SOCKET_PRESENT) && + !(s->state & SOCKET_SUSPEND)){ + send_event(s, CS_EVENT_PM_SUSPEND, CS_EVENT_PRI_LOW); + s->ss_entry(s->sock, SS_SetSocket, &dead_socket); + s->state |= SOCKET_SUSPEND; + } + } + break; + case PM_RESUME: + DEBUG(1, "cs: received resume notification\n"); + if (!down) { + printk(KERN_DEBUG "cs: received bogus resume event\n"); + break; + } + down = 0; + for (i = 0; i < sockets; i++) { + s = socket_table[i]; + /* Do this just to reinitialize the socket */ + init_socket(s); + s->ss_entry(s->sock, SS_GetStatus, &stat); + /* If there was or is a card here, we need to do something + about it... but parse_events will sort it all out. */ + if ((s->state & SOCKET_PRESENT) || (stat & SS_DETECT)) + parse_events(s, SS_DETECT); + } + break; + } + return 0; +} /* handle_pm_event */ +#endif + +/*====================================================================== + + Special stuff for managing IO windows, because they are scarce. + +======================================================================*/ + +static int alloc_io_space(socket_info_t *s, u_int attr, ioaddr_t *base, + ioaddr_t num, u_int lines, char *name) +{ + int i; + ioaddr_t try, align; + + align = (*base) ? (lines ? 1<<lines : 0) : 1; + if (align && (align < num)) { + if (*base) { + DEBUG(0, "odd IO request: num %04x align %04x\n", + num, align); + align = 0; + } else + while (align && (align < num)) align <<= 1; + } + if (*base & ~(align-1)) { + DEBUG(0, "odd IO request: base %04x align %04x\n", + *base, align); + while (*base & ~(align-1)) align <<= 1; + } + /* Check for an already-allocated window that must conflict with + what was asked for. It is a hack because it does not catch all + potential conflicts, just the most obvious ones. */ + for (i = 0; i < MAX_IO_WIN; i++) + if ((s->io[i].NumPorts != 0) && + ((s->io[i].BasePort & (align-1)) == *base)) + return 1; + for (i = 0; i < MAX_IO_WIN; i++) { + if (s->io[i].NumPorts == 0) { + if (find_io_region(base, num, align, name) == 0) { + s->io[i].Attributes = attr; + s->io[i].BasePort = *base; + s->io[i].NumPorts = s->io[i].InUse = num; + break; + } else + return 1; + } else if (s->io[i].Attributes != attr) + continue; + /* Try to extend top of window */ + try = s->io[i].BasePort + s->io[i].NumPorts; + if ((*base == 0) || (*base == try)) + if (find_io_region(&try, num, 0, name) == 0) { + *base = try; + s->io[i].NumPorts += num; + s->io[i].InUse += num; + break; + } + /* Try to extend bottom of window */ + try = s->io[i].BasePort - num; + if ((*base == 0) || (*base == try)) + if (find_io_region(&try, num, 0, name) == 0) { + s->io[i].BasePort = *base = try; + s->io[i].NumPorts += num; + s->io[i].InUse += num; + break; + } + } + return (i == MAX_IO_WIN); +} /* alloc_io_space */ + +static void release_io_space(socket_info_t *s, ioaddr_t base, + ioaddr_t num) +{ + int i; + release_region(base, num); + for (i = 0; i < MAX_IO_WIN; i++) { + if ((s->io[i].BasePort <= base) && + (s->io[i].BasePort+s->io[i].NumPorts >= base+num)) { + s->io[i].InUse -= num; + /* Free the window if no one else is using it */ + if (s->io[i].InUse == 0) + s->io[i].NumPorts = 0; + } + } +} + +/*====================================================================== + + Access_configuration_register() reads and writes configuration + registers in attribute memory. Memory window 0 is reserved for + this and the tuple reading services. + +======================================================================*/ + +static int access_configuration_register(client_handle_t handle, + conf_reg_t *reg) +{ + socket_info_t *s; + config_t *c; + int addr; + u_char val; + + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + s = SOCKET(handle); + if (!(s->state & SOCKET_PRESENT)) + return CS_NO_CARD; + if (handle->Function == BIND_FN_ALL) { + if (reg->Function >= s->functions) + return CS_BAD_ARGS; + c = &s->config[reg->Function]; + } else + c = CONFIG(handle); + if (!(c->state & CONFIG_LOCKED)) + return CS_CONFIGURATION_LOCKED; + + addr = (c->ConfigBase + reg->Offset) >> 1; + + switch (reg->Action) { + case CS_READ: + read_cis_mem(s, 1, addr, 1, &val); + reg->Value = val; + break; + case CS_WRITE: + val = reg->Value; + write_cis_mem(s, 1, addr, 1, &val); + break; + default: + return CS_BAD_ARGS; + break; + } + return CS_SUCCESS; +} /* access_configuration_register */ + +/*====================================================================== + + Bind_device() associates a device driver with a particular socket. + It is normally called by Driver Services after it has identified + a newly inserted card. An instance of that driver will then be + eligible to register as a client of this socket. + +======================================================================*/ + +static int bind_device(bind_req_t *req) +{ + client_t *client; + socket_info_t *s; + + if (CHECK_SOCKET(req->Socket)) + return CS_BAD_SOCKET; + s = SOCKET(req); + + client = (client_t *)kmalloc(sizeof(client_t), GFP_KERNEL); + if (!client) return CS_OUT_OF_RESOURCE; + memset(client, '\0', sizeof(client_t)); + client->client_magic = CLIENT_MAGIC; + strncpy(client->dev_info, (char *)req->dev_info, DEV_NAME_LEN); + client->Socket = req->Socket; + client->Function = req->Function; + client->state = CLIENT_UNBOUND; + client->erase_busy.next = &client->erase_busy; + client->erase_busy.prev = &client->erase_busy; + init_waitqueue_head(&client->mtd_req); + client->next = s->clients; + s->clients = client; + DEBUG(1, "cs: bind_device(): client 0x%p, sock %d, dev %s\n", + client, client->Socket, client->dev_info); + return CS_SUCCESS; +} /* bind_device */ + +/*====================================================================== + + Bind_mtd() associates a device driver with a particular memory + region. It is normally called by Driver Services after it has + identified a memory device type. An instance of the corresponding + driver will then be able to register to control this region. + +======================================================================*/ + +static int bind_mtd(mtd_bind_t *req) +{ + socket_info_t *s; + memory_handle_t region; + + if (CHECK_SOCKET(req->Socket)) + return CS_BAD_SOCKET; + s = SOCKET(req); + + if (req->Attributes & REGION_TYPE_AM) + region = s->a_region; + else + region = s->c_region; + + while (region) { + if (region->info.CardOffset == req->CardOffset) break; + region = region->info.next; + } + if (!region || (region->mtd != NULL)) + return CS_BAD_OFFSET; + strncpy(region->dev_info, (char *)req->dev_info, DEV_NAME_LEN); + + DEBUG(1, "cs: bind_mtd(): attr 0x%x, offset 0x%x, dev %s\n", + req->Attributes, req->CardOffset, (char *)req->dev_info); + return CS_SUCCESS; +} /* bind_mtd */ + +/*====================================================================*/ + +static int deregister_client(client_handle_t handle) +{ + client_t **client; + socket_info_t *s; + memory_handle_t region; + u_long flags; + int i, sn; + + DEBUG(1, "cs: deregister_client(%p)\n", handle); + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + if (handle->state & + (CLIENT_IRQ_REQ|CLIENT_IO_REQ|CLIENT_CONFIG_LOCKED)) + return CS_IN_USE; + for (i = 0; i < MAX_WIN; i++) + if (handle->state & CLIENT_WIN_REQ(i)) + return CS_IN_USE; + + /* Disconnect all MTD links */ + s = SOCKET(handle); + if (handle->mtd_count) { + for (region = s->a_region; region; region = region->info.next) + if (region->mtd == handle) region->mtd = NULL; + for (region = s->c_region; region; region = region->info.next) + if (region->mtd == handle) region->mtd = NULL; + } + + sn = handle->Socket; s = socket_table[sn]; + + if ((handle->state & CLIENT_STALE) || + (handle->Attributes & INFO_MASTER_CLIENT)) { + spin_lock_irqsave(&s->lock, flags); + client = &s->clients; + while ((*client) && ((*client) != handle)) + client = &(*client)->next; + if (*client == NULL) { + spin_unlock_irqrestore(&s->lock, flags); + return CS_BAD_HANDLE; + } + *client = handle->next; + handle->client_magic = 0; + kfree(handle); + spin_unlock_irqrestore(&s->lock, flags); + } else { + handle->state = CLIENT_UNBOUND; + handle->mtd_count = 0; + handle->event_handler = NULL; + } + + if (--s->real_clients == 0) + s->ss_entry(sn, SS_RegisterCallback, NULL); + + return CS_SUCCESS; +} /* deregister_client */ + +/*====================================================================*/ + +static int get_configuration_info(client_handle_t handle, + config_info_t *config) +{ + socket_info_t *s; + config_t *c; + + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + s = SOCKET(handle); + if (!(s->state & SOCKET_PRESENT)) + return CS_NO_CARD; + + if (handle->Function == BIND_FN_ALL) { + if (config->Function && (config->Function >= s->functions)) + return CS_BAD_ARGS; + } else + config->Function = handle->Function; + +#ifdef CONFIG_CARDBUS + if (s->state & SOCKET_CARDBUS) { + u_char fn = config->Function; + memset(config, 0, sizeof(config_info_t)); + config->Function = fn; + config->Vcc = s->socket.Vcc; + config->Vpp1 = config->Vpp2 = s->socket.Vpp; + config->Option = s->cap.cardbus; + config->IntType = INT_CARDBUS; + /* This is a nasty hack */ + pcibios_read_config_dword(s->cap.cardbus, 0, 0, &config->ConfigBase); + if (s->cb_config) { + config->Attributes = CONF_VALID_CLIENT; + config->AssignedIRQ = s->irq.AssignedIRQ; + if (config->AssignedIRQ) + config->Attributes |= CONF_ENABLE_IRQ; + config->BasePort1 = s->io[0].BasePort; + config->NumPorts1 = s->io[0].NumPorts; + } + return CS_SUCCESS; + } +#endif + + c = (s->config != NULL) ? &s->config[config->Function] : NULL; + + if ((c == NULL) || !(c->state & CONFIG_LOCKED)) { + config->Attributes = 0; + config->Vcc = s->socket.Vcc; + config->Vpp1 = config->Vpp2 = s->socket.Vpp; + return CS_SUCCESS; + } + + /* !!! This is a hack !!! */ + memcpy(&config->Attributes, &c->Attributes, sizeof(config_t)); + config->Attributes |= CONF_VALID_CLIENT; + config->CardValues = c->CardValues; + config->IRQAttributes = c->irq.Attributes; + config->AssignedIRQ = s->irq.AssignedIRQ; + config->BasePort1 = c->io.BasePort1; + config->NumPorts1 = c->io.NumPorts1; + config->Attributes1 = c->io.Attributes1; + config->BasePort2 = c->io.BasePort2; + config->NumPorts2 = c->io.NumPorts2; + config->Attributes2 = c->io.Attributes2; + config->IOAddrLines = c->io.IOAddrLines; + + return CS_SUCCESS; +} /* get_configuration_info */ + +/*====================================================================== + + Return information about this version of Card Services. + +======================================================================*/ + +static int get_card_services_info(servinfo_t *info) +{ + info->Signature[0] = 'C'; + info->Signature[1] = 'S'; + info->Count = sockets; + info->Revision = CS_RELEASE_CODE; + info->CSLevel = 0x0210; + info->VendorString = (char *)release; + return CS_SUCCESS; +} /* get_card_services_info */ + +/*====================================================================== + + Note that get_first_client() *does* recognize the Socket field + in the request structure. + +======================================================================*/ + +static int get_first_client(client_handle_t *handle, client_req_t *req) +{ + socket_t s; + if (req->Attributes & CLIENT_THIS_SOCKET) + s = req->Socket; + else + s = 0; + if (CHECK_SOCKET(req->Socket)) + return CS_BAD_SOCKET; + if (socket_table[s]->clients == NULL) + return CS_NO_MORE_ITEMS; + *handle = socket_table[s]->clients; + return CS_SUCCESS; +} /* get_first_client */ + +/*====================================================================*/ + +static int get_next_client(client_handle_t *handle, client_req_t *req) +{ + socket_info_t *s; + if ((handle == NULL) || CHECK_HANDLE(*handle)) + return CS_BAD_HANDLE; + if ((*handle)->next == NULL) { + if (req->Attributes & CLIENT_THIS_SOCKET) + return CS_NO_MORE_ITEMS; + s = SOCKET(*handle); + if (s->clients == NULL) + return CS_NO_MORE_ITEMS; + *handle = s->clients; + } else + *handle = (*handle)->next; + return CS_SUCCESS; +} /* get_next_client */ + +/*====================================================================*/ + +static int get_window(window_handle_t *handle, int idx, win_req_t *req) +{ + socket_info_t *s; + window_t *win; + int w; + + if (idx == 0) + s = SOCKET((client_handle_t)*handle); + else + s = (*handle)->sock; + if (!(s->state & SOCKET_PRESENT)) + return CS_NO_CARD; + for (w = idx; w < MAX_WIN; w++) + if (s->state & SOCKET_WIN_REQ(w)) break; + if (w == MAX_WIN) + return CS_NO_MORE_ITEMS; + win = &s->win[w]; + req->Base = win->ctl.sys_start; + req->Size = win->ctl.sys_stop - win->ctl.sys_start + 1; + req->AccessSpeed = win->ctl.speed; + req->Attributes = 0; + if (win->ctl.flags & MAP_ATTRIB) + req->Attributes |= WIN_MEMORY_TYPE_AM; + if (win->ctl.flags & MAP_ACTIVE) + req->Attributes |= WIN_ENABLE; + if (win->ctl.flags & MAP_16BIT) + req->Attributes |= WIN_DATA_WIDTH_16; + if (win->ctl.flags & MAP_USE_WAIT) + req->Attributes |= WIN_USE_WAIT; + *handle = win; + return CS_SUCCESS; +} /* get_window */ + +static int get_first_window(client_handle_t *handle, win_req_t *req) +{ + if ((handle == NULL) || CHECK_HANDLE(*handle)) + return CS_BAD_HANDLE; + return get_window((window_handle_t *)handle, 0, req); +} + +static int get_next_window(window_handle_t *win, win_req_t *req) +{ + if ((win == NULL) || ((*win)->magic != WINDOW_MAGIC)) + return CS_BAD_HANDLE; + return get_window(win, (*win)->index+1, req); +} + +/*====================================================================== + + Get the current socket state bits. We don't support the latched + SocketState yet: I haven't seen any point for it. + +======================================================================*/ + +static int cs_get_status(client_handle_t handle, cs_status_t *status) +{ + socket_info_t *s; + config_t *c; + int val; + + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + s = SOCKET(handle); + s->ss_entry(s->sock, SS_GetStatus, &val); + status->CardState = status->SocketState = 0; + status->CardState |= (val & SS_DETECT) ? CS_EVENT_CARD_DETECT : 0; + status->CardState |= (val & SS_CARDBUS) ? CS_EVENT_CB_DETECT : 0; + status->CardState |= (val & SS_3VCARD) ? CS_EVENT_3VCARD : 0; + status->CardState |= (val & SS_XVCARD) ? CS_EVENT_XVCARD : 0; + if (s->state & SOCKET_SUSPEND) + status->CardState |= CS_EVENT_PM_SUSPEND; + if (!(s->state & SOCKET_PRESENT)) + return CS_NO_CARD; + if (s->state & SOCKET_SETUP_PENDING) + status->CardState |= CS_EVENT_CARD_INSERTION; + + /* Get info from the PRR, if necessary */ + if (handle->Function == BIND_FN_ALL) { + if (status->Function && (status->Function >= s->functions)) + return CS_BAD_ARGS; + c = (s->config != NULL) ? &s->config[status->Function] : NULL; + } else + c = CONFIG(handle); + if ((c != NULL) && (c->state & CONFIG_LOCKED) && + (c->IntType & (INT_MEMORY_AND_IO | INT_ZOOMED_VIDEO))) { + u_char reg; + if (c->Present & PRESENT_PIN_REPLACE) { + read_cis_mem(s, 1, (c->ConfigBase+CISREG_PRR)>>1, 1, ®); + status->CardState |= + (reg & PRR_WP_STATUS) ? CS_EVENT_WRITE_PROTECT : 0; + status->CardState |= + (reg & PRR_READY_STATUS) ? CS_EVENT_READY_CHANGE : 0; + status->CardState |= + (reg & PRR_BVD2_STATUS) ? CS_EVENT_BATTERY_LOW : 0; + status->CardState |= + (reg & PRR_BVD1_STATUS) ? CS_EVENT_BATTERY_DEAD : 0; + } else { + /* No PRR? Then assume we're always ready */ + status->CardState |= CS_EVENT_READY_CHANGE; + } + if (c->Present & PRESENT_EXT_STATUS) { + read_cis_mem(s, 1, (c->ConfigBase+CISREG_ESR)>>1, 1, ®); + status->CardState |= + (reg & ESR_REQ_ATTN) ? CS_EVENT_REQUEST_ATTENTION : 0; + } + return CS_SUCCESS; + } + status->CardState |= + (val & SS_WRPROT) ? CS_EVENT_WRITE_PROTECT : 0; + status->CardState |= + (val & SS_BATDEAD) ? CS_EVENT_BATTERY_DEAD : 0; + status->CardState |= + (val & SS_BATWARN) ? CS_EVENT_BATTERY_LOW : 0; + status->CardState |= + (val & SS_READY) ? CS_EVENT_READY_CHANGE : 0; + return CS_SUCCESS; +} /* cs_get_status */ + +/*====================================================================== + + Change the card address of an already open memory window. + +======================================================================*/ + +static int get_mem_page(window_handle_t win, memreq_t *req) +{ + if ((win == NULL) || (win->magic != WINDOW_MAGIC)) + return CS_BAD_HANDLE; + req->Page = 0; + req->CardOffset = win->ctl.card_start; + return CS_SUCCESS; +} /* get_mem_page */ + +static int map_mem_page(window_handle_t win, memreq_t *req) +{ + socket_info_t *s; + if ((win == NULL) || (win->magic != WINDOW_MAGIC)) + return CS_BAD_HANDLE; + if (req->Page != 0) + return CS_BAD_PAGE; + s = win->sock; + win->ctl.card_start = req->CardOffset; + if (s->ss_entry(s->sock, SS_SetMemMap, &win->ctl) != 0) + return CS_BAD_OFFSET; + return CS_SUCCESS; +} /* map_mem_page */ + +/*====================================================================== + + Modify a locked socket configuration + +======================================================================*/ + +static int modify_configuration(client_handle_t handle, + modconf_t *mod) +{ + socket_info_t *s; + config_t *c; + + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + s = SOCKET(handle); c = CONFIG(handle); + if (!(s->state & SOCKET_PRESENT)) + return CS_NO_CARD; + if (!(c->state & CONFIG_LOCKED)) + return CS_CONFIGURATION_LOCKED; + + if (mod->Attributes & CONF_IRQ_CHANGE_VALID) { + if (mod->Attributes & CONF_ENABLE_IRQ) { + c->Attributes |= CONF_ENABLE_IRQ; + s->socket.io_irq = s->irq.AssignedIRQ; + } else { + c->Attributes &= ~CONF_ENABLE_IRQ; + s->socket.io_irq = 0; + } + s->ss_entry(s->sock, SS_SetSocket, &s->socket); + } + + if (mod->Attributes & CONF_VCC_CHANGE_VALID) + return CS_BAD_VCC; + + /* We only allow changing Vpp1 and Vpp2 to the same value */ + if ((mod->Attributes & CONF_VPP1_CHANGE_VALID) && + (mod->Attributes & CONF_VPP2_CHANGE_VALID)) { + if (mod->Vpp1 != mod->Vpp2) + return CS_BAD_VPP; + c->Vpp1 = c->Vpp2 = s->socket.Vpp = mod->Vpp1; + if (s->ss_entry(s->sock, SS_SetSocket, &s->socket)) + return CS_BAD_VPP; + } else if ((mod->Attributes & CONF_VPP1_CHANGE_VALID) || + (mod->Attributes & CONF_VPP2_CHANGE_VALID)) + return CS_BAD_VPP; + + return CS_SUCCESS; +} /* modify_configuration */ + +/*====================================================================== + + Modify the attributes of a window returned by RequestWindow. + +======================================================================*/ + +static int modify_window(window_handle_t win, modwin_t *req) +{ + if ((win == NULL) || (win->magic != WINDOW_MAGIC)) + return CS_BAD_HANDLE; + + win->ctl.flags &= ~(MAP_ATTRIB|MAP_ACTIVE); + if (req->Attributes & WIN_MEMORY_TYPE) + win->ctl.flags |= MAP_ATTRIB; + if (req->Attributes & WIN_ENABLE) + win->ctl.flags |= MAP_ACTIVE; + if (req->Attributes & WIN_DATA_WIDTH_16) + win->ctl.flags |= MAP_16BIT; + if (req->Attributes & WIN_USE_WAIT) + win->ctl.flags |= MAP_USE_WAIT; + win->ctl.speed = req->AccessSpeed; + win->sock->ss_entry(win->sock->sock, SS_SetMemMap, &win->ctl); + + return CS_SUCCESS; +} /* modify_window */ + +/*====================================================================== + + Register_client() uses the dev_info_t handle to match the + caller with a socket. The driver must have already been bound + to a socket with bind_device() -- in fact, bind_device() + allocates the client structure that will be used. + +======================================================================*/ + +static int register_client(client_handle_t *handle, client_reg_t *req) +{ + client_t *client; + socket_info_t *s; + socket_t ns; + + /* Look for unbound client with matching dev_info */ + client = NULL; + for (ns = 0; ns < sockets; ns++) { + client = socket_table[ns]->clients; + while (client != NULL) { + if ((strcmp(client->dev_info, (char *)req->dev_info) == 0) + && (client->state & CLIENT_UNBOUND)) break; + client = client->next; + } + if (client != NULL) break; + } + if (client == NULL) + return CS_OUT_OF_RESOURCE; + + s = socket_table[ns]; + if (++s->real_clients == 1) { + ss_callback_t call; + int status; + call.handler = &parse_events; + call.info = s; + s->ss_entry(ns, SS_RegisterCallback, &call); + s->ss_entry(ns, SS_GetStatus, &status); + if ((status & SS_DETECT) && + !(s->state & SOCKET_SETUP_PENDING)) { + s->state |= SOCKET_SETUP_PENDING; + setup_socket(ns); + } + } + + *handle = client; + client->state &= ~CLIENT_UNBOUND; + client->Socket = ns; + client->Attributes = req->Attributes; + client->EventMask = req->EventMask; + client->event_handler = req->event_handler; + client->event_callback_args = req->event_callback_args; + client->event_callback_args.client_handle = client; + client->event_callback_args.bus = s->cap.bus; + + if (s->state & SOCKET_CARDBUS) + client->state |= CLIENT_CARDBUS; + + if ((!(s->state & SOCKET_CARDBUS)) && (s->functions == 0) && + (client->Function != BIND_FN_ALL)) { + cistpl_longlink_mfc_t mfc; + if (read_tuple(client, CISTPL_LONGLINK_MFC, &mfc) + == CS_SUCCESS) + s->functions = mfc.nfn; + else + s->functions = 1; + s->config = kmalloc(sizeof(config_t) * s->functions, + GFP_KERNEL); + memset(s->config, 0, sizeof(config_t) * s->functions); + } + + DEBUG(1, "cs: register_client(): client 0x%p, sock %d, dev %s\n", + client, client->Socket, client->dev_info); + if (client->EventMask & CS_EVENT_REGISTRATION_COMPLETE) + EVENT(client, CS_EVENT_REGISTRATION_COMPLETE, CS_EVENT_PRI_LOW); + if ((socket_table[ns]->state & SOCKET_PRESENT) && + !(socket_table[ns]->state & SOCKET_SETUP_PENDING)) { + if (client->EventMask & CS_EVENT_CARD_INSERTION) + EVENT(client, CS_EVENT_CARD_INSERTION, CS_EVENT_PRI_LOW); + else + client->PendingEvents |= CS_EVENT_CARD_INSERTION; + } + return CS_SUCCESS; +} /* register_client */ + +/*====================================================================*/ + +static int release_configuration(client_handle_t handle, + config_req_t *req) +{ + socket_info_t *s; + pccard_io_map io = { 0, 0, 0, 0, 1 }; + int i; + + if (CHECK_HANDLE(handle) || + !(handle->state & CLIENT_CONFIG_LOCKED)) + return CS_BAD_HANDLE; + handle->state &= ~CLIENT_CONFIG_LOCKED; + s = SOCKET(handle); + +#ifdef CONFIG_CARDBUS + if (handle->state & CLIENT_CARDBUS) { + cb_disable(s); + s->lock_count = 0; + return CS_SUCCESS; + } +#endif + + if (!(handle->state & CLIENT_STALE)) { + config_t *c = CONFIG(handle); + if (--(s->lock_count) == 0) { + s->socket.flags = SS_OUTPUT_ENA; + s->socket.Vpp = 0; + s->socket.io_irq = 0; + s->ss_entry(s->sock, SS_SetSocket, &s->socket); + } + if (c->state & CONFIG_IO_REQ) + for (i = 0; i < MAX_IO_WIN; i++) { + if (s->io[i].NumPorts == 0) + continue; + s->io[i].Config--; + if (s->io[i].Config != 0) + continue; + io.map = i; + s->ss_entry(s->sock, SS_SetIOMap, &io); + } + c->state &= ~CONFIG_LOCKED; + } + + return CS_SUCCESS; +} /* release_configuration */ + +/*====================================================================== + + Release_io() releases the I/O ranges allocated by a client. This + may be invoked some time after a card ejection has already dumped + the actual socket configuration, so if the client is "stale", we + don't bother checking the port ranges against the current socket + values. + +======================================================================*/ + +static int release_io(client_handle_t handle, io_req_t *req) +{ + socket_info_t *s; + + if (CHECK_HANDLE(handle) || !(handle->state & CLIENT_IO_REQ)) + return CS_BAD_HANDLE; + handle->state &= ~CLIENT_IO_REQ; + s = SOCKET(handle); + +#ifdef CONFIG_CARDBUS + if (handle->state & CLIENT_CARDBUS) { + cb_release(s); + return CS_SUCCESS; + } +#endif + + if (!(handle->state & CLIENT_STALE)) { + config_t *c = CONFIG(handle); + if (c->state & CONFIG_LOCKED) + return CS_CONFIGURATION_LOCKED; + if ((c->io.BasePort1 != req->BasePort1) || + (c->io.NumPorts1 != req->NumPorts1) || + (c->io.BasePort2 != req->BasePort2) || + (c->io.NumPorts2 != req->NumPorts2)) + return CS_BAD_ARGS; + c->state &= ~CONFIG_IO_REQ; + } + + release_io_space(s, req->BasePort1, req->NumPorts1); + if (req->NumPorts2) + release_io_space(s, req->BasePort2, req->NumPorts2); + + return CS_SUCCESS; +} /* release_io */ + +/*====================================================================*/ + +static int cs_release_irq(client_handle_t handle, irq_req_t *req) +{ + socket_info_t *s; + if (CHECK_HANDLE(handle) || !(handle->state & CLIENT_IRQ_REQ)) + return CS_BAD_HANDLE; + handle->state &= ~CLIENT_IRQ_REQ; + s = SOCKET(handle); + + if (!(handle->state & CLIENT_STALE)) { + config_t *c = CONFIG(handle); + if (c->state & CONFIG_LOCKED) + return CS_CONFIGURATION_LOCKED; + if (c->irq.Attributes != req->Attributes) + return CS_BAD_ATTRIBUTE; + if (s->irq.AssignedIRQ != req->AssignedIRQ) + return CS_BAD_IRQ; + if (--s->irq.Config == 0) { + c->state &= ~CONFIG_IRQ_REQ; + s->irq.AssignedIRQ = 0; + } + } + + if (req->Attributes & IRQ_HANDLE_PRESENT) { + bus_free_irq(s->cap.bus, req->AssignedIRQ, req->Instance); + } + +#ifdef CONFIG_ISA + if (req->AssignedIRQ != s->cap.pci_irq) + undo_irq(req->Attributes, req->AssignedIRQ); +#endif + + return CS_SUCCESS; +} /* cs_release_irq */ + +/*====================================================================*/ + +static int release_window(window_handle_t win) +{ + socket_info_t *s; + + if ((win == NULL) || (win->magic != WINDOW_MAGIC)) + return CS_BAD_HANDLE; + s = win->sock; + if (!(win->handle->state & CLIENT_WIN_REQ(win->index))) + return CS_BAD_HANDLE; + + /* Shut down memory window */ + win->ctl.flags &= ~MAP_ACTIVE; + s->ss_entry(s->sock, SS_SetMemMap, &win->ctl); + s->state &= ~SOCKET_WIN_REQ(win->index); + + /* Release system memory */ + release_mem_region(win->base, win->size); + win->handle->state &= ~CLIENT_WIN_REQ(win->index); + + win->magic = 0; + + return CS_SUCCESS; +} /* release_window */ + +/*====================================================================*/ + +static int request_configuration(client_handle_t handle, + config_req_t *req) +{ + int i; + u_int base; + socket_info_t *s; + config_t *c; + pccard_io_map iomap; + + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + i = handle->Socket; s = socket_table[i]; + if (!(s->state & SOCKET_PRESENT)) + return CS_NO_CARD; + +#ifdef CONFIG_CARDBUS + if (handle->state & CLIENT_CARDBUS) { + if (!(req->IntType & INT_CARDBUS)) + return CS_UNSUPPORTED_MODE; + if (s->lock_count != 0) + return CS_CONFIGURATION_LOCKED; + cb_enable(s); + handle->state |= CLIENT_CONFIG_LOCKED; + s->lock_count++; + return CS_SUCCESS; + } +#endif + + if (req->IntType & INT_CARDBUS) + return CS_UNSUPPORTED_MODE; + c = CONFIG(handle); + if (c->state & CONFIG_LOCKED) + return CS_CONFIGURATION_LOCKED; + + /* Do power control. We don't allow changes in Vcc. */ + if (s->socket.Vcc != req->Vcc) + printk(KERN_DEBUG "cs: ignoring requested Vcc\n"); + if (req->Vpp1 != req->Vpp2) + return CS_BAD_VPP; + s->socket.Vpp = req->Vpp1; + if (s->ss_entry(s->sock, SS_SetSocket, &s->socket)) + return CS_BAD_VPP; + + c->Vcc = req->Vcc; c->Vpp1 = c->Vpp2 = req->Vpp1; + + /* Pick memory or I/O card, DMA mode, interrupt */ + c->IntType = req->IntType; + c->Attributes = req->Attributes; + if (req->IntType & INT_MEMORY_AND_IO) + s->socket.flags |= SS_IOCARD; + if (req->IntType & INT_ZOOMED_VIDEO) + s->socket.flags |= SS_ZVCARD; + if (req->Attributes & CONF_ENABLE_DMA) + s->socket.flags |= SS_DMA_MODE; + if (req->Attributes & CONF_ENABLE_SPKR) + s->socket.flags |= SS_SPKR_ENA; + if (req->Attributes & CONF_ENABLE_IRQ) + s->socket.io_irq = s->irq.AssignedIRQ; + else + s->socket.io_irq = 0; + s->ss_entry(s->sock, SS_SetSocket, &s->socket); + s->lock_count++; + + /* Set up CIS configuration registers */ + base = c->ConfigBase = req->ConfigBase; + c->Present = c->CardValues = req->Present; + if (req->Present & PRESENT_COPY) { + c->Copy = req->Copy; + write_cis_mem(s, 1, (base + CISREG_SCR)>>1, 1, &c->Copy); + } + if (req->Present & PRESENT_OPTION) { + if (s->functions == 1) { + c->Option = req->ConfigIndex & COR_CONFIG_MASK; + } else { + c->Option = req->ConfigIndex & COR_MFC_CONFIG_MASK; + c->Option |= COR_FUNC_ENA|COR_IREQ_ENA; + if (req->Present & PRESENT_IOBASE_0) + c->Option |= COR_ADDR_DECODE; + } + if (c->state & CONFIG_IRQ_REQ) + if (!(c->irq.Attributes & IRQ_FORCED_PULSE)) + c->Option |= COR_LEVEL_REQ; + write_cis_mem(s, 1, (base + CISREG_COR)>>1, 1, &c->Option); + mdelay(40); + } + if (req->Present & PRESENT_STATUS) { + c->Status = req->Status; + write_cis_mem(s, 1, (base + CISREG_CCSR)>>1, 1, &c->Status); + } + if (req->Present & PRESENT_PIN_REPLACE) { + c->Pin = req->Pin; + write_cis_mem(s, 1, (base + CISREG_PRR)>>1, 1, &c->Pin); + } + if (req->Present & PRESENT_EXT_STATUS) { + c->ExtStatus = req->ExtStatus; + write_cis_mem(s, 1, (base + CISREG_ESR)>>1, 1, &c->ExtStatus); + } + if (req->Present & PRESENT_IOBASE_0) { + u_char b = c->io.BasePort1 & 0xff; + write_cis_mem(s, 1, (base + CISREG_IOBASE_0)>>1, 1, &b); + b = (c->io.BasePort1 >> 8) & 0xff; + write_cis_mem(s, 1, (base + CISREG_IOBASE_1)>>1, 1, &b); + } + if (req->Present & PRESENT_IOSIZE) { + u_char b = c->io.NumPorts1 + c->io.NumPorts2 - 1; + write_cis_mem(s, 1, (base + CISREG_IOSIZE)>>1, 1, &b); + } + + /* Configure I/O windows */ + if (c->state & CONFIG_IO_REQ) { + iomap.speed = io_speed; + for (i = 0; i < MAX_IO_WIN; i++) + if (s->io[i].NumPorts != 0) { + iomap.map = i; + iomap.flags = MAP_ACTIVE; + switch (s->io[i].Attributes & IO_DATA_PATH_WIDTH) { + case IO_DATA_PATH_WIDTH_16: + iomap.flags |= MAP_16BIT; break; + case IO_DATA_PATH_WIDTH_AUTO: + iomap.flags |= MAP_AUTOSZ; break; + default: + break; + } + iomap.start = s->io[i].BasePort; + iomap.stop = iomap.start + s->io[i].NumPorts - 1; + s->ss_entry(s->sock, SS_SetIOMap, &iomap); + s->io[i].Config++; + } + } + + c->state |= CONFIG_LOCKED; + handle->state |= CLIENT_CONFIG_LOCKED; + return CS_SUCCESS; +} /* request_configuration */ + +/*====================================================================== + + Request_io() reserves ranges of port addresses for a socket. + I have not implemented range sharing or alias addressing. + +======================================================================*/ + +static int request_io(client_handle_t handle, io_req_t *req) +{ + socket_info_t *s; + config_t *c; + + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + s = SOCKET(handle); + if (!(s->state & SOCKET_PRESENT)) + return CS_NO_CARD; + + if (handle->state & CLIENT_CARDBUS) { +#ifdef CONFIG_CARDBUS + int ret = cb_config(s); + if (ret == CS_SUCCESS) + handle->state |= CLIENT_IO_REQ; + return ret; +#else + return CS_UNSUPPORTED_FUNCTION; +#endif + } + + if (!req) + return CS_UNSUPPORTED_MODE; + c = CONFIG(handle); + if (c->state & CONFIG_LOCKED) + return CS_CONFIGURATION_LOCKED; + if (c->state & CONFIG_IO_REQ) + return CS_IN_USE; + if (req->Attributes1 & (IO_SHARED | IO_FORCE_ALIAS_ACCESS)) + return CS_BAD_ATTRIBUTE; + if ((req->NumPorts2 > 0) && + (req->Attributes2 & (IO_SHARED | IO_FORCE_ALIAS_ACCESS))) + return CS_BAD_ATTRIBUTE; + + if (alloc_io_space(s, req->Attributes1, &req->BasePort1, + req->NumPorts1, req->IOAddrLines, + handle->dev_info)) + return CS_IN_USE; + + if (req->NumPorts2) { + if (alloc_io_space(s, req->Attributes2, &req->BasePort2, + req->NumPorts2, req->IOAddrLines, + handle->dev_info)) { + release_io_space(s, req->BasePort1, req->NumPorts1); + return CS_IN_USE; + } + } + + c->io = *req; + c->state |= CONFIG_IO_REQ; + handle->state |= CLIENT_IO_REQ; + return CS_SUCCESS; +} /* request_io */ + +/*====================================================================== + + Request_irq() reserves an irq for this client. + + Also, since Linux only reserves irq's when they are actually + hooked, we don't guarantee that an irq will still be available + when the configuration is locked. Now that I think about it, + there might be a way to fix this using a dummy handler. + +======================================================================*/ + +static int cs_request_irq(client_handle_t handle, irq_req_t *req) +{ + socket_info_t *s; + config_t *c; + int ret = CS_IN_USE, irq = 0; + + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + s = SOCKET(handle); + if (!(s->state & SOCKET_PRESENT)) + return CS_NO_CARD; + c = CONFIG(handle); + if (c->state & CONFIG_LOCKED) + return CS_CONFIGURATION_LOCKED; + if (c->state & CONFIG_IRQ_REQ) + return CS_IN_USE; + +#ifdef CONFIG_ISA + if (s->irq.AssignedIRQ != 0) { + /* If the interrupt is already assigned, it must match */ + irq = s->irq.AssignedIRQ; + if (req->IRQInfo1 & IRQ_INFO2_VALID) { + u_int mask = req->IRQInfo2 & s->cap.irq_mask; + ret = ((mask >> irq) & 1) ? 0 : CS_BAD_ARGS; + } else + ret = ((req->IRQInfo1&IRQ_MASK) == irq) ? 0 : CS_BAD_ARGS; + } else { + if (req->IRQInfo1 & IRQ_INFO2_VALID) { + u_int try, mask = req->IRQInfo2 & s->cap.irq_mask; + for (try = 0; try < 2; try++) { + for (irq = 0; irq < 16; irq++) + if ((mask >> irq) & 1) { + ret = try_irq(req->Attributes, irq, try); + if (ret == 0) break; + } + if (ret == 0) break; + } + } else { + irq = req->IRQInfo1 & IRQ_MASK; + ret = try_irq(req->Attributes, irq, 1); + } + } +#endif + if (ret != 0) { + if (!s->cap.pci_irq) + return ret; + irq = s->cap.pci_irq; + } + + if (req->Attributes & IRQ_HANDLE_PRESENT) { + if (bus_request_irq(s->cap.bus, irq, req->Handler, + ((req->Attributes & IRQ_TYPE_DYNAMIC_SHARING) || + (s->functions > 1) || + (irq == s->cap.pci_irq)) ? SA_SHIRQ : 0, + handle->dev_info, req->Instance)) + return CS_IN_USE; + } + + c->irq.Attributes = req->Attributes; + s->irq.AssignedIRQ = req->AssignedIRQ = irq; + s->irq.Config++; + + c->state |= CONFIG_IRQ_REQ; + handle->state |= CLIENT_IRQ_REQ; + return CS_SUCCESS; +} /* cs_request_irq */ + +/*====================================================================== + + Request_window() establishes a mapping between card memory space + and system memory space. + +======================================================================*/ + +static int request_window(client_handle_t *handle, win_req_t *req) +{ + socket_info_t *s; + window_t *win; + u_long align; + int w; + + if (CHECK_HANDLE(*handle)) + return CS_BAD_HANDLE; + s = SOCKET(*handle); + if (!(s->state & SOCKET_PRESENT)) + return CS_NO_CARD; + if (req->Attributes & (WIN_PAGED | WIN_SHARED)) + return CS_BAD_ATTRIBUTE; + + /* Window size defaults to smallest available */ + if (req->Size == 0) + req->Size = s->cap.map_size; + align = (((s->cap.features & SS_CAP_MEM_ALIGN) || + (req->Attributes & WIN_STRICT_ALIGN)) ? + req->Size : s->cap.map_size); + if (req->Size & (s->cap.map_size-1)) + return CS_BAD_SIZE; + if ((req->Base && (s->cap.features & SS_CAP_STATIC_MAP)) || + (req->Base & (align-1))) + return CS_BAD_BASE; + if (req->Base) + align = 0; + + /* Allocate system memory window */ + for (w = 0; w < MAX_WIN; w++) + if (!(s->state & SOCKET_WIN_REQ(w))) break; + if (w == MAX_WIN) + return CS_OUT_OF_RESOURCE; + + win = &s->win[w]; + win->magic = WINDOW_MAGIC; + win->index = w; + win->handle = *handle; + win->sock = s; + win->base = req->Base; + win->size = req->Size; + + if (!(s->cap.features & SS_CAP_STATIC_MAP) && + find_mem_region(&win->base, win->size, align, + (req->Attributes & WIN_MAP_BELOW_1MB) || + !(s->cap.features & SS_CAP_PAGE_REGS), + (*handle)->dev_info)) + return CS_IN_USE; + (*handle)->state |= CLIENT_WIN_REQ(w); + + /* Configure the socket controller */ + win->ctl.map = w+1; + win->ctl.flags = 0; + win->ctl.speed = req->AccessSpeed; + if (req->Attributes & WIN_MEMORY_TYPE) + win->ctl.flags |= MAP_ATTRIB; + if (req->Attributes & WIN_ENABLE) + win->ctl.flags |= MAP_ACTIVE; + if (req->Attributes & WIN_DATA_WIDTH_16) + win->ctl.flags |= MAP_16BIT; + if (req->Attributes & WIN_USE_WAIT) + win->ctl.flags |= MAP_USE_WAIT; + win->ctl.sys_start = win->base; + win->ctl.sys_stop = win->base + win->size-1; + win->ctl.card_start = 0; + if (s->ss_entry(s->sock, SS_SetMemMap, &win->ctl) != 0) + return CS_BAD_ARGS; + s->state |= SOCKET_WIN_REQ(w); + + /* Return window handle */ + req->Base = win->ctl.sys_start; + *handle = (client_handle_t)win; + + return CS_SUCCESS; +} /* request_window */ + +/*====================================================================== + + I'm not sure which "reset" function this is supposed to use, + but for now, it uses the low-level interface's reset, not the + CIS register. + +======================================================================*/ + +static int reset_card(client_handle_t handle, client_req_t *req) +{ + int i, ret; + socket_info_t *s; + + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + i = handle->Socket; s = socket_table[i]; + if (!(s->state & SOCKET_PRESENT)) + return CS_NO_CARD; + if (s->state & SOCKET_RESET_PENDING) + return CS_IN_USE; + s->state |= SOCKET_RESET_PENDING; + + ret = send_event(s, CS_EVENT_RESET_REQUEST, CS_EVENT_PRI_LOW); + if (ret != 0) { + s->state &= ~SOCKET_RESET_PENDING; + handle->event_callback_args.info = (void *)(u_long)ret; + EVENT(handle, CS_EVENT_RESET_COMPLETE, CS_EVENT_PRI_LOW); + } else { + DEBUG(1, "cs: resetting socket %d\n", i); + send_event(s, CS_EVENT_RESET_PHYSICAL, CS_EVENT_PRI_LOW); + s->reset_handle = handle; + reset_socket(i); + } + return CS_SUCCESS; +} /* reset_card */ + +/*====================================================================== + + These shut down or wake up a socket. They are sort of user + initiated versions of the APM suspend and resume actions. + +======================================================================*/ + +static int suspend_card(client_handle_t handle, client_req_t *req) +{ + int i; + socket_info_t *s; + + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + i = handle->Socket; s = socket_table[i]; + if (!(s->state & SOCKET_PRESENT)) + return CS_NO_CARD; + if (s->state & SOCKET_SUSPEND) + return CS_IN_USE; + + DEBUG(1, "cs: suspending socket %d\n", i); + send_event(s, CS_EVENT_PM_SUSPEND, CS_EVENT_PRI_LOW); + s->ss_entry(s->sock, SS_SetSocket, &dead_socket); + s->state |= SOCKET_SUSPEND; + + return CS_SUCCESS; +} /* suspend_card */ + +static int resume_card(client_handle_t handle, client_req_t *req) +{ + int i; + socket_info_t *s; + + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + i = handle->Socket; s = socket_table[i]; + if (!(s->state & SOCKET_PRESENT)) + return CS_NO_CARD; + if (!(s->state & SOCKET_SUSPEND)) + return CS_IN_USE; + + DEBUG(1, "cs: waking up socket %d\n", i); + setup_socket(i); + + return CS_SUCCESS; +} /* resume_card */ + +/*====================================================================== + + These handle user requests to eject or insert a card. + +======================================================================*/ + +static int eject_card(client_handle_t handle, client_req_t *req) +{ + int i, ret; + socket_info_t *s; + u_long flags; + + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + i = handle->Socket; s = socket_table[i]; + if (!(s->state & SOCKET_PRESENT)) + return CS_NO_CARD; + + DEBUG(1, "cs: user eject request on socket %d\n", i); + + ret = send_event(s, CS_EVENT_EJECTION_REQUEST, CS_EVENT_PRI_LOW); + if (ret != 0) + return ret; + + spin_lock_irqsave(&s->lock, flags); + do_shutdown(s); + spin_unlock_irqrestore(&s->lock, flags); + + return CS_SUCCESS; + +} /* eject_card */ + +static int insert_card(client_handle_t handle, client_req_t *req) +{ + int i, status; + socket_info_t *s; + u_long flags; + + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + i = handle->Socket; s = socket_table[i]; + if (s->state & SOCKET_PRESENT) + return CS_IN_USE; + + DEBUG(1, "cs: user insert request on socket %d\n", i); + + spin_lock_irqsave(&s->lock, flags); + if (!(s->state & SOCKET_SETUP_PENDING)) { + s->state |= SOCKET_SETUP_PENDING; + spin_unlock_irqrestore(&s->lock, flags); + s->ss_entry(i, SS_GetStatus, &status); + if (status & SS_DETECT) + setup_socket(i); + else { + s->state &= ~SOCKET_SETUP_PENDING; + return CS_NO_CARD; + } + } else + spin_unlock_irqrestore(&s->lock, flags); + + return CS_SUCCESS; +} /* insert_card */ + +/*====================================================================== + + Maybe this should send a CS_EVENT_CARD_INSERTION event if we + haven't sent one to this client yet? + +======================================================================*/ + +static int set_event_mask(client_handle_t handle, eventmask_t *mask) +{ + u_int events, bit; + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + if (handle->Attributes & CONF_EVENT_MASK_VALID) + return CS_BAD_SOCKET; + handle->EventMask = mask->EventMask; + events = handle->PendingEvents & handle->EventMask; + handle->PendingEvents -= events; + while (events != 0) { + bit = ((events ^ (events-1)) + 1) >> 1; + EVENT(handle, bit, CS_EVENT_PRI_LOW); + events -= bit; + } + return CS_SUCCESS; +} /* set_event_mask */ + +/*====================================================================*/ + +static int report_error(client_handle_t handle, error_info_t *err) +{ + int i; + char *serv; + + if (CHECK_HANDLE(handle)) + printk(KERN_NOTICE); + else + printk(KERN_NOTICE "%s: ", handle->dev_info); + + for (i = 0; i < SERVICE_COUNT; i++) + if (service_table[i].key == err->func) break; + if (i < SERVICE_COUNT) + serv = service_table[i].msg; + else + serv = "Unknown service number"; + + for (i = 0; i < ERROR_COUNT; i++) + if (error_table[i].key == err->retcode) break; + if (i < ERROR_COUNT) + printk("%s: %s\n", serv, error_table[i].msg); + else + printk("%s: Unknown error code %#x\n", serv, err->retcode); + + return CS_SUCCESS; +} /* report_error */ + +/*====================================================================*/ + +int CardServices(int func, void *a1, void *a2, void *a3) +{ + +#ifdef PCMCIA_DEBUG + if (pc_debug > 2) { + int i; + for (i = 0; i < SERVICE_COUNT; i++) + if (service_table[i].key == func) break; + if (i < SERVICE_COUNT) + printk(KERN_DEBUG "cs: CardServices(%s, 0x%p, 0x%p)\n", + service_table[i].msg, a1, a2); + else + printk(KERN_DEBUG "cs: CardServices(Unknown func %d, " + "0x%p, 0x%p)\n", func, a1, a2); + } +#endif + switch (func) { + case AccessConfigurationRegister: + return access_configuration_register(a1, a2); break; + case AdjustResourceInfo: + return adjust_resource_info(a1, a2); break; + case CheckEraseQueue: + return check_erase_queue(a1); break; + case CloseMemory: + return close_memory(a1); break; + case CopyMemory: + return copy_memory(a1, a2); break; + case DeregisterClient: + return deregister_client(a1); break; + case DeregisterEraseQueue: + return deregister_erase_queue(a1); break; + case GetFirstClient: + return get_first_client(a1, a2); break; + case GetCardServicesInfo: + return get_card_services_info(a1); break; + case GetConfigurationInfo: + return get_configuration_info(a1, a2); break; + case GetNextClient: + return get_next_client(a1, a2); break; + case GetFirstRegion: + return get_first_region(a1, a2); break; + case GetFirstTuple: + return get_first_tuple(a1, a2); break; + case GetNextRegion: + return get_next_region(a1, a2); break; + case GetNextTuple: + return get_next_tuple(a1, a2); break; + case GetStatus: + return cs_get_status(a1, a2); break; + case GetTupleData: + return get_tuple_data(a1, a2); break; + case MapMemPage: + return map_mem_page(a1, a2); break; + case ModifyConfiguration: + return modify_configuration(a1, a2); break; + case ModifyWindow: + return modify_window(a1, a2); break; + case OpenMemory: + return open_memory(a1, a2); + case ParseTuple: + return parse_tuple(a1, a2, a3); break; + case ReadMemory: + return read_memory(a1, a2, a3); break; + case RegisterClient: + return register_client(a1, a2); break; + case RegisterEraseQueue: + return register_erase_queue(a1, a2); break; + case RegisterMTD: + return register_mtd(a1, a2); break; + case ReleaseConfiguration: + return release_configuration(a1, a2); break; + case ReleaseIO: + return release_io(a1, a2); break; + case ReleaseIRQ: + return cs_release_irq(a1, a2); break; + case ReleaseWindow: + return release_window(a1); break; + case RequestConfiguration: + return request_configuration(a1, a2); break; + case RequestIO: + return request_io(a1, a2); break; + case RequestIRQ: + return cs_request_irq(a1, a2); break; + case RequestWindow: + return request_window(a1, a2); break; + case ResetCard: + return reset_card(a1, a2); break; + case SetEventMask: + return set_event_mask(a1, a2); break; + case ValidateCIS: + return validate_cis(a1, a2); break; + case WriteMemory: + return write_memory(a1, a2, a3); break; + case BindDevice: + return bind_device(a1); break; + case BindMTD: + return bind_mtd(a1); break; + case ReportError: + return report_error(a1, a2); break; + case SuspendCard: + return suspend_card(a1, a2); break; + case ResumeCard: + return resume_card(a1, a2); break; + case EjectCard: + return eject_card(a1, a2); break; + case InsertCard: + return insert_card(a1, a2); break; + case ReplaceCIS: + return replace_cis(a1, a2); break; + case GetFirstWindow: + return get_first_window(a1, a2); break; + case GetNextWindow: + return get_next_window(a1, a2); break; + case GetMemPage: + return get_mem_page(a1, a2); break; + default: + return CS_UNSUPPORTED_FUNCTION; break; + } + +} /* CardServices */ + +/*====================================================================== + + OS-specific module glue goes here + +======================================================================*/ + +#include <linux/pci.h> + +#if (LINUX_VERSION_CODE <= VERSION(2,1,17)) + +#undef CONFIG_MODVERSIONS +static struct symbol_table cs_symtab = { +#include <linux/symtab_begin.h> +#undef X +#define X(sym) { (void *)&sym, SYMBOL_NAME_STR(sym) } + X(register_ss_entry), + X(unregister_ss_entry), + X(CardServices), + X(MTDHelperEntry), +#ifdef HAS_PROC_BUS + X(proc_pccard), +#endif +#ifndef HAVE_MEMRESERVE + X(request_mem_region), + X(release_mem_region), +#endif +#ifdef CONFIG_PNP_BIOS + X(check_pnp_irq), +#endif +#ifdef CONFIG_PCI + X(pci_irq_mask), + X(pci_devices), + X(pci_root), + X(pci_find_slot), + X(pci_find_class), + X(pci_enable_device), + X(pci_set_power_state), +#endif +#include <linux/symtab_end.h> +}; + +#else + +EXPORT_SYMBOL(register_ss_entry); +EXPORT_SYMBOL(unregister_ss_entry); +EXPORT_SYMBOL(CardServices); +EXPORT_SYMBOL(MTDHelperEntry); +#ifdef HAS_PROC_BUS +EXPORT_SYMBOL(proc_pccard); +#endif +#ifndef HAVE_MEMRESERVE +EXPORT_SYMBOL(request_mem_region); +EXPORT_SYMBOL(release_mem_region); +#endif +#ifdef CONFIG_PNP_BIOS +EXPORT_SYMBOL(check_pnp_irq); +#endif +#ifdef CONFIG_PCI +EXPORT_SYMBOL(pci_irq_mask); +#if (LINUX_VERSION_CODE < VERSION(2,3,24)) +EXPORT_SYMBOL(pci_enable_device); +EXPORT_SYMBOL(pci_set_power_state); +#endif +#endif + +#endif + +static int __init init_pcmcia_cs(void) +{ + printk(KERN_INFO "%s\n", release); +#ifdef UTS_RELEASE + printk(KERN_INFO " %s\n", kernel); +#endif + printk(KERN_INFO " %s\n", options); + DEBUG(0, "%s\n", version); +#ifdef CONFIG_PM + if (do_apm) + pm_register(PM_SYS_DEV, PM_SYS_PCMCIA, handle_pm_event); +#endif +#ifdef CONFIG_PCI + pci_fixup_init(); +#endif +#ifdef CONFIG_PNP_BIOS + if (do_pnp) { + pnp_bios_init(); + pnp_proc_init(); + pnp_rsrc_init(); + } +#endif + register_symtab(&cs_symtab); +#ifdef HAS_PROC_BUS + proc_pccard = proc_mkdir("pccard", proc_bus); +#ifdef CONFIG_PNP_BIOS + if (proc_pccard) { + create_proc_read_entry("ioport", 0, proc_pccard, + proc_read_io, NULL); + create_proc_read_entry("irq", 0, proc_pccard, + proc_read_irq, NULL); + } +#endif +#ifndef HAVE_MEMRESERVE + if (proc_pccard) + create_proc_read_entry("memory", 0, proc_pccard, + proc_read_mem, NULL); +#endif +#endif + return 0; +} + +static void __exit exit_pcmcia_cs(void) +{ + printk(KERN_INFO "unloading PCMCIA Card Services\n"); +#ifdef HAS_PROC_BUS + if (proc_pccard) { +#ifdef CONFIG_PNP_BIOS + remove_proc_entry("ioport", proc_pccard); + remove_proc_entry("irq", proc_pccard); +#endif +#ifndef HAVE_MEMRESERVE + remove_proc_entry("memory", proc_pccard); +#endif + remove_proc_entry("pccard", proc_bus); + } +#endif +#ifdef CONFIG_PM + if (do_apm) + pm_unregister_all(handle_pm_event); +#endif +#ifdef CONFIG_PCI + pci_fixup_done(); +#endif +#ifdef CONFIG_PNP_BIOS + if (do_pnp) { + pnp_proc_done(); + pnp_rsrc_done(); + } +#endif + release_resource_db(); +} + +module_init(init_pcmcia_cs); +module_exit(exit_pcmcia_cs); diff --git a/linux/pcmcia-cs/modules/cs_internal.h b/linux/pcmcia-cs/modules/cs_internal.h new file mode 100644 index 0000000..c4d4d45 --- /dev/null +++ b/linux/pcmcia-cs/modules/cs_internal.h @@ -0,0 +1,300 @@ +/* + * cs_internal.h 1.58 2004/04/25 17:58:22 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in + * which case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use + * your version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + */ + +#ifndef _LINUX_CS_INTERNAL_H +#define _LINUX_CS_INTERNAL_H + +#include <linux/config.h> +#include <linux/spinlock.h> + +typedef struct erase_busy_t { + eraseq_entry_t *erase; + client_handle_t client; + struct timer_list timeout; + struct erase_busy_t *prev, *next; +} erase_busy_t; + +#define ERASEQ_MAGIC 0xFA67 +typedef struct eraseq_t { + u_short eraseq_magic; + client_handle_t handle; + int count; + eraseq_entry_t *entry; +} eraseq_t; + +#define CLIENT_MAGIC 0x51E6 +typedef struct client_t { + u_short client_magic; + socket_t Socket; + u_char Function; + dev_info_t dev_info; + u_int Attributes; + u_int state; + event_t EventMask, PendingEvents; + int (*event_handler)(event_t event, int priority, + event_callback_args_t *); + event_callback_args_t event_callback_args; + struct client_t *next; + u_int mtd_count; + wait_queue_head_t mtd_req; + erase_busy_t erase_busy; +} client_t; + +/* Flags in client state */ +#define CLIENT_CONFIG_LOCKED 0x0001 +#define CLIENT_IRQ_REQ 0x0002 +#define CLIENT_IO_REQ 0x0004 +#define CLIENT_UNBOUND 0x0008 +#define CLIENT_STALE 0x0010 +#define CLIENT_WIN_REQ(i) (0x20<<(i)) +#define CLIENT_CARDBUS 0x8000 + +typedef struct io_window_t { + u_int Attributes; + ioaddr_t BasePort, NumPorts; + ioaddr_t InUse, Config; +} io_window_t; + +#define WINDOW_MAGIC 0xB35C +typedef struct window_t { + u_short magic; + u_short index; + client_handle_t handle; + struct socket_info_t *sock; + u_long base; + u_long size; + pccard_mem_map ctl; +} window_t; + +#define REGION_MAGIC 0xE3C9 +typedef struct region_t { + u_short region_magic; + u_short state; + dev_info_t dev_info; + client_handle_t mtd; + u_int MediaID; + region_info_t info; +} region_t; + +#define REGION_STALE 0x01 + +/* Each card function gets one of these guys */ +typedef struct config_t { + u_int state; + u_int Attributes; + u_int Vcc, Vpp1, Vpp2; + u_int IntType; + u_int ConfigBase; + u_char Status, Pin, Copy, Option, ExtStatus; + u_int Present; + u_int CardValues; + io_req_t io; + struct { + u_int Attributes; + } irq; +} config_t; + +/* Maximum number of IO windows per socket */ +#define MAX_IO_WIN 2 + +/* Maximum number of memory windows per socket */ +#define MAX_WIN 4 + +/* The size of the CIS cache */ +#define MAX_CIS_TABLE 64 +#define MAX_CIS_DATA 512 + +typedef struct socket_info_t { +#ifdef USE_SPIN_LOCKS + spinlock_t lock; +#endif + ss_entry_t ss_entry; + u_int sock; + socket_state_t socket; + socket_cap_t cap; + u_int state; + u_short functions; + u_short lock_count; + client_handle_t clients; + u_int real_clients; + client_handle_t reset_handle; + struct timer_list setup, shutdown; + u_long setup_timeout; + pccard_mem_map cis_mem; + u_char *cis_virt; + config_t *config; +#ifdef CONFIG_CARDBUS + u_int cb_cis_space; + cb_bridge_map cb_cis_map; + u_char *cb_cis_virt; + struct cb_config_t *cb_config; +#endif + struct { + u_int AssignedIRQ; + u_int Config; + } irq; + io_window_t io[MAX_IO_WIN]; + window_t win[MAX_WIN]; + region_t *c_region, *a_region; + erase_busy_t erase_busy; + int cis_used; + struct { + u_int addr; + u_short len; + u_short attr; + } cis_table[MAX_CIS_TABLE]; + char cis_cache[MAX_CIS_DATA]; + u_int fake_cis_len; + char *fake_cis; +#ifdef HAS_PROC_BUS + struct proc_dir_entry *proc; +#endif +} socket_info_t; + +/* Flags in config state */ +#define CONFIG_LOCKED 0x01 +#define CONFIG_IRQ_REQ 0x02 +#define CONFIG_IO_REQ 0x04 + +/* Flags in socket state */ +#define SOCKET_PRESENT 0x0008 +#define SOCKET_SETUP_PENDING 0x0010 +#define SOCKET_SHUTDOWN_PENDING 0x0020 +#define SOCKET_RESET_PENDING 0x0040 +#define SOCKET_SUSPEND 0x0080 +#define SOCKET_WIN_REQ(i) (0x0100<<(i)) +#define SOCKET_IO_REQ(i) (0x1000<<(i)) +#define SOCKET_REGION_INFO 0x4000 +#define SOCKET_CARDBUS 0x8000 + +#define CHECK_HANDLE(h) \ + (((h) == NULL) || ((h)->client_magic != CLIENT_MAGIC)) + +#define CHECK_SOCKET(s) \ + (((s) >= sockets) || (socket_table[s]->ss_entry == NULL)) + +#define SOCKET(h) (socket_table[(h)->Socket]) +#define CONFIG(h) (&SOCKET(h)->config[(h)->Function]) + +#define CHECK_REGION(r) \ + (((r) == NULL) || ((r)->region_magic != REGION_MAGIC)) + +#define CHECK_ERASEQ(q) \ + (((q) == NULL) || ((q)->eraseq_magic != ERASEQ_MAGIC)) + +#define EVENT(h, e, p) \ + ((h)->event_handler((e), (p), &(h)->event_callback_args)) + +/* In cardbus.c */ +int cb_alloc(socket_info_t *s); +void cb_free(socket_info_t *s); +int cb_config(socket_info_t *s); +void cb_release(socket_info_t *s); +void cb_enable(socket_info_t *s); +void cb_disable(socket_info_t *s); +int read_cb_mem(socket_info_t *s, u_char fn, int space, + u_int addr, u_int len, void *ptr); +void cb_release_cis_mem(socket_info_t *s); + +/* In cistpl.c */ +int read_cis_mem(socket_info_t *s, int attr, + u_int addr, u_int len, void *ptr); +void write_cis_mem(socket_info_t *s, int attr, + u_int addr, u_int len, void *ptr); +void release_cis_mem(socket_info_t *s); +int verify_cis_cache(socket_info_t *s); +void preload_cis_cache(socket_info_t *s); +int get_first_tuple(client_handle_t handle, tuple_t *tuple); +int get_next_tuple(client_handle_t handle, tuple_t *tuple); +int get_tuple_data(client_handle_t handle, tuple_t *tuple); +int parse_tuple(client_handle_t handle, tuple_t *tuple, cisparse_t *parse); +int validate_cis(client_handle_t handle, cisinfo_t *info); +int replace_cis(client_handle_t handle, cisdump_t *cis); +int read_tuple(client_handle_t handle, cisdata_t code, void *parse); + +/* In bulkmem.c */ +void retry_erase_list(struct erase_busy_t *list, u_int cause); +int get_first_region(client_handle_t handle, region_info_t *rgn); +int get_next_region(client_handle_t handle, region_info_t *rgn); +int register_mtd(client_handle_t handle, mtd_reg_t *reg); +int register_erase_queue(client_handle_t *handle, eraseq_hdr_t *header); +int deregister_erase_queue(eraseq_handle_t eraseq); +int check_erase_queue(eraseq_handle_t eraseq); +int open_memory(client_handle_t *handle, open_mem_t *open); +int close_memory(memory_handle_t handle); +int read_memory(memory_handle_t handle, mem_op_t *req, caddr_t buf); +int write_memory(memory_handle_t handle, mem_op_t *req, caddr_t buf); +int copy_memory(memory_handle_t handle, copy_op_t *req); + +/* In rsrc_mgr */ +void validate_mem(int (*is_valid)(u_long), int (*do_cksum)(u_long), + int force_low); +int find_io_region(ioaddr_t *base, ioaddr_t num, ioaddr_t align, + char *name); +int find_mem_region(u_long *base, u_long num, u_long align, + int force_low, char *name); +int try_irq(u_int Attributes, int irq, int specific); +void undo_irq(u_int Attributes, int irq); +int adjust_resource_info(client_handle_t handle, adjust_t *adj); +void release_resource_db(void); +int proc_read_io(char *buf, char **start, off_t pos, + int count, int *eof, void *data); +int proc_read_mem(char *buf, char **start, off_t pos, + int count, int *eof, void *data); + +/* in pnp components */ +int proc_read_irq(char *buf, char **start, off_t pos, + int count, int *eof, void *data); +int check_pnp_irq(int n); +void pnp_bios_init(void); +void pnp_proc_init(void); +void pnp_proc_done(void); +void pnp_rsrc_init(void); +void pnp_rsrc_done(void); + +/* in pci_fixup */ +void pci_fixup_init(void); +void pci_fixup_done(void); + +#define MAX_SOCK 8 +extern socket_t sockets; +extern socket_info_t *socket_table[MAX_SOCK]; + +#ifdef HAS_PROC_BUS +extern struct proc_dir_entry *proc_pccard; +#endif + +#ifdef PCMCIA_DEBUG +extern int pc_debug; +#define DEBUG(n, args...) do { if (pc_debug>(n)) printk(KERN_DEBUG args); } while (0) +#else +#define DEBUG(n, args...) do { } while (0) +#endif + +#endif /* _LINUX_CS_INTERNAL_H */ diff --git a/linux/pcmcia-cs/modules/ds.c b/linux/pcmcia-cs/modules/ds.c new file mode 100644 index 0000000..b6eb10e --- /dev/null +++ b/linux/pcmcia-cs/modules/ds.c @@ -0,0 +1,1004 @@ +/*====================================================================== + + PC Card Driver Services + + ds.c 1.115 2002/10/12 19:03:44 + + The contents of this file are subject to the Mozilla Public + License Version 1.1 (the "License"); you may not use this file + except in compliance with the License. You may obtain a copy of + the License at http://www.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS + IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + implied. See the License for the specific language governing + rights and limitations under the License. + + The initial developer of the original code is David A. Hinds + <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + + Alternatively, the contents of this file may be used under the + terms of the GNU General Public License version 2 (the "GPL"), in + which case the provisions of the GPL are applicable instead of the + above. If you wish to allow the use of your version of this file + only under the terms of the GPL and not to allow others to use + your version of this file under the MPL, indicate your decision + by deleting the provisions above and replace them with the notice + and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this + file under either the MPL or the GPL. + +======================================================================*/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/fcntl.h> +#include <linux/sched.h> +#include <linux/timer.h> +#include <linux/ioctl.h> +#include <linux/proc_fs.h> +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,1,23)) +#include <linux/poll.h> +#endif + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/bulkmem.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/ds.h> + +/*====================================================================*/ + +/* Module parameters */ + +MODULE_AUTHOR("David Hinds <dahinds@users.sourceforge.net>"); +MODULE_DESCRIPTION("PCMCIA Driver Services " CS_RELEASE); +MODULE_LICENSE("Dual MPL/GPL"); + +#define INT_MODULE_PARM(n, v) static int n = v; MODULE_PARM(n, "i") + +#ifdef PCMCIA_DEBUG +INT_MODULE_PARM(pc_debug, PCMCIA_DEBUG); +#define DEBUG(n, args...) if (pc_debug>(n)) printk(KERN_DEBUG args) +static const char *version = +"ds.c 1.115 2002/10/12 19:03:44 (David Hinds)"; +#else +#define DEBUG(n, args...) +#endif + +/*====================================================================*/ + +typedef struct driver_info_t { + dev_info_t dev_info; + int use_count, status; + dev_link_t *(*attach)(void); + void (*detach)(dev_link_t *); + struct driver_info_t *next; +} driver_info_t; + +typedef struct socket_bind_t { + driver_info_t *driver; + u_char function; + dev_link_t *instance; + struct socket_bind_t *next; +} socket_bind_t; + +/* Device user information */ +#define MAX_EVENTS 32 +#define USER_MAGIC 0x7ea4 +#define CHECK_USER(u) \ + (((u) == NULL) || ((u)->user_magic != USER_MAGIC)) +typedef struct user_info_t { + u_int user_magic; + int event_head, event_tail; + event_t event[MAX_EVENTS]; + struct user_info_t *next; +} user_info_t; + +/* Socket state information */ +typedef struct socket_info_t { + client_handle_t handle; + int state; + user_info_t *user; + int req_pending, req_result; + wait_queue_head_t queue, request; + struct timer_list removal; + socket_bind_t *bind; +} socket_info_t; + +#define SOCKET_PRESENT 0x01 +#define SOCKET_BUSY 0x02 +#define SOCKET_REMOVAL_PENDING 0x10 + +/*====================================================================*/ + +/* Device driver ID passed to Card Services */ +static dev_info_t dev_info = "Driver Services"; + +/* Linked list of all registered device drivers */ +static driver_info_t *root_driver = NULL; + +static int sockets = 0, major_dev = -1; +static socket_info_t *socket_table = NULL; + +extern struct proc_dir_entry *proc_pccard; + +/* We use this to distinguish in-kernel from modular drivers */ +static int init_status = 1; + +/*====================================================================*/ + +static void cs_error(client_handle_t handle, int func, int ret) +{ + error_info_t err = { func, ret }; + CardServices(ReportError, handle, &err); +} + +/*====================================================================== + + Register_pccard_driver() and unregister_pccard_driver() are used + tell Driver Services that a PC Card client driver is available to + be bound to sockets. + +======================================================================*/ + +int register_pccard_driver(dev_info_t *dev_info, + dev_link_t *(*attach)(void), + void (*detach)(dev_link_t *)) +{ + driver_info_t *driver; + socket_bind_t *b; + int i; + + DEBUG(0, "ds: register_pccard_driver('%s')\n", (char *)dev_info); + for (driver = root_driver; driver; driver = driver->next) + if (strncmp((char *)dev_info, (char *)driver->dev_info, + DEV_NAME_LEN) == 0) + break; + if (!driver) { + driver = kmalloc(sizeof(driver_info_t), GFP_KERNEL); + if (!driver) return -ENOMEM; + strncpy(driver->dev_info, (char *)dev_info, DEV_NAME_LEN); + driver->use_count = 0; + driver->status = init_status; + driver->next = root_driver; + root_driver = driver; + } + + driver->attach = attach; + driver->detach = detach; + if (driver->use_count == 0) return 0; + + /* Instantiate any already-bound devices */ + for (i = 0; i < sockets; i++) + for (b = socket_table[i].bind; b; b = b->next) { + if (b->driver != driver) continue; + b->instance = driver->attach(); + if (b->instance == NULL) + printk(KERN_NOTICE "ds: unable to create instance " + "of '%s'!\n", driver->dev_info); + } + + return 0; +} /* register_pccard_driver */ + +/*====================================================================*/ + +int unregister_pccard_driver(dev_info_t *dev_info) +{ + driver_info_t *target, **d = &root_driver; + socket_bind_t *b; + int i; + + DEBUG(0, "ds: unregister_pccard_driver('%s')\n", + (char *)dev_info); + while ((*d) && (strncmp((*d)->dev_info, (char *)dev_info, + DEV_NAME_LEN) != 0)) + d = &(*d)->next; + if (*d == NULL) + return -ENODEV; + + target = *d; + if (target->use_count == 0) { + *d = target->next; + kfree(target); + } else { + /* Blank out any left-over device instances */ + target->attach = NULL; target->detach = NULL; + for (i = 0; i < sockets; i++) + for (b = socket_table[i].bind; b; b = b->next) + if (b->driver == target) b->instance = NULL; + } + return 0; +} /* unregister_pccard_driver */ + +/*====================================================================*/ + +#ifdef HAS_PROC_BUS +static int proc_read_drivers(char *buf, char **start, off_t pos, + int count, int *eof, void *data) +{ + driver_info_t *d; + char *p = buf; + for (d = root_driver; d; d = d->next) + p += sprintf(p, "%-24.24s %d %d\n", d->dev_info, + d->status, d->use_count); + return (p - buf); +} +#endif + +/*====================================================================== + + These manage a ring buffer of events pending for one user process + +======================================================================*/ + +static int queue_empty(user_info_t *user) +{ + return (user->event_head == user->event_tail); +} + +static event_t get_queued_event(user_info_t *user) +{ + user->event_tail = (user->event_tail+1) % MAX_EVENTS; + return user->event[user->event_tail]; +} + +static void queue_event(user_info_t *user, event_t event) +{ + user->event_head = (user->event_head+1) % MAX_EVENTS; + if (user->event_head == user->event_tail) + user->event_tail = (user->event_tail+1) % MAX_EVENTS; + user->event[user->event_head] = event; +} + +static void handle_event(socket_info_t *s, event_t event) +{ + user_info_t *user; + for (user = s->user; user; user = user->next) + queue_event(user, event); + wake_up_interruptible(&s->queue); +} + +static int handle_request(socket_info_t *s, event_t event) +{ + if (s->req_pending != 0) + return CS_IN_USE; + if (s->state & SOCKET_BUSY) + s->req_pending = 1; + handle_event(s, event); + if (s->req_pending > 0) { + interruptible_sleep_on(&s->request); + if (signal_pending(current)) + return CS_IN_USE; + else + return s->req_result; + } + return CS_SUCCESS; +} + +static void handle_removal(u_long sn) +{ + socket_info_t *s = &socket_table[sn]; + handle_event(s, CS_EVENT_CARD_REMOVAL); + s->state &= ~SOCKET_REMOVAL_PENDING; +} + +/*====================================================================== + + The card status event handler. + +======================================================================*/ + +static int ds_event(event_t event, int priority, + event_callback_args_t *args) +{ + socket_info_t *s; + int i; + + DEBUG(1, "ds: ds_event(0x%06x, %d, 0x%p)\n", + event, priority, args->client_handle); + s = args->client_data; + i = s - socket_table; + + switch (event) { + + case CS_EVENT_CARD_REMOVAL: + s->state &= ~SOCKET_PRESENT; + if (!(s->state & SOCKET_REMOVAL_PENDING)) { + s->state |= SOCKET_REMOVAL_PENDING; + s->removal.expires = jiffies + HZ/10; + add_timer(&s->removal); + } + break; + + case CS_EVENT_CARD_INSERTION: + s->state |= SOCKET_PRESENT; + handle_event(s, event); + break; + + case CS_EVENT_EJECTION_REQUEST: + return handle_request(s, event); + break; + + default: + handle_event(s, event); + break; + } + + return 0; +} /* ds_event */ + +/*====================================================================== + + bind_mtd() connects a memory region with an MTD client. + +======================================================================*/ + +static int bind_mtd(int i, mtd_info_t *mtd_info) +{ + mtd_bind_t bind_req; + int ret; + + bind_req.dev_info = &mtd_info->dev_info; + bind_req.Attributes = mtd_info->Attributes; + bind_req.Socket = i; + bind_req.CardOffset = mtd_info->CardOffset; + ret = CardServices(BindMTD, &bind_req); + if (ret != CS_SUCCESS) { + cs_error(NULL, BindMTD, ret); + printk(KERN_NOTICE "ds: unable to bind MTD '%s' to socket %d" + " offset 0x%x\n", + (char *)bind_req.dev_info, i, bind_req.CardOffset); + return -ENODEV; + } + return 0; +} /* bind_mtd */ + +/*====================================================================== + + bind_request() connects a socket to a particular client driver. + It looks up the specified device ID in the list of registered + drivers, binds it to the socket, and tries to create an instance + of the device. unbind_request() deletes a driver instance. + +======================================================================*/ + +static int bind_request(int i, bind_info_t *bind_info) +{ + struct driver_info_t *driver; + socket_bind_t *b; + bind_req_t bind_req; + socket_info_t *s = &socket_table[i]; + int ret; + + DEBUG(2, "bind_request(%d, '%s')\n", i, + (char *)bind_info->dev_info); + for (driver = root_driver; driver; driver = driver->next) + if (strcmp((char *)driver->dev_info, + (char *)bind_info->dev_info) == 0) + break; + if (driver == NULL) { + driver = kmalloc(sizeof(driver_info_t), GFP_KERNEL); + if (!driver) return -ENOMEM; + strncpy(driver->dev_info, bind_info->dev_info, DEV_NAME_LEN); + driver->use_count = 0; + driver->next = root_driver; + driver->attach = NULL; driver->detach = NULL; + root_driver = driver; + } + + for (b = s->bind; b; b = b->next) + if ((driver == b->driver) && + (bind_info->function == b->function)) + break; + if (b != NULL) { + bind_info->instance = b->instance; + return -EBUSY; + } + b = kmalloc(sizeof(socket_bind_t), GFP_KERNEL); + if (!b) + return -ENOMEM; + + bind_req.Socket = i; + bind_req.Function = bind_info->function; + bind_req.dev_info = &driver->dev_info; + ret = CardServices(BindDevice, &bind_req); + if (ret != CS_SUCCESS) { + cs_error(NULL, BindDevice, ret); + printk(KERN_NOTICE "ds: unable to bind '%s' to socket %d\n", + (char *)dev_info, i); + kfree(b); + return -ENODEV; + } + + /* Add binding to list for this socket */ + driver->use_count++; + b->driver = driver; + b->function = bind_info->function; + b->instance = NULL; + b->next = s->bind; + s->bind = b; + + if (driver->attach) { + b->instance = driver->attach(); + if (b->instance == NULL) { + printk(KERN_NOTICE "ds: unable to create instance " + "of '%s'!\n", (char *)bind_info->dev_info); + return -ENODEV; + } + } + + return 0; +} /* bind_request */ + +/*====================================================================*/ + +static int get_device_info(int i, bind_info_t *bind_info, int first) +{ + socket_info_t *s = &socket_table[i]; + socket_bind_t *b; + dev_node_t *node; + + for (b = s->bind; b; b = b->next) + if ((strcmp((char *)b->driver->dev_info, + (char *)bind_info->dev_info) == 0) && + (b->function == bind_info->function)) + break; + if (b == NULL) return -ENODEV; + if ((b->instance == NULL) || + (b->instance->state & DEV_CONFIG_PENDING)) + return -EAGAIN; + if (first) + node = b->instance->dev; + else + for (node = b->instance->dev; node; node = node->next) + if (node == bind_info->next) break; + if (node == NULL) return -ENODEV; + + strncpy(bind_info->name, node->dev_name, DEV_NAME_LEN); + bind_info->name[DEV_NAME_LEN-1] = '\0'; + bind_info->major = node->major; + bind_info->minor = node->minor; + bind_info->next = node->next; + + return 0; +} /* get_device_info */ + +/*====================================================================*/ + +static int unbind_request(int i, bind_info_t *bind_info) +{ + socket_info_t *s = &socket_table[i]; + socket_bind_t **b, *c; + + DEBUG(2, "unbind_request(%d, '%s')\n", i, + (char *)bind_info->dev_info); + for (b = &s->bind; *b; b = &(*b)->next) + if ((strcmp((char *)(*b)->driver->dev_info, + (char *)bind_info->dev_info) == 0) && + ((*b)->function == bind_info->function)) + break; + if (*b == NULL) + return -ENODEV; + + c = *b; + c->driver->use_count--; + if (c->driver->detach) { + if (c->instance) + c->driver->detach(c->instance); + } else { + if (c->driver->use_count == 0) { + driver_info_t **d; + for (d = &root_driver; *d; d = &((*d)->next)) + if (c->driver == *d) break; + *d = (*d)->next; + kfree(c->driver); + } + } + *b = c->next; + kfree(c); + + return 0; +} /* unbind_request */ + +/*====================================================================== + + The user-mode PC Card device interface + +======================================================================*/ + +static int ds_open(struct inode *inode, struct file *file) +{ + socket_t i = MINOR(inode->i_rdev); + socket_info_t *s; + user_info_t *user; + + DEBUG(0, "ds_open(socket %d)\n", i); + if ((i >= sockets) || (sockets == 0)) + return -ENODEV; + s = &socket_table[i]; + if ((file->f_flags & O_ACCMODE) != O_RDONLY) { + if (s->state & SOCKET_BUSY) + return -EBUSY; + else + s->state |= SOCKET_BUSY; + } + + MOD_INC_USE_COUNT; + user = kmalloc(sizeof(user_info_t), GFP_KERNEL); + if (!user) { + MOD_DEC_USE_COUNT; + return -ENOMEM; + } + user->event_tail = user->event_head = 0; + user->next = s->user; + user->user_magic = USER_MAGIC; + s->user = user; + file->private_data = user; + + if (s->state & SOCKET_PRESENT) + queue_event(user, CS_EVENT_CARD_INSERTION); + return 0; +} /* ds_open */ + +/*====================================================================*/ + +static FS_RELEASE_T ds_release(struct inode *inode, struct file *file) +{ + socket_t i = MINOR(inode->i_rdev); + socket_info_t *s; + user_info_t *user, **link; + + DEBUG(0, "ds_release(socket %d)\n", i); + if ((i >= sockets) || (sockets == 0)) + return (FS_RELEASE_T)0; + s = &socket_table[i]; + user = file->private_data; + if (CHECK_USER(user)) + return (FS_RELEASE_T)0; + + /* Unlink user data structure */ + if ((file->f_flags & O_ACCMODE) != O_RDONLY) + s->state &= ~SOCKET_BUSY; + file->private_data = NULL; + for (link = &s->user; *link; link = &(*link)->next) + if (*link == user) break; + if (link == NULL) + return (FS_RELEASE_T)0; + *link = user->next; + user->user_magic = 0; + kfree(user); + + MOD_DEC_USE_COUNT; + return (FS_RELEASE_T)0; +} /* ds_release */ + +/*====================================================================*/ + +static ssize_t ds_read FOPS(struct inode *inode, + struct file *file, char *buf, + size_t count, loff_t *ppos) +{ + socket_t i = MINOR(F_INODE(file)->i_rdev); + socket_info_t *s; + user_info_t *user; + + DEBUG(2, "ds_read(socket %d)\n", i); + + if ((i >= sockets) || (sockets == 0)) + return -ENODEV; + if (count < 4) + return -EINVAL; + s = &socket_table[i]; + user = file->private_data; + if (CHECK_USER(user)) + return -EIO; + + if (queue_empty(user)) { + interruptible_sleep_on(&s->queue); + if (signal_pending(current)) + return -EINTR; + } + put_user(get_queued_event(user), (int *)buf); + return 4; +} /* ds_read */ + +/*====================================================================*/ + +static ssize_t ds_write FOPS(struct inode *inode, + struct file *file, const char *buf, + size_t count, loff_t *ppos) +{ + socket_t i = MINOR(F_INODE(file)->i_rdev); + socket_info_t *s; + user_info_t *user; + + DEBUG(2, "ds_write(socket %d)\n", i); + + if ((i >= sockets) || (sockets == 0)) + return -ENODEV; + if (count != 4) + return -EINVAL; + if ((file->f_flags & O_ACCMODE) == O_RDONLY) + return -EBADF; + s = &socket_table[i]; + user = file->private_data; + if (CHECK_USER(user)) + return -EIO; + + if (s->req_pending) { + s->req_pending--; + get_user(s->req_result, (int *)buf); + if ((s->req_result != 0) || (s->req_pending == 0)) + wake_up_interruptible(&s->request); + } else + return -EIO; + + return 4; +} /* ds_write */ + +/*====================================================================*/ + +#if (LINUX_VERSION_CODE < VERSION(2,1,23)) + +static int ds_select(struct inode *inode, struct file *file, + int sel_type, select_table *wait) +{ + socket_t i = MINOR(inode->i_rdev); + socket_info_t *s; + user_info_t *user; + + DEBUG(2, "ds_select(socket %d)\n", i); + + if ((i >= sockets) || (sockets == 0)) + return -ENODEV; + s = &socket_table[i]; + user = file->private_data; + if (CHECK_USER(user)) + return -EIO; + if (sel_type != SEL_IN) + return 0; + if (!queue_empty(user)) + return 1; + select_wait(&s->queue, wait); + return 0; +} /* ds_select */ + +#else + +static u_int ds_poll(struct file *file, poll_table *wait) +{ + socket_t i = MINOR(F_INODE(file)->i_rdev); + socket_info_t *s; + user_info_t *user; + + DEBUG(2, "ds_poll(socket %d)\n", i); + + if ((i >= sockets) || (sockets == 0)) + return POLLERR; + s = &socket_table[i]; + user = file->private_data; + if (CHECK_USER(user)) + return POLLERR; + POLL_WAIT(file, &s->queue, wait); + if (!queue_empty(user)) + return POLLIN | POLLRDNORM; + return 0; +} /* ds_poll */ + +#endif + +/*====================================================================*/ + +static int ds_ioctl(struct inode * inode, struct file * file, + u_int cmd, u_long arg) +{ + socket_t i = MINOR(inode->i_rdev); + socket_info_t *s; + u_int size; + int ret, err; + ds_ioctl_arg_t buf; + + DEBUG(2, "ds_ioctl(socket %d, %#x, %#lx)\n", i, cmd, arg); + + if ((i >= sockets) || (sockets == 0)) + return -ENODEV; + s = &socket_table[i]; + + size = (cmd & IOCSIZE_MASK) >> IOCSIZE_SHIFT; + if (size > sizeof(ds_ioctl_arg_t)) return -EINVAL; + + /* Permission check */ + if (!(cmd & IOC_OUT) && !capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (cmd & IOC_IN) { + err = verify_area(VERIFY_READ, (char *)arg, size); + if (err) { + DEBUG(3, "ds_ioctl(): verify_read = %d\n", err); + return err; + } + } + if (cmd & IOC_OUT) { + err = verify_area(VERIFY_WRITE, (char *)arg, size); + if (err) { + DEBUG(3, "ds_ioctl(): verify_write = %d\n", err); + return err; + } + } + + err = ret = 0; + + if (cmd & IOC_IN) copy_from_user((char *)&buf, (char *)arg, size); + + switch (cmd) { + case DS_ADJUST_RESOURCE_INFO: + ret = CardServices(AdjustResourceInfo, s->handle, &buf.adjust); + break; + case DS_GET_CARD_SERVICES_INFO: + ret = CardServices(GetCardServicesInfo, &buf.servinfo); + break; + case DS_GET_CONFIGURATION_INFO: + ret = CardServices(GetConfigurationInfo, s->handle, &buf.config); + break; + case DS_GET_FIRST_TUPLE: + ret = CardServices(GetFirstTuple, s->handle, &buf.tuple); + break; + case DS_GET_NEXT_TUPLE: + ret = CardServices(GetNextTuple, s->handle, &buf.tuple); + break; + case DS_GET_TUPLE_DATA: + buf.tuple.TupleData = buf.tuple_parse.data; + buf.tuple.TupleDataMax = sizeof(buf.tuple_parse.data); + ret = CardServices(GetTupleData, s->handle, &buf.tuple); + break; + case DS_PARSE_TUPLE: + buf.tuple.TupleData = buf.tuple_parse.data; + ret = CardServices(ParseTuple, s->handle, &buf.tuple, + &buf.tuple_parse.parse); + break; + case DS_RESET_CARD: + ret = CardServices(ResetCard, s->handle, NULL); + break; + case DS_GET_STATUS: + ret = CardServices(GetStatus, s->handle, &buf.status); + break; + case DS_VALIDATE_CIS: + ret = CardServices(ValidateCIS, s->handle, &buf.cisinfo); + break; + case DS_SUSPEND_CARD: + ret = CardServices(SuspendCard, s->handle, NULL); + break; + case DS_RESUME_CARD: + ret = CardServices(ResumeCard, s->handle, NULL); + break; + case DS_EJECT_CARD: + ret = CardServices(EjectCard, s->handle, NULL); + break; + case DS_INSERT_CARD: + ret = CardServices(InsertCard, s->handle, NULL); + break; + case DS_ACCESS_CONFIGURATION_REGISTER: + if ((buf.conf_reg.Action == CS_WRITE) && !capable(CAP_SYS_ADMIN)) + return -EPERM; + ret = CardServices(AccessConfigurationRegister, s->handle, + &buf.conf_reg); + break; + case DS_GET_FIRST_REGION: + ret = CardServices(GetFirstRegion, s->handle, &buf.region); + break; + case DS_GET_NEXT_REGION: + ret = CardServices(GetNextRegion, s->handle, &buf.region); + break; + case DS_GET_FIRST_WINDOW: + buf.win_info.handle = (window_handle_t)s->handle; + ret = CardServices(GetFirstWindow, &buf.win_info.handle, + &buf.win_info.window); + break; + case DS_GET_NEXT_WINDOW: + ret = CardServices(GetNextWindow, &buf.win_info.handle, + &buf.win_info.window); + break; + case DS_GET_MEM_PAGE: + ret = CardServices(GetMemPage, buf.win_info.handle, + &buf.win_info.map); + break; + case DS_REPLACE_CIS: + ret = CardServices(ReplaceCIS, s->handle, &buf.cisdump); + break; + case DS_BIND_REQUEST: + if (!capable(CAP_SYS_ADMIN)) return -EPERM; + err = bind_request(i, &buf.bind_info); + break; + case DS_GET_DEVICE_INFO: + err = get_device_info(i, &buf.bind_info, 1); + break; + case DS_GET_NEXT_DEVICE: + err = get_device_info(i, &buf.bind_info, 0); + break; + case DS_UNBIND_REQUEST: + err = unbind_request(i, &buf.bind_info); + break; + case DS_BIND_MTD: + if (!capable(CAP_SYS_ADMIN)) return -EPERM; + err = bind_mtd(i, &buf.mtd_info); + break; + default: + err = -EINVAL; + } + + if ((err == 0) && (ret != CS_SUCCESS)) { + DEBUG(2, "ds_ioctl: ret = %d\n", ret); + switch (ret) { + case CS_BAD_SOCKET: case CS_NO_CARD: + err = -ENODEV; break; + case CS_BAD_ARGS: case CS_BAD_ATTRIBUTE: case CS_BAD_IRQ: + case CS_BAD_TUPLE: + err = -EINVAL; break; + case CS_IN_USE: + err = -EBUSY; break; + case CS_OUT_OF_RESOURCE: + err = -ENOSPC; break; + case CS_NO_MORE_ITEMS: + err = -ENODATA; break; + case CS_UNSUPPORTED_FUNCTION: + err = -ENOSYS; break; + default: + err = -EIO; break; + } + } + + if (cmd & IOC_OUT) copy_to_user((char *)arg, (char *)&buf, size); + + return err; +} /* ds_ioctl */ + +/*====================================================================*/ + +static struct file_operations ds_fops = { + open: ds_open, + release: ds_release, + ioctl: ds_ioctl, + read: ds_read, + write: ds_write, +#if (LINUX_VERSION_CODE < VERSION(2,1,23)) + select: ds_select +#else + poll: ds_poll +#endif +}; + +#if (LINUX_VERSION_CODE <= VERSION(2,1,17)) + +#undef CONFIG_MODVERSIONS +static struct symbol_table ds_symtab = { +#include <linux/symtab_begin.h> +#undef X +#define X(sym) { (void *)&sym, SYMBOL_NAME_STR(sym) } + X(register_pccard_driver), + X(unregister_pccard_driver), +#include <linux/symtab_end.h> +}; + +#else + +EXPORT_SYMBOL(register_pccard_driver); +EXPORT_SYMBOL(unregister_pccard_driver); + +#endif + +/*====================================================================*/ + +int __init init_pcmcia_ds(void) +{ + client_reg_t client_reg; + servinfo_t serv; + bind_req_t bind; + socket_info_t *s; + int i, ret; + + DEBUG(0, "%s\n", version); + + CardServices(GetCardServicesInfo, &serv); + if (serv.Revision != CS_RELEASE_CODE) { + printk(KERN_NOTICE "ds: Card Services release does not match!\n"); + return -EINVAL; + } + if (serv.Count == 0) { + printk(KERN_NOTICE "ds: no socket drivers loaded!\n"); + return -1; + } + + sockets = serv.Count; + socket_table = kmalloc(sockets*sizeof(socket_info_t), GFP_KERNEL); + if (!socket_table) return -1; + for (i = 0, s = socket_table; i < sockets; i++, s++) { + s->state = 0; + s->user = NULL; + s->req_pending = 0; + init_waitqueue_head(&s->queue); + init_waitqueue_head(&s->request); + s->handle = NULL; + init_timer(&s->removal); + s->removal.data = i; + s->removal.function = &handle_removal; + s->bind = NULL; + } + + /* Set up hotline to Card Services */ + client_reg.dev_info = bind.dev_info = &dev_info; + client_reg.Attributes = INFO_MASTER_CLIENT; + client_reg.EventMask = + CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL | + CS_EVENT_RESET_PHYSICAL | CS_EVENT_CARD_RESET | + CS_EVENT_EJECTION_REQUEST | CS_EVENT_INSERTION_REQUEST | + CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME; + client_reg.event_handler = &ds_event; + client_reg.Version = 0x0210; + for (i = 0; i < sockets; i++) { + bind.Socket = i; + bind.Function = BIND_FN_ALL; + ret = CardServices(BindDevice, &bind); + if (ret != CS_SUCCESS) { + cs_error(NULL, BindDevice, ret); + break; + } + client_reg.event_callback_args.client_data = &socket_table[i]; + ret = CardServices(RegisterClient, &socket_table[i].handle, + &client_reg); + if (ret != CS_SUCCESS) { + cs_error(NULL, RegisterClient, ret); + break; + } + } + + /* Set up character device for user mode clients */ + i = register_chrdev(0, "pcmcia", &ds_fops); + if (i == -EBUSY) + printk(KERN_NOTICE "unable to find a free device # for " + "Driver Services\n"); + else + major_dev = i; + register_symtab(&ds_symtab); + +#ifdef HAS_PROC_BUS + if (proc_pccard) + create_proc_read_entry("drivers", 0, proc_pccard, + proc_read_drivers, NULL); + init_status = 0; +#endif + return 0; +} + +#ifdef MODULE + +int __init init_module(void) +{ + return init_pcmcia_ds(); +} + +void __exit cleanup_module(void) +{ + int i; +#ifdef HAS_PROC_BUS + if (proc_pccard) + remove_proc_entry("drivers", proc_pccard); +#endif + if (major_dev != -1) + unregister_chrdev(major_dev, "pcmcia"); + for (i = 0; i < sockets; i++) + CardServices(DeregisterClient, socket_table[i].handle); + sockets = 0; + kfree(socket_table); +} + +#endif diff --git a/linux/pcmcia-cs/modules/ene.h b/linux/pcmcia-cs/modules/ene.h new file mode 100644 index 0000000..6b9b18b --- /dev/null +++ b/linux/pcmcia-cs/modules/ene.h @@ -0,0 +1,59 @@ +/* + * ene.h 1.2 2001/08/24 12:15:33 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in + * which case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use + * your version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + */ + +#ifndef _LINUX_ENE_H +#define _LINUX_ENE_H + +#ifndef PCI_VENDOR_ID_ENE +#define PCI_VENDOR_ID_ENE 0x1524 +#endif + +#ifndef PCI_DEVICE_ID_ENE_1211 +#define PCI_DEVICE_ID_ENE_1211 0x1211 +#endif +#ifndef PCI_DEVICE_ID_ENE_1225 +#define PCI_DEVICE_ID_ENE_1225 0x1225 +#endif +#ifndef PCI_DEVICE_ID_ENE_1410 +#define PCI_DEVICE_ID_ENE_1410 0x1410 +#endif +#ifndef PCI_DEVICE_ID_ENE_1420 +#define PCI_DEVICE_ID_ENE_1420 0x1420 +#endif + +#define ENE_PCIC_ID \ + IS_ENE1211, IS_ENE1225, IS_ENE1410, IS_ENE1420 + +#define ENE_PCIC_INFO \ + { "ENE 1211", IS_TI|IS_CARDBUS, ID(ENE, 1211) }, \ + { "ENE 1225", IS_TI|IS_CARDBUS, ID(ENE, 1225) }, \ + { "ENE 1410", IS_TI|IS_CARDBUS, ID(ENE, 1410) }, \ + { "ENE 1420", IS_TI|IS_CARDBUS, ID(ENE, 1420) } + +#endif /* _LINUX_ENE_H */ diff --git a/linux/pcmcia-cs/modules/i82365.c b/linux/pcmcia-cs/modules/i82365.c new file mode 100644 index 0000000..fc1c782 --- /dev/null +++ b/linux/pcmcia-cs/modules/i82365.c @@ -0,0 +1,2570 @@ +/*====================================================================== + + Device driver for Intel 82365 and compatible PC Card controllers, + and Yenta-compatible PCI-to-CardBus controllers. + + i82365.c 1.358 2003/09/13 17:34:01 + + The contents of this file are subject to the Mozilla Public + License Version 1.1 (the "License"); you may not use this file + except in compliance with the License. You may obtain a copy of + the License at http://www.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS + IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + implied. See the License for the specific language governing + rights and limitations under the License. + + The initial developer of the original code is David A. Hinds + <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + + Alternatively, the contents of this file may be used under the + terms of the GNU General Public License version 2 (the "GPL"), in + which case the provisions of the GPL are applicable instead of the + above. If you wish to allow the use of your version of this file + only under the terms of the GPL and not to allow others to use + your version of this file under the MPL, indicate your decision + by deleting the provisions above and replace them with the notice + and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this + file under either the MPL or the GPL. + +======================================================================*/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/config.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/string.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/timer.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/pci.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/proc_fs.h> +#include <linux/spinlock.h> +#include <asm/irq.h> +#include <asm/io.h> +#include <asm/bitops.h> +#include <asm/system.h> + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/ss.h> +#include <pcmcia/cs.h> + +/* ISA-bus controllers */ +#include "i82365.h" +#include "cirrus.h" +#include "vg468.h" +#include "ricoh.h" +#include "o2micro.h" + +/* PCI-bus controllers */ +#include "yenta.h" +#include "ti113x.h" +#include "smc34c90.h" +#include "topic.h" +#include "ene.h" + +/*====================================================================*/ + +/* Module parameters */ + +MODULE_AUTHOR("David Hinds <dahinds@users.sourceforge.net>"); +MODULE_DESCRIPTION("Intel ExCA/Yenta PCMCIA socket driver"); +MODULE_LICENSE("Dual MPL/GPL"); + +#define INT_MODULE_PARM(n, v) static int n = v; MODULE_PARM(n, "i") + +/* General options */ +INT_MODULE_PARM(poll_interval, 0); /* in ticks, 0 means never */ +INT_MODULE_PARM(cycle_time, 120); /* in ns, 120 ns = 8.33 MHz */ +INT_MODULE_PARM(do_scan, 1); /* Probe free interrupts? */ + +/* Cirrus options */ +INT_MODULE_PARM(has_dma, -1); +INT_MODULE_PARM(has_led, -1); +INT_MODULE_PARM(has_ring, -1); +INT_MODULE_PARM(has_vsense, 0); +INT_MODULE_PARM(dynamic_mode, 0); +INT_MODULE_PARM(freq_bypass, -1); +INT_MODULE_PARM(setup_time, -1); +INT_MODULE_PARM(cmd_time, -1); +INT_MODULE_PARM(recov_time, -1); + +#ifdef CONFIG_ISA +INT_MODULE_PARM(i365_base, 0x3e0); /* IO address for probes */ +INT_MODULE_PARM(extra_sockets, 0); /* Probe at i365_base+2? */ +INT_MODULE_PARM(ignore, -1); /* Ignore this socket # */ +INT_MODULE_PARM(cs_irq, 0); /* card status irq */ +INT_MODULE_PARM(irq_mask, 0xffff); /* bit map of irq's to use */ +static int irq_list[16] = { -1 }; +MODULE_PARM(irq_list, "1-16i"); +INT_MODULE_PARM(async_clock, -1); /* Vadem specific */ +INT_MODULE_PARM(cable_mode, -1); +INT_MODULE_PARM(wakeup, 0); /* Cirrus specific */ +#endif + +#ifdef CONFIG_PCI +static int pci_irq_list[8] = { 0 }; /* PCI interrupt assignments */ +MODULE_PARM(pci_irq_list, "1-8i"); +INT_MODULE_PARM(do_pci_probe, 1); /* Scan for PCI bridges? */ +INT_MODULE_PARM(fast_pci, -1); +INT_MODULE_PARM(cb_write_post, -1); +INT_MODULE_PARM(irq_mode, -1); /* Override BIOS routing? */ +INT_MODULE_PARM(hold_time, -1); /* Ricoh specific */ +INT_MODULE_PARM(p2cclk, -1); /* TI specific */ +#endif + +#if defined(CONFIG_ISA) && defined(CONFIG_PCI) +INT_MODULE_PARM(pci_csc, 1); /* PCI card status irqs? */ +INT_MODULE_PARM(pci_int, 1); /* PCI IO card irqs? */ +#elif defined(CONFIG_ISA) && !defined(CONFIG_PCI) +#define pci_csc 0 +#define pci_int 0 +#elif !defined(CONFIG_ISA) && defined(CONFIG_PCI) +#define pci_csc 0 +#define pci_int 1 /* We must use PCI irq's */ +#else +#error "No bus architectures defined!" +#endif + +#ifdef PCMCIA_DEBUG +INT_MODULE_PARM(pc_debug, PCMCIA_DEBUG); +#define DEBUG(n, args...) if (pc_debug>(n)) printk(KERN_DEBUG args) +static const char *version = +"i82365.c 1.358 2003/09/13 17:34:01 (David Hinds)"; +#else +#define DEBUG(n, args...) do { } while (0) +#endif + +/*====================================================================*/ + +typedef struct socket_info_t { + u_short type, flags; + socket_cap_t cap; + ioaddr_t ioaddr; + u_short psock; + u_char cs_irq, intr; + void (*handler)(void *info, u_int events); + void *info; +#ifdef HAS_PROC_BUS + struct proc_dir_entry *proc; +#endif + u_char pci_irq_code; +#ifdef CONFIG_PCI + u_short vendor, device; + u_char revision, bus, devfn; + u_short bcr; + u_char pci_lat, cb_lat, sub_bus, cache; + u_int cb_phys; + char *cb_virt; +#endif + union { + cirrus_state_t cirrus; + vg46x_state_t vg46x; + o2micro_state_t o2micro; + ti113x_state_t ti113x; + ricoh_state_t ricoh; + topic_state_t topic; + } state; +} socket_info_t; + +/* Where we keep track of our sockets... */ +static int sockets = 0; +static socket_info_t socket[8] = { + { 0, }, /* ... */ +}; + +#ifdef CONFIG_ISA +static int grab_irq; +#ifdef USE_SPIN_LOCKS +static spinlock_t isa_lock = SPIN_LOCK_UNLOCKED; +#endif +#define ISA_LOCK(s, f) \ + if (!((s)->flags & IS_CARDBUS)) spin_lock_irqsave(&isa_lock, f) +#define ISA_UNLOCK(n, f) \ + if (!((s)->flags & IS_CARDBUS)) spin_unlock_irqrestore(&isa_lock, f) +#else +#define ISA_LOCK(n, f) do { } while (0) +#define ISA_UNLOCK(n, f) do { } while (0) +#endif + +static void pcic_interrupt_wrapper(u_long data); +static struct timer_list poll_timer = { + function: pcic_interrupt_wrapper +}; + +#define flip(v,b,f) (v = ((f)<0) ? v : ((f) ? ((v)|(b)) : ((v)&(~b)))) + +/*====================================================================*/ + +/* Some PCI shortcuts */ + +#ifdef CONFIG_PCI +static int pci_readb(socket_info_t *s, int r, u_char *v) +{ return pcibios_read_config_byte(s->bus, s->devfn, r, v); } +static int pci_writeb(socket_info_t *s, int r, u_char v) +{ return pcibios_write_config_byte(s->bus, s->devfn, r, v); } +static int pci_readw(socket_info_t *s, int r, u_short *v) +{ return pcibios_read_config_word(s->bus, s->devfn, r, v); } +static int pci_writew(socket_info_t *s, int r, u_short v) +{ return pcibios_write_config_word(s->bus, s->devfn, r, v); } +static int pci_readl(socket_info_t *s, int r, u_int *v) +{ return pcibios_read_config_dword(s->bus, s->devfn, r, v); } +static int pci_writel(socket_info_t *s, int r, u_int v) +{ return pcibios_write_config_dword(s->bus, s->devfn, r, v); } +#endif + +#define cb_readb(s, r) readb((s)->cb_virt + (r)) +#define cb_readl(s, r) readl((s)->cb_virt + (r)) +#define cb_writeb(s, r, v) writeb(v, (s)->cb_virt + (r)) +#define cb_writel(s, r, v) writel(v, (s)->cb_virt + (r)) + +/*====================================================================*/ + +/* These definitions must match the pcic table! */ +typedef enum pcic_id { +#ifdef CONFIG_ISA + IS_I82365A, IS_I82365B, IS_I82365DF, IS_IBM, IS_RF5Cx96, + IS_VLSI, IS_VG468, IS_VG469, IS_PD6710, IS_PD672X, IS_VT83C469, +#endif +#ifdef CONFIG_PCI + IS_I82092AA, IS_OM82C092G, CIRRUS_PCIC_ID, O2MICRO_PCIC_ID, + RICOH_PCIC_ID, SMC_PCIC_ID, TI_PCIC_ID, ENE_PCIC_ID, + TOPIC_PCIC_ID, IS_UNK_PCI, IS_UNK_CARDBUS +#endif +} pcic_id; + +/* Flags for classifying groups of controllers */ +#define IS_VADEM 0x0001 +#define IS_CIRRUS 0x0002 +#define IS_TI 0x0004 +#define IS_O2MICRO 0x0008 +#define IS_TOPIC 0x0020 +#define IS_RICOH 0x0040 +#define IS_UNKNOWN 0x0400 +#define IS_VG_PWR 0x0800 +#define IS_DF_PWR 0x1000 +#define IS_PCI 0x2000 +#define IS_CARDBUS 0x4000 +#define IS_ALIVE 0x8000 + +typedef struct pcic_t { + char *name; + u_short flags; +#ifdef CONFIG_PCI + u_short vendor, device; +#endif +} pcic_t; + +#define ID(a,b) PCI_VENDOR_ID_##a,PCI_DEVICE_ID_##a##_##b + +static pcic_t pcic[] = { +#ifdef CONFIG_ISA + { "Intel i82365sl A step", 0 }, + { "Intel i82365sl B step", 0 }, + { "Intel i82365sl DF", IS_DF_PWR }, + { "IBM Clone", 0 }, + { "Ricoh RF5C296/396", 0 }, + { "VLSI 82C146", 0 }, + { "Vadem VG-468", IS_VADEM }, + { "Vadem VG-469", IS_VADEM|IS_VG_PWR }, + { "Cirrus PD6710", IS_CIRRUS }, + { "Cirrus PD672x", IS_CIRRUS }, + { "VIA VT83C469", IS_CIRRUS }, +#endif +#ifdef CONFIG_PCI + { "Intel 82092AA", IS_PCI, ID(INTEL, 82092AA_0) }, + { "Omega Micro 82C092G", IS_PCI, ID(OMEGA, 82C092G) }, + CIRRUS_PCIC_INFO, O2MICRO_PCIC_INFO, RICOH_PCIC_INFO, + SMC_PCIC_INFO, TI_PCIC_INFO, ENE_PCIC_INFO, TOPIC_PCIC_INFO, + { "Unknown", IS_PCI|IS_UNKNOWN, 0, 0 }, + { "Unknown", IS_CARDBUS|IS_UNKNOWN, 0, 0 } +#endif +}; + +#define PCIC_COUNT (sizeof(pcic)/sizeof(pcic_t)) + +/*====================================================================*/ + +static u_char i365_get(socket_info_t *s, u_short reg) +{ +#ifdef CONFIG_PCI + if (s->cb_virt) + return cb_readb(s, 0x0800 + reg); +#endif + outb(I365_REG(s->psock, reg), s->ioaddr); + return inb(s->ioaddr+1); +} + +static void i365_set(socket_info_t *s, u_short reg, u_char data) +{ +#ifdef CONFIG_PCI + if (s->cb_virt) { + cb_writeb(s, 0x0800 + reg, data); + return; + } +#endif + outb(I365_REG(s->psock, reg), s->ioaddr); + outb(data, s->ioaddr+1); +} + +static void i365_bset(socket_info_t *s, u_short reg, u_char mask) +{ + i365_set(s, reg, i365_get(s, reg) | mask); +} + +static void i365_bclr(socket_info_t *s, u_short reg, u_char mask) +{ + i365_set(s, reg, i365_get(s, reg) & ~mask); +} + +static void i365_bflip(socket_info_t *s, u_short reg, u_char mask, int b) +{ + u_char d = i365_get(s, reg); + i365_set(s, reg, (b) ? (d | mask) : (d & ~mask)); +} + +static u_short i365_get_pair(socket_info_t *s, u_short reg) +{ + return (i365_get(s, reg) + (i365_get(s, reg+1) << 8)); +} + +static void i365_set_pair(socket_info_t *s, u_short reg, u_short data) +{ + i365_set(s, reg, data & 0xff); + i365_set(s, reg+1, data >> 8); +} + +/*====================================================================== + + Code to save and restore global state information for Cirrus + PD67xx controllers, and to set and report global configuration + options. + + The VIA controllers also use these routines, as they are mostly + Cirrus lookalikes, without the timing registers. + +======================================================================*/ + +#ifdef CONFIG_PCI + +static int __init get_pci_irq(socket_info_t *s) +{ + u8 irq = pci_irq_list[s - socket]; + if (!irq) + irq = pci_find_slot(s->bus, s->devfn)->irq; + if (irq >= NR_IRQS) irq = 0; + s->cap.pci_irq = irq; + return irq; +} + +#endif + +static void __init cirrus_get_state(socket_info_t *s) +{ + cirrus_state_t *p = &s->state.cirrus; + int i; + + p->misc1 = i365_get(s, PD67_MISC_CTL_1); + p->misc1 &= (PD67_MC1_MEDIA_ENA | PD67_MC1_INPACK_ENA); + p->misc2 = i365_get(s, PD67_MISC_CTL_2); + if (s->flags & IS_PCI) + p->ectl1 = pd67_ext_get(s, PD67_EXT_CTL_1); + for (i = 0; i < 6; i++) + p->timer[i] = i365_get(s, PD67_TIME_SETUP(0)+i); +} + +static void cirrus_set_state(socket_info_t *s) +{ + cirrus_state_t *p = &s->state.cirrus; + u_char misc; + int i; + + misc = i365_get(s, PD67_MISC_CTL_2); + i365_set(s, PD67_MISC_CTL_2, p->misc2); + if (misc & PD67_MC2_SUSPEND) mdelay(50); + misc = i365_get(s, PD67_MISC_CTL_1); + misc &= ~(PD67_MC1_MEDIA_ENA | PD67_MC1_INPACK_ENA); + i365_set(s, PD67_MISC_CTL_1, misc | p->misc1); + if (s->flags & IS_PCI) + pd67_ext_set(s, PD67_EXT_CTL_1, p->ectl1); + else if (has_vsense) { + socket_info_t *t = (s->psock) ? s : s+1; + pd67_ext_set(t, PD67_EXT_CTL_2, PD67_EC2_GPSTB_IOR); + } + for (i = 0; i < 6; i++) + i365_set(s, PD67_TIME_SETUP(0)+i, p->timer[i]); +} + +#ifdef CONFIG_PCI +static int cirrus_set_irq_mode(socket_info_t *s, int pcsc, int pint) +{ + flip(s->bcr, PD6832_BCR_MGMT_IRQ_ENA, !pcsc); + return 0; +} +#endif + +static u_int __init cirrus_set_opts(socket_info_t *s, char *buf) +{ + cirrus_state_t *p = &s->state.cirrus; + u_int mask = 0xffff; + + p->misc1 |= PD67_MC1_SPKR_ENA; + if (has_ring == -1) has_ring = 1; + flip(p->misc2, PD67_MC2_IRQ15_RI, has_ring); + flip(p->misc2, PD67_MC2_DYNAMIC_MODE, dynamic_mode); + if (p->misc2 & PD67_MC2_IRQ15_RI) + strcat(buf, " [ring]"); + if (p->misc2 & PD67_MC2_DYNAMIC_MODE) + strcat(buf, " [dyn mode]"); + if (p->misc1 & PD67_MC1_INPACK_ENA) + strcat(buf, " [inpack]"); + if (!(s->flags & (IS_PCI|IS_CARDBUS))) { + flip(p->misc2, PD67_MC2_FREQ_BYPASS, freq_bypass); + if (p->misc2 & PD67_MC2_FREQ_BYPASS) + strcat(buf, " [freq bypass]"); + if (p->misc2 & PD67_MC2_IRQ15_RI) + mask &= ~0x8000; + if (has_led > 0) { + strcat(buf, " [led]"); + mask &= ~0x1000; + } + if (has_dma > 0) { + strcat(buf, " [dma]"); + mask &= ~0x0600; + } +#ifdef CONFIG_PCI + } else { + p->misc1 &= ~PD67_MC1_MEDIA_ENA; + p->misc1 &= ~(PD67_MC1_PULSE_MGMT | PD67_MC1_PULSE_IRQ); + p->ectl1 &= ~(PD67_EC1_INV_MGMT_IRQ | PD67_EC1_INV_CARD_IRQ); + flip(p->misc2, PD67_MC2_FAST_PCI, fast_pci); + if (p->misc2 & PD67_MC2_IRQ15_RI) + mask &= (s->type == IS_PD6730) ? ~0x0400 : ~0x8000; + if ((s->flags & IS_PCI) && (irq_mode == 1) && get_pci_irq(s)) { + /* Configure PD6729 bridge for PCI interrupts */ + p->ectl1 |= PD67_EC1_INV_MGMT_IRQ | PD67_EC1_INV_CARD_IRQ; + s->pci_irq_code = 3; /* PCI INTA = "irq 3" */ + buf += strlen(buf); + sprintf(buf, " [pci irq %d]", s->cap.pci_irq); + mask = 0; + } +#endif + } +#ifdef CONFIG_ISA + if (s->type != IS_VT83C469) +#endif + { + if (setup_time >= 0) + p->timer[0] = p->timer[3] = setup_time; + if (cmd_time > 0) { + p->timer[1] = cmd_time; + p->timer[4] = cmd_time*2+4; + } + if (p->timer[1] == 0) { + p->timer[1] = 6; p->timer[4] = 16; + if (p->timer[0] == 0) + p->timer[0] = p->timer[3] = 1; + } + if (recov_time >= 0) + p->timer[2] = p->timer[5] = recov_time; + buf += strlen(buf); + sprintf(buf, " [%d/%d/%d] [%d/%d/%d]", p->timer[0], p->timer[1], + p->timer[2], p->timer[3], p->timer[4], p->timer[5]); + } + return mask; +} + +/*====================================================================== + + Code to save and restore global state information for Vadem VG468 + and VG469 controllers, and to set and report global configuration + options. + +======================================================================*/ + +#ifdef CONFIG_ISA + +static void __init vg46x_get_state(socket_info_t *s) +{ + vg46x_state_t *p = &s->state.vg46x; + p->ctl = i365_get(s, VG468_CTL); + if (s->type == IS_VG469) + p->ema = i365_get(s, VG469_EXT_MODE); +} + +static void vg46x_set_state(socket_info_t *s) +{ + vg46x_state_t *p = &s->state.vg46x; + i365_set(s, VG468_CTL, p->ctl); + if (s->type == IS_VG469) + i365_set(s, VG469_EXT_MODE, p->ema); +} + +static u_int __init vg46x_set_opts(socket_info_t *s, char *buf) +{ + vg46x_state_t *p = &s->state.vg46x; + + flip(p->ctl, VG468_CTL_ASYNC, async_clock); + flip(p->ema, VG469_MODE_CABLE, cable_mode); + if (p->ctl & VG468_CTL_ASYNC) + strcat(buf, " [async]"); + if (p->ctl & VG468_CTL_INPACK) + strcat(buf, " [inpack]"); + if (s->type == IS_VG469) { + u_char vsel = i365_get(s, VG469_VSELECT); + if (vsel & VG469_VSEL_EXT_STAT) { + strcat(buf, " [ext mode]"); + if (vsel & VG469_VSEL_EXT_BUS) + strcat(buf, " [isa buf]"); + } + if (p->ema & VG469_MODE_CABLE) + strcat(buf, " [cable]"); + if (p->ema & VG469_MODE_COMPAT) + strcat(buf, " [c step]"); + } + return 0xffff; +} + +#endif + +/*====================================================================== + + Code to save and restore global state information for TI 1130 and + TI 1131 controllers, and to set and report global configuration + options. + +======================================================================*/ + +#ifdef CONFIG_PCI + +static void __init ti113x_get_state(socket_info_t *s) +{ + ti113x_state_t *p = &s->state.ti113x; + pci_readl(s, TI113X_SYSTEM_CONTROL, &p->sysctl); + pci_readb(s, TI113X_CARD_CONTROL, &p->cardctl); + pci_readb(s, TI113X_DEVICE_CONTROL, &p->devctl); + pci_readb(s, TI1250_DIAGNOSTIC, &p->diag); + pci_readl(s, TI12XX_IRQMUX, &p->irqmux); +} + +static void ti113x_set_state(socket_info_t *s) +{ + ti113x_state_t *p = &s->state.ti113x; + pci_writel(s, TI113X_SYSTEM_CONTROL, p->sysctl); + pci_writeb(s, TI113X_CARD_CONTROL, p->cardctl); + pci_writeb(s, TI113X_DEVICE_CONTROL, p->devctl); + pci_writeb(s, TI1250_MULTIMEDIA_CTL, 0); + pci_writeb(s, TI1250_DIAGNOSTIC, p->diag); + pci_writel(s, TI12XX_IRQMUX, p->irqmux); + i365_set_pair(s, TI113X_IO_OFFSET(0), 0); + i365_set_pair(s, TI113X_IO_OFFSET(1), 0); +} + +static int ti113x_set_irq_mode(socket_info_t *s, int pcsc, int pint) +{ + ti113x_state_t *p = &s->state.ti113x; + s->intr = (pcsc) ? I365_INTR_ENA : 0; + if (s->type <= IS_TI1131) { + p->cardctl &= ~(TI113X_CCR_PCI_IRQ_ENA | + TI113X_CCR_PCI_IREQ | TI113X_CCR_PCI_CSC); + if (pcsc) + p->cardctl |= TI113X_CCR_PCI_IRQ_ENA | TI113X_CCR_PCI_CSC; + if (pint) + p->cardctl |= TI113X_CCR_PCI_IRQ_ENA | TI113X_CCR_PCI_IREQ; + } else if (s->type == IS_TI1250A) { + p->diag &= TI1250_DIAG_PCI_CSC | TI1250_DIAG_PCI_IREQ; + if (pcsc) + p->diag |= TI1250_DIAG_PCI_CSC; + if (pint) + p->diag |= TI1250_DIAG_PCI_IREQ; + } + return 0; +} + +static u_int __init ti113x_set_opts(socket_info_t *s, char *buf) +{ + ti113x_state_t *p = &s->state.ti113x; + u_int mask = 0xffff; + int old = (s->type <= IS_TI1131); + + flip(p->cardctl, TI113X_CCR_RIENB, has_ring); + p->cardctl &= ~TI113X_CCR_ZVENABLE; + p->cardctl |= TI113X_CCR_SPKROUTEN; + if (!old) flip(p->sysctl, TI122X_SCR_P2CCLK, p2cclk); + switch (irq_mode) { + case 0: + p->devctl &= ~TI113X_DCR_IMODE_MASK; + p->irqmux = (p->irqmux & ~0x0f) | 0x02; /* route INTA */ + if (!(p->sysctl & TI122X_SCR_INTRTIE)) + p->irqmux = (p->irqmux & ~0xf0) | 0x20; /* route INTB */ + break; + case 1: + p->devctl &= ~TI113X_DCR_IMODE_MASK; + p->devctl |= TI113X_DCR_IMODE_ISA; + break; + case 2: + p->devctl &= ~TI113X_DCR_IMODE_MASK; + p->devctl |= TI113X_DCR_IMODE_SERIAL; + break; + case 3: + p->devctl &= ~TI113X_DCR_IMODE_MASK; + p->devctl |= TI12XX_DCR_IMODE_ALL_SERIAL; + break; + default: + /* Feeble fallback: if PCI-only but no PCI irq, try ISA */ + if (((p->devctl & TI113X_DCR_IMODE_MASK) == 0) && + (s->cap.pci_irq == 0)) + p->devctl |= TI113X_DCR_IMODE_ISA; + } + if (p->cardctl & TI113X_CCR_RIENB) { + strcat(buf, " [ring]"); + if (old) mask &= ~0x8000; + } + if (old && (p->sysctl & TI113X_SCR_CLKRUN_ENA)) { + if (p->sysctl & TI113X_SCR_CLKRUN_SEL) { + strcat(buf, " [clkrun irq 12]"); + mask &= ~0x1000; + } else { + strcat(buf, " [clkrun irq 10]"); + mask &= ~0x0400; + } + } + switch (p->devctl & TI113X_DCR_IMODE_MASK) { + case TI12XX_DCR_IMODE_PCI_ONLY: + strcat(buf, " [pci only]"); + mask = 0; + break; + case TI113X_DCR_IMODE_ISA: + strcat(buf, " [isa irq]"); + if (old) mask &= ~0x0018; + break; + case TI113X_DCR_IMODE_SERIAL: + strcat(buf, " [pci + serial irq]"); + mask = 0xffff; + break; + case TI12XX_DCR_IMODE_ALL_SERIAL: + strcat(buf, " [serial pci & irq]"); + mask = 0xffff; + break; + } + return mask; +} + +#endif + +/*====================================================================== + + Code to save and restore global state information for the Ricoh + RL5C4XX controllers, and to set and report global configuration + options. + + The interrupt test doesn't seem to be reliable with Ricoh + bridges. It seems to depend on what type of card is in the + socket, and on the history of that socket, in some way that + doesn't show up in the current socket state. + +======================================================================*/ + +#ifdef CONFIG_PCI + +static void __init ricoh_get_state(socket_info_t *s) +{ + ricoh_state_t *p = &s->state.ricoh; + pci_readw(s, RL5C4XX_CONFIG, &p->config); + pci_readw(s, RL5C4XX_MISC, &p->misc); + pci_readw(s, RL5C4XX_16BIT_CTL, &p->ctl); + pci_readw(s, RL5C4XX_16BIT_IO_0, &p->io); + pci_readw(s, RL5C4XX_16BIT_MEM_0, &p->mem); +} + +static void ricoh_set_state(socket_info_t *s) +{ + ricoh_state_t *p = &s->state.ricoh; + pci_writew(s, RL5C4XX_CONFIG, p->config); + pci_writew(s, RL5C4XX_MISC, p->misc); + pci_writew(s, RL5C4XX_16BIT_CTL, p->ctl); + pci_writew(s, RL5C4XX_16BIT_IO_0, p->io); + pci_writew(s, RL5C4XX_16BIT_MEM_0, p->mem); +} + +static u_int __init ricoh_set_opts(socket_info_t *s, char *buf) +{ + ricoh_state_t *p = &s->state.ricoh; + u_int mask = 0xffff; + int old = (s->type < IS_RL5C475); + + p->ctl = RL5C4XX_16CTL_IO_TIMING | RL5C4XX_16CTL_MEM_TIMING; + if (old) + p->ctl |= RL5C46X_16CTL_LEVEL_1 | RL5C46X_16CTL_LEVEL_2; + else + p->config |= RL5C4XX_CONFIG_PREFETCH; + + if (setup_time >= 0) { + p->io = (p->io & ~RL5C4XX_SETUP_MASK) + + ((setup_time+1) << RL5C4XX_SETUP_SHIFT); + p->mem = (p->mem & ~RL5C4XX_SETUP_MASK) + + (setup_time << RL5C4XX_SETUP_SHIFT); + } + if (cmd_time >= 0) { + p->io = (p->io & ~RL5C4XX_CMD_MASK) + + (cmd_time << RL5C4XX_CMD_SHIFT); + p->mem = (p->mem & ~RL5C4XX_CMD_MASK) + + (cmd_time << RL5C4XX_CMD_SHIFT); + } + if (hold_time >= 0) { + p->io = (p->io & ~RL5C4XX_HOLD_MASK) + + (hold_time << RL5C4XX_HOLD_SHIFT); + p->mem = (p->mem & ~RL5C4XX_HOLD_MASK) + + (hold_time << RL5C4XX_HOLD_SHIFT); + } + if (irq_mode == 0) { + mask = 0; + p->misc &= ~RL5C47X_MISC_SRIRQ_ENA; + sprintf(buf, " [pci only]"); + buf += strlen(buf); + } else if (!old) { + switch (irq_mode) { + case 1: + p->misc &= ~RL5C47X_MISC_SRIRQ_ENA; break; + case 2: + p->misc |= RL5C47X_MISC_SRIRQ_ENA; break; + } + if (p->misc & RL5C47X_MISC_SRIRQ_ENA) + sprintf(buf, " [serial irq]"); + else + sprintf(buf, " [isa irq]"); + buf += strlen(buf); + } + sprintf(buf, " [io %d/%d/%d] [mem %d/%d/%d]", + (p->io & RL5C4XX_SETUP_MASK) >> RL5C4XX_SETUP_SHIFT, + (p->io & RL5C4XX_CMD_MASK) >> RL5C4XX_CMD_SHIFT, + (p->io & RL5C4XX_HOLD_MASK) >> RL5C4XX_HOLD_SHIFT, + (p->mem & RL5C4XX_SETUP_MASK) >> RL5C4XX_SETUP_SHIFT, + (p->mem & RL5C4XX_CMD_MASK) >> RL5C4XX_CMD_SHIFT, + (p->mem & RL5C4XX_HOLD_MASK) >> RL5C4XX_HOLD_SHIFT); + return mask; +} + +#endif + +/*====================================================================== + + Code to save and restore global state information for O2Micro + controllers, and to set and report global configuration options. + +======================================================================*/ + +#ifdef CONFIG_PCI + +static void __init o2micro_get_state(socket_info_t *s) +{ + o2micro_state_t *p = &s->state.o2micro; + if ((s->revision == 0x34) || (s->revision == 0x62) || + (s->type == IS_OZ6812)) { + p->mode_a = i365_get(s, O2_MODE_A_2); + p->mode_b = i365_get(s, O2_MODE_B_2); + } else { + p->mode_a = i365_get(s, O2_MODE_A); + p->mode_b = i365_get(s, O2_MODE_B); + } + p->mode_c = i365_get(s, O2_MODE_C); + p->mode_d = i365_get(s, O2_MODE_D); + if (s->flags & IS_CARDBUS) { + p->mhpg = i365_get(s, O2_MHPG_DMA); + p->fifo = i365_get(s, O2_FIFO_ENA); + p->mode_e = i365_get(s, O2_MODE_E); + } +} + +static void o2micro_set_state(socket_info_t *s) +{ + o2micro_state_t *p = &s->state.o2micro; + if ((s->revision == 0x34) || (s->revision == 0x62) || + (s->type == IS_OZ6812)) { + i365_set(s, O2_MODE_A_2, p->mode_a); + i365_set(s, O2_MODE_B_2, p->mode_b); + } else { + i365_set(s, O2_MODE_A, p->mode_a); + i365_set(s, O2_MODE_B, p->mode_b); + } + i365_set(s, O2_MODE_C, p->mode_c); + i365_set(s, O2_MODE_D, p->mode_d); + if (s->flags & IS_CARDBUS) { + i365_set(s, O2_MHPG_DMA, p->mhpg); + i365_set(s, O2_FIFO_ENA, p->fifo); + i365_set(s, O2_MODE_E, p->mode_e); + } +} + +static u_int __init o2micro_set_opts(socket_info_t *s, char *buf) +{ + o2micro_state_t *p = &s->state.o2micro; + u_int mask = 0xffff; + + p->mode_b = (p->mode_b & ~O2_MODE_B_IDENT) | O2_MODE_B_ID_CSTEP; + flip(p->mode_b, O2_MODE_B_IRQ15_RI, has_ring); + p->mode_c &= ~(O2_MODE_C_ZVIDEO | O2_MODE_C_DREQ_MASK); + if (s->flags & IS_CARDBUS) { + p->mode_d &= ~O2_MODE_D_W97_IRQ; + p->mode_e &= ~O2_MODE_E_MHPG_DMA; + p->mhpg = O2_MHPG_CINT_ENA | O2_MHPG_CSC_ENA; + if (s->revision == 0x34) + p->mode_c = 0x20; + } else { + if (p->mode_b & O2_MODE_B_IRQ15_RI) mask &= ~0x8000; + } + if (p->mode_b & O2_MODE_B_IRQ15_RI) + strcat(buf, " [ring]"); + if (irq_mode != -1) + p->mode_d = irq_mode; + if (p->mode_d & O2_MODE_D_ISA_IRQ) { + strcat(buf, " [pci+isa]"); + } else { + switch (p->mode_d & O2_MODE_D_IRQ_MODE) { + case O2_MODE_D_IRQ_PCPCI: + strcat(buf, " [pc/pci]"); break; + case O2_MODE_D_IRQ_PCIWAY: + strcat(buf, " [pci/way]"); break; + case O2_MODE_D_IRQ_PCI: + strcat(buf, " [pci only]"); mask = 0; break; + } + } + if (s->flags & IS_CARDBUS) { + if (p->mode_d & O2_MODE_D_W97_IRQ) + strcat(buf, " [win97]"); + } + return mask; +} + +#endif + +/*====================================================================== + + Code to save and restore global state information for the Toshiba + ToPIC 95 and 97 controllers, and to set and report global + configuration options. + +======================================================================*/ + +#ifdef CONFIG_PCI + +static void __init topic_get_state(socket_info_t *s) +{ + topic_state_t *p = &s->state.topic; + pci_readb(s, TOPIC_SLOT_CONTROL, &p->slot); + pci_readb(s, TOPIC_CARD_CONTROL, &p->ccr); + pci_readb(s, TOPIC_CARD_DETECT, &p->cdr); + pci_readl(s, TOPIC_REGISTER_CONTROL, &p->rcr); + p->fcr = i365_get(s, TOPIC_FUNCTION_CONTROL); +} + +static void topic_set_state(socket_info_t *s) +{ + topic_state_t *p = &s->state.topic; + u_int state; + pci_writeb(s, TOPIC_SLOT_CONTROL, p->slot); + pci_writeb(s, TOPIC_CARD_CONTROL, p->ccr); + pci_writeb(s, TOPIC_CARD_DETECT, p->cdr); + pci_writel(s, TOPIC_REGISTER_CONTROL, p->rcr); + i365_set(s, TOPIC_FUNCTION_CONTROL, p->fcr); + state = cb_readl(s, CB_SOCKET_STATE); + if (!(state & CB_SS_32BIT)) + cb_writel(s, CB_SOCKET_CONTROL, 0); + if (!(state & CB_SS_VSENSE)) + cb_writel(s, CB_SOCKET_FORCE, CB_SF_CVSTEST); +} + +static u_int __init topic_set_opts(socket_info_t *s, char *buf) +{ + topic_state_t *p = &s->state.topic; + + p->slot |= TOPIC_SLOT_SLOTON|TOPIC_SLOT_SLOTEN; + p->slot &= ~TOPIC_SLOT_ID_LOCK; + p->cdr |= TOPIC_CDR_MODE_PC32; + p->cdr &= ~(TOPIC_CDR_SW_DETECT); + p->ccr |= TOPIC97_ICR_IRQSEL; + p->fcr |= TOPIC_FCR_3V_ENA; + sprintf(buf, " [slot 0x%02x] [ccr 0x%02x] [cdr 0x%02x] [rcr 0x%02x]", + p->slot, p->ccr, p->cdr, p->rcr); + return 0xffff; +} + +#endif + +/*====================================================================== + + Routines to handle common CardBus options + +======================================================================*/ + +/* Default settings for PCI command configuration register */ +#define CMD_DFLT (PCI_COMMAND_IO|PCI_COMMAND_MEMORY| \ + PCI_COMMAND_MASTER|PCI_COMMAND_WAIT) + +#ifdef CONFIG_PCI + +static void __init cb_get_state(socket_info_t *s) +{ + pci_readb(s, PCI_CACHE_LINE_SIZE, &s->cache); + pci_readb(s, PCI_LATENCY_TIMER, &s->pci_lat); + pci_readb(s, CB_LATENCY_TIMER, &s->cb_lat); + pci_readb(s, CB_CARDBUS_BUS, &s->cap.cardbus); + pci_readb(s, CB_SUBORD_BUS, &s->sub_bus); + pci_readw(s, CB_BRIDGE_CONTROL, &s->bcr); + get_pci_irq(s); +} + +static void cb_set_state(socket_info_t *s) +{ + pci_set_power_state(pci_find_slot(s->bus, s->devfn), 0); + pci_writel(s, CB_LEGACY_MODE_BASE, 0); + pci_writel(s, PCI_BASE_ADDRESS_0, s->cb_phys); + pci_writew(s, PCI_COMMAND, CMD_DFLT); + pci_writeb(s, PCI_CACHE_LINE_SIZE, s->cache); + pci_writeb(s, PCI_LATENCY_TIMER, s->pci_lat); + pci_writeb(s, CB_LATENCY_TIMER, s->cb_lat); + pci_writeb(s, CB_CARDBUS_BUS, s->cap.cardbus); + pci_writeb(s, CB_SUBORD_BUS, s->sub_bus); + pci_writew(s, CB_BRIDGE_CONTROL, s->bcr); +} + +static int cb_get_irq_mode(socket_info_t *s) +{ + return (!(s->bcr & CB_BCR_ISA_IRQ)); +} + +static int cb_set_irq_mode(socket_info_t *s, int pcsc, int pint) +{ + flip(s->bcr, CB_BCR_ISA_IRQ, !(pint)); + if (s->flags & IS_CIRRUS) + return cirrus_set_irq_mode(s, pcsc, pint); + else if (s->flags & IS_TI) + return ti113x_set_irq_mode(s, pcsc, pint); + /* By default, assume that we can't do ISA status irqs */ + return (!pcsc); +} + +static void __init cb_set_opts(socket_info_t *s, char *buf) +{ + s->bcr |= CB_BCR_WRITE_POST; + /* some TI1130's seem to exhibit problems with write posting */ + if (((s->type == IS_TI1130) && (s->revision == 4) && + (cb_write_post < 0)) || (cb_write_post == 0)) + s->bcr &= ~CB_BCR_WRITE_POST; + if (s->cache == 0) s->cache = 8; + if (s->pci_lat == 0) s->pci_lat = 0xa8; + if (s->cb_lat == 0) s->cb_lat = 0xb0; + if (s->cap.pci_irq == 0) + strcat(buf, " [no pci irq]"); + else + sprintf(buf, " [pci irq %d]", s->cap.pci_irq); + buf += strlen(buf); + if (!(s->flags & IS_TOPIC)) + s->cap.features |= SS_CAP_PAGE_REGS; + sprintf(buf, " [lat %d/%d] [bus %d/%d]", + s->pci_lat, s->cb_lat, s->cap.cardbus, s->sub_bus); +} + +#endif + +/*====================================================================== + + Power control for Cardbus controllers: used both for 16-bit and + Cardbus cards. + +======================================================================*/ + +#ifdef CONFIG_PCI + +static void cb_get_power(socket_info_t *s, socket_state_t *state) +{ + u_int reg = cb_readl(s, CB_SOCKET_CONTROL); + state->Vcc = state->Vpp = 0; + switch (reg & CB_SC_VCC_MASK) { + case CB_SC_VCC_3V: state->Vcc = 33; break; + case CB_SC_VCC_5V: state->Vcc = 50; break; + } + switch (reg & CB_SC_VPP_MASK) { + case CB_SC_VPP_3V: state->Vpp = 33; break; + case CB_SC_VPP_5V: state->Vpp = 50; break; + case CB_SC_VPP_12V: state->Vpp = 120; break; + } +} + +static int cb_set_power(socket_info_t *s, socket_state_t *state) +{ + u_int reg = 0; + /* restart card voltage detection if it seems appropriate */ + if ((state->Vcc == 0) && (state->Vpp == 0) && + !(cb_readl(s, CB_SOCKET_STATE) & CB_SS_VSENSE)) + cb_writel(s, CB_SOCKET_FORCE, CB_SF_CVSTEST); + switch (state->Vcc) { + case 0: reg = 0; break; + case 33: reg = CB_SC_VCC_3V; break; + case 50: reg = CB_SC_VCC_5V; break; + default: return -EINVAL; + } + switch (state->Vpp) { + case 0: break; + case 33: reg |= CB_SC_VPP_3V; break; + case 50: reg |= CB_SC_VPP_5V; break; + case 120: reg |= CB_SC_VPP_12V; break; + default: return -EINVAL; + } + if (reg != cb_readl(s, CB_SOCKET_CONTROL)) + cb_writel(s, CB_SOCKET_CONTROL, reg); + return 0; +} + +#endif + +/*====================================================================== + + Generic routines to get and set controller options + +======================================================================*/ + +static void __init get_bridge_state(socket_info_t *s) +{ + if (s->flags & IS_CIRRUS) + cirrus_get_state(s); +#ifdef CONFIG_ISA + else if (s->flags & IS_VADEM) + vg46x_get_state(s); +#endif +#ifdef CONFIG_PCI + else if (s->flags & IS_O2MICRO) + o2micro_get_state(s); + else if (s->flags & IS_TI) + ti113x_get_state(s); + else if (s->flags & IS_RICOH) + ricoh_get_state(s); + else if (s->flags & IS_TOPIC) + topic_get_state(s); + if (s->flags & IS_CARDBUS) + cb_get_state(s); +#endif +} + +static void set_bridge_state(socket_info_t *s) +{ +#ifdef CONFIG_PCI + if (s->flags & IS_CARDBUS) + cb_set_state(s); +#endif + if (s->flags & IS_CIRRUS) { + cirrus_set_state(s); + } else { + i365_set(s, I365_GBLCTL, 0x00); + i365_set(s, I365_GENCTL, 0x00); + /* Trouble: changes timing of memory operations */ + /* i365_bset(s, I365_ADDRWIN, I365_ADDR_MEMCS16); */ + } + i365_bflip(s, I365_INTCTL, I365_INTR_ENA, s->intr); +#ifdef CONFIG_ISA + if (s->flags & IS_VADEM) + vg46x_set_state(s); +#endif +#ifdef CONFIG_PCI + if (s->flags & IS_O2MICRO) + o2micro_set_state(s); + else if (s->flags & IS_TI) + ti113x_set_state(s); + else if (s->flags & IS_RICOH) + ricoh_set_state(s); + else if (s->flags & IS_TOPIC) + topic_set_state(s); +#endif +} + +static u_int __init set_bridge_opts(socket_info_t *s, u_short ns) +{ + u_short i; + u_int m = 0xffff; + char buf[128]; + + for (i = 0; i < ns; i++) { + if (s[i].flags & IS_ALIVE) { + printk(KERN_INFO " host opts [%d]: already alive!\n", i); + continue; + } + buf[0] = '\0'; + get_bridge_state(s+i); + if (s[i].flags & IS_CIRRUS) + m = cirrus_set_opts(s+i, buf); +#ifdef CONFIG_ISA + else if (s[i].flags & IS_VADEM) + m = vg46x_set_opts(s+i, buf); +#endif +#ifdef CONFIG_PCI + else if (s[i].flags & IS_O2MICRO) + m = o2micro_set_opts(s+i, buf); + else if (s[i].flags & IS_TI) + m = ti113x_set_opts(s+i, buf); + else if (s[i].flags & IS_RICOH) + m = ricoh_set_opts(s+i, buf); + else if (s[i].flags & IS_TOPIC) + m = topic_set_opts(s+i, buf); + if (s[i].flags & IS_CARDBUS) + cb_set_opts(s+i, buf+strlen(buf)); +#endif + set_bridge_state(s+i); + printk(KERN_INFO " host opts [%d]:%s\n", i, + (*buf) ? buf : " none"); + } +#ifdef CONFIG_PCI + m &= ~pci_irq_mask; +#endif + return m; +} + +/*====================================================================== + + Interrupt testing code, for ISA and PCI interrupts + +======================================================================*/ + +static volatile u_int irq_hits, irq_shared; +static volatile socket_info_t *irq_sock; + +static void irq_count(int irq, void *dev, struct pt_regs *regs) +{ + irq_hits++; + DEBUG(2, "-> hit on irq %d\n", irq); + if (!irq_shared && (irq_hits > 100)) { + printk(KERN_INFO " PCI irq %d seems to be wedged!\n", irq); + disable_irq(irq); + return; + } +#ifdef CONFIG_PCI + if (irq_sock->flags & IS_CARDBUS) { + cb_writel(irq_sock, CB_SOCKET_EVENT, -1); + } else +#endif + i365_get((socket_info_t *)irq_sock, I365_CSC); + return; +} + +static u_int __init test_irq(socket_info_t *s, int irq, int pci) +{ + u_char csc = (pci) ? 0 : irq; + +#ifdef CONFIG_PNP_BIOS + extern int check_pnp_irq(int); + if (!pci && check_pnp_irq(irq)) return 1; +#endif + + DEBUG(2, " testing %s irq %d\n", pci ? "PCI" : "ISA", irq); + irq_sock = s; irq_shared = irq_hits = 0; + if (request_irq(irq, irq_count, 0, "scan", socket)) { + irq_shared++; + if (!pci || request_irq(irq, irq_count, SA_SHIRQ, "scan", socket)) + return 1; + } + irq_hits = 0; + __set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(HZ/100); + if (irq_hits && !irq_shared) { + free_irq(irq, socket); + DEBUG(2, " spurious hit!\n"); + return 1; + } + + /* Generate one interrupt */ +#ifdef CONFIG_PCI + if (s->flags & IS_CARDBUS) { + cb_writel(s, CB_SOCKET_EVENT, -1); + i365_set(s, I365_CSCINT, I365_CSC_STSCHG | (csc << 4)); + cb_writel(s, CB_SOCKET_EVENT, -1); + cb_writel(s, CB_SOCKET_MASK, CB_SM_CSTSCHG); + cb_writel(s, CB_SOCKET_FORCE, CB_SE_CSTSCHG); + mdelay(1); + cb_writel(s, CB_SOCKET_EVENT, -1); + cb_writel(s, CB_SOCKET_MASK, 0); + } else +#endif + { + i365_set(s, I365_CSCINT, I365_CSC_DETECT | (csc << 4)); + i365_bset(s, I365_GENCTL, I365_CTL_SW_IRQ); + mdelay(1); + } + + free_irq(irq, socket); + + /* mask all interrupts */ + i365_set(s, I365_CSCINT, 0); + DEBUG(2, " hits = %d\n", irq_hits); + + return pci ? (irq_hits == 0) : (irq_hits != 1); +} + +#ifdef CONFIG_ISA +static int _check_irq(int irq, int flags) +{ +#ifdef CONFIG_PNP_BIOS + extern int check_pnp_irq(int); + if ((flags != SA_SHIRQ) && check_pnp_irq(irq)) + return -1; +#endif + if (request_irq(irq, irq_count, flags, "x", irq_count) != 0) + return -1; + free_irq(irq, irq_count); + return 0; +} + +static u_int __init isa_scan(socket_info_t *s, u_int mask0) +{ + u_int mask1 = 0; + int i; + +#ifdef CONFIG_PCI + /* Only scan if we can select ISA csc irq's */ + if (!(s->flags & IS_CARDBUS) || (cb_set_irq_mode(s, 0, 0) == 0)) +#endif + if (do_scan) { + set_bridge_state(s); + i365_set(s, I365_CSCINT, 0); + for (i = 0; i < 16; i++) + if ((mask0 & (1 << i)) && (test_irq(s, i, 0) == 0)) + mask1 |= (1 << i); + for (i = 0; i < 16; i++) + if ((mask1 & (1 << i)) && (test_irq(s, i, 0) != 0)) + mask1 ^= (1 << i); + } + + printk(KERN_INFO " ISA irqs ("); + /* we trust TI bridges to do this right */ + if (mask1 || (s->flags & IS_TI)) { + printk("scanned"); + } else { + /* Fallback: just find interrupts that aren't in use */ + for (i = 0; i < 16; i++) + if ((mask0 & (1 << i)) && (_check_irq(i, 0) == 0)) + mask1 |= (1 << i); + printk("default"); + /* If scan failed, default to polled status */ + if (!cs_irq && (poll_interval == 0)) poll_interval = HZ; + } + printk(") = "); + + for (i = 0; i < 16; i++) + if (mask1 & (1<<i)) + printk("%s%d", ((mask1 & ((1<<i)-1)) ? "," : ""), i); + if (mask1 == 0) printk("none!"); + + return mask1; +} +#endif /* CONFIG_ISA */ + +#ifdef CONFIG_PCI +static int __init pci_scan(socket_info_t *s) +{ + int ret; + if ((s->flags & IS_RICOH) || !(s->flags & IS_CARDBUS) || !do_scan) { + /* for PCI-to-PCMCIA bridges, just check for wedged irq */ + irq_sock = s; irq_hits = 0; + if (request_irq(s->cap.pci_irq, irq_count, 0, "scan", socket)) + return 1; + udelay(50); + free_irq(s->cap.pci_irq, socket); + return (!irq_hits); + } + cb_set_irq_mode(s, 1, 0); + set_bridge_state(s); + i365_set(s, I365_CSCINT, 0); + ret = ((test_irq(s, s->cap.pci_irq, 1) == 0) && + (test_irq(s, s->cap.pci_irq, 1) == 0)); + if (!ret) + printk(KERN_INFO " PCI irq %d test failed\n", + s->cap.pci_irq); + return ret; +} +#endif /* CONFIG_PCI */ + +/*====================================================================*/ + +#ifdef CONFIG_ISA + +static int __init isa_identify(u_short port, u_short sock) +{ + socket_info_t *s = socket+sockets; + u_char val; + int type = -1; + + /* Use the next free entry in the socket table */ + s->ioaddr = port; + s->psock = sock; + + /* Wake up a sleepy Cirrus controller */ + if (wakeup) { + i365_bclr(s, PD67_MISC_CTL_2, PD67_MC2_SUSPEND); + /* Pause at least 50 ms */ + mdelay(50); + } + + if ((val = i365_get(s, I365_IDENT)) & 0x70) + return -1; + switch (val) { + case 0x82: + type = IS_I82365A; break; + case 0x83: + type = IS_I82365B; break; + case 0x84: + type = IS_I82365DF; break; + case 0x88: case 0x89: case 0x8a: + type = IS_IBM; break; + } + + /* Check for Vadem VG-468 chips */ + outb(0x0e, port); + outb(0x37, port); + i365_bset(s, VG468_MISC, VG468_MISC_VADEMREV); + val = i365_get(s, I365_IDENT); + if (val & I365_IDENT_VADEM) { + i365_bclr(s, VG468_MISC, VG468_MISC_VADEMREV); + type = ((val & 7) >= 4) ? IS_VG469 : IS_VG468; + } + + /* Check for Ricoh chips */ + val = i365_get(s, RF5C_CHIP_ID); + if ((val == RF5C_CHIP_RF5C296) || (val == RF5C_CHIP_RF5C396)) + type = IS_RF5Cx96; + + /* Check for Cirrus CL-PD67xx chips */ + i365_set(s, PD67_CHIP_INFO, 0); + val = i365_get(s, PD67_CHIP_INFO); + if ((val & PD67_INFO_CHIP_ID) == PD67_INFO_CHIP_ID) { + val = i365_get(s, PD67_CHIP_INFO); + if ((val & PD67_INFO_CHIP_ID) == 0) { + type = (val & PD67_INFO_SLOTS) ? IS_PD672X : IS_PD6710; + i365_set(s, PD67_EXT_INDEX, 0xe5); + if (i365_get(s, PD67_EXT_INDEX) != 0xe5) + type = IS_VT83C469; + } + } + return type; +} /* isa_identify */ + +#endif + +/*====================================================================== + + See if a card is present, powered up, in IO mode, and already + bound to a (non PC Card) Linux driver. We leave these alone. + + We make an exception for cards that seem to be serial devices. + +======================================================================*/ + +static int __init is_alive(socket_info_t *s) +{ + u_char stat; + u_short start, stop; + + stat = i365_get(s, I365_STATUS); + start = i365_get_pair(s, I365_IO(0)+I365_W_START); + stop = i365_get_pair(s, I365_IO(0)+I365_W_STOP); + if ((stop - start < 0x40) && (stop - start >= 0x07) && + ((start & 0xfeef) != 0x02e8) && (start >= 0x100) && + (stat & I365_CS_DETECT) && (stat & I365_CS_POWERON) && + (i365_get(s, I365_INTCTL) & I365_PC_IOCARD) && + (i365_get(s, I365_ADDRWIN) & I365_ENA_IO(0)) && + (check_region(start, stop-start+1) != 0)) + return 1; + else + return 0; +} + +/*====================================================================*/ + +static void __init add_socket(u_int port, int psock, int type) +{ + socket_info_t *s = socket+sockets; + s->ioaddr = port; + s->psock = psock; + s->type = type; + s->flags = pcic[type].flags; + if (is_alive(s)) + s->flags |= IS_ALIVE; + sockets++; +} + +static void __init add_pcic(int ns, int type) +{ + u_int mask = 0, i; + int use_pci = 0, isa_irq = 0; + socket_info_t *s = &socket[sockets-ns]; + + if (s->ioaddr > 0) request_region(s->ioaddr, 2, "i82365"); + + printk(KERN_INFO " %s", pcic[type].name); +#ifdef CONFIG_PCI + if (s->flags & IS_UNKNOWN) + printk(" [%04x %04x]", s->vendor, s->device); + printk(" rev %02x", s->revision); + if (s->flags & IS_CARDBUS) + printk(" PCI-to-CardBus at slot %02x:%02x, mem %#08x\n", + s->bus, PCI_SLOT(s->devfn), s->cb_phys); + else if (s->flags & IS_PCI) + printk(" PCI-to-PCMCIA at slot %02x:%02x, port %#x\n", + s->bus, PCI_SLOT(s->devfn), s->ioaddr); + else +#endif + printk(" ISA-to-PCMCIA at port %#x ofs 0x%02x\n", + s->ioaddr, s->psock*0x40); + +#ifdef CONFIG_ISA + if (irq_list[0] == -1) + mask = irq_mask; + else + for (i = mask = 0; i < 16; i++) + mask |= (1<<irq_list[i]); +#endif + /* Set host options, build basic interrupt mask */ + mask &= I365_ISA_IRQ_MASK & set_bridge_opts(s, ns); + +#ifdef CONFIG_PCI + /* Can we use PCI interrupts for card status changes? */ + if (pci_csc || pci_int) { + for (i = 0; i < ns; i++) + if (!s[i].cap.pci_irq || !pci_scan(&s[i])) break; + use_pci = (i == ns); + } +#endif +#ifdef CONFIG_ISA + /* Scan, report ISA card interrupts */ + if (mask) + mask = isa_scan(s, mask); +#endif + +#ifdef CONFIG_PCI + if (!mask) + printk(KERN_INFO " %s card interrupts,", + (use_pci && pci_int) ? "PCI" : "*NO*"); + if (use_pci && pci_csc) + printk(" PCI status changes\n"); +#endif + +#ifdef CONFIG_ISA + /* Poll if only two sensible interrupts available */ + if (!(use_pci && pci_csc) && !poll_interval) { + u_int tmp = (mask & 0xff20); + tmp = tmp & (tmp-1); + if ((tmp & (tmp-1)) == 0) + poll_interval = HZ; + } + /* Only try an ISA cs_irq if this is the first controller */ + if (!(use_pci && pci_csc) && !grab_irq && + (cs_irq || !poll_interval)) { + /* Avoid irq 12 unless it is explicitly requested */ + u_int cs_mask = mask & ((cs_irq) ? (1<<cs_irq) : ~(1<<12)); + for (isa_irq = 15; isa_irq > 0; isa_irq--) + if (cs_mask & (1 << isa_irq)) break; + if (isa_irq) { + grab_irq = 1; + cs_irq = isa_irq; + printk(" status change on irq %d\n", isa_irq); + } + } +#endif + + if (!(use_pci && pci_csc) && !isa_irq) { + if (poll_interval == 0) + poll_interval = HZ; + printk(" polling interval = %d ms\n", poll_interval*1000/HZ); + } + + /* Update socket interrupt information, capabilities */ + for (i = 0; i < ns; i++) { + s[i].cap.features |= SS_CAP_PCCARD; + s[i].cap.map_size = 0x1000; + s[i].cap.irq_mask = mask; + if (!use_pci) + s[i].cap.pci_irq = 0; + s[i].cs_irq = isa_irq; +#ifdef CONFIG_PCI + if (s[i].flags & IS_CARDBUS) { + s[i].cap.features |= SS_CAP_CARDBUS; + cb_set_irq_mode(s+i, pci_csc && s[i].cap.pci_irq, 0); + } +#endif + } + +} /* add_pcic */ + +/*====================================================================*/ + +#ifdef CONFIG_PCI + +static int __init pci_lookup(u_int class, struct pci_dev **id, + u_char *bus, u_char *devfn) +{ + if ((*id = pci_find_class(class<<8, *id)) != NULL) { + *bus = (*id)->bus->number; + *devfn = (*id)->devfn; + return 0; + } else return -1; +} + +static void __init add_pci_bridge(int type, u_short v, u_short d) +{ + socket_info_t *s = &socket[sockets]; + u_int addr, ns; + + pci_enable_device(pci_find_slot(s->bus, s->devfn)); + pci_writew(s, PCI_COMMAND, CMD_DFLT); + + if (type == PCIC_COUNT) type = IS_UNK_PCI; + pci_readl(s, PCI_BASE_ADDRESS_0, &addr); + addr &= ~0x1; + for (ns = 0; ns < ((type == IS_I82092AA) ? 4 : 2); ns++) { + s[ns].bus = s->bus; s[ns].devfn = s->devfn; + s[ns].vendor = v; s[ns].device = d; + add_socket(addr, ns, type); + } + add_pcic(ns, type); +} + +static int check_cb_mapping(socket_info_t *s) +{ + u_int state = cb_readl(s, CB_SOCKET_STATE) >> 16; + /* A few sanity checks to validate the bridge mapping */ + if ((cb_readb(s, 0x800+I365_IDENT) & 0x70) || + (cb_readb(s, 0x800+I365_CSC) && cb_readb(s, 0x800+I365_CSC) && + cb_readb(s, 0x800+I365_CSC)) || cb_readl(s, CB_SOCKET_FORCE) || + ((state & ~0x3000) || !(state & 0x3000))) + return 1; + return 0; +} + +static void __init add_cb_bridge(int type, u_short v, u_short d0) +{ + socket_info_t *s = &socket[sockets]; + u_char bus = s->bus, devfn = s->devfn; + u_short d, ns; + u_char a, r, max; + + /* PCI bus enumeration is broken on some systems */ + for (ns = 0; ns < sockets; ns++) + if ((socket[ns].bus == bus) && + (socket[ns].devfn == devfn)) + return; + + if (type == PCIC_COUNT) type = IS_UNK_CARDBUS; + pci_readb(s, PCI_HEADER_TYPE, &a); + pci_readb(s, PCI_CLASS_REVISION, &r); + max = (a & 0x80) ? 8 : 1; + for (ns = 0; ns < max; ns++, s++, devfn++) { + s->bus = bus; s->devfn = devfn; + if (pci_readw(s, PCI_DEVICE_ID, &d) || (d != d0)) + break; + s->vendor = v; s->device = d; s->revision = r; + + pci_enable_device(pci_find_slot(bus, devfn)); + pci_set_power_state(pci_find_slot(bus, devfn), 0); + pci_writew(s, PCI_COMMAND, CMD_DFLT); + + /* Set up CardBus register mapping */ + pci_writel(s, CB_LEGACY_MODE_BASE, 0); + pci_readl(s, PCI_BASE_ADDRESS_0, &s->cb_phys); + if (s->cb_phys == 0) { + printk("\n" KERN_NOTICE " Bridge register mapping failed:" + " check cb_mem_base setting\n"); + break; + } + s->cb_virt = ioremap(s->cb_phys, 0x1000); + if (check_cb_mapping(s) != 0) { + printk("\n" KERN_NOTICE " Bad bridge mapping at " + "0x%08x!\n", s->cb_phys); + break; + } + + request_mem_region(s->cb_phys, 0x1000, "i82365"); + add_socket(0, 0, type); + } + if (ns == 0) return; + + add_pcic(ns, type); + + /* Look up PCI bus bridge structures if needed */ + s -= ns; + for (a = 0; a < ns; a++) { + struct pci_dev *self = pci_find_slot(bus, s[a].devfn); +#if (LINUX_VERSION_CODE >= VERSION(2,3,40)) + s[a].cap.cb_bus = self->subordinate; +#else + struct pci_bus *child; + for (child = self->bus->children; child; child = child->next) + if (child->number == s[a].cap.cardbus) break; + s[a].cap.cb_bus = child; +#endif + } +} + +static void __init pci_probe(u_int class) +{ + socket_info_t *s = &socket[sockets]; + u_short i, v, d; + struct pci_dev *id; + + id = 0; + while (pci_lookup(class, &id, &s->bus, &s->devfn) == 0) { + if (PCI_FUNC(s->devfn) != 0) continue; + pci_readw(s, PCI_VENDOR_ID, &v); + pci_readw(s, PCI_DEVICE_ID, &d); + for (i = 0; i < PCIC_COUNT; i++) + if ((pcic[i].vendor == v) && (pcic[i].device == d)) break; + /* The "ToPIC95-A" is unusable as a CardBus bridge */ + if (i == IS_TOPIC95_A) + continue; + if (((i < PCIC_COUNT) && (pcic[i].flags & IS_CARDBUS)) || + (class == PCI_CLASS_BRIDGE_CARDBUS)) + add_cb_bridge(i, v, d); + else + add_pci_bridge(i, v, d); + s = &socket[sockets]; + } +} + +#endif + +/*====================================================================*/ + +#ifdef CONFIG_ISA + +static void __init isa_probe(ioaddr_t base) +{ + int i, j, sock, k, ns, id; + ioaddr_t port; + + if (check_region(base, 2) != 0) { + if (sockets == 0) + printk("port conflict at %#x\n", base); + return; + } + + id = isa_identify(base, 0); + if ((id == IS_I82365DF) && (isa_identify(base, 1) != id)) { + for (i = 0; i < 4; i++) { + if (i == ignore) continue; + port = base + ((i & 1) << 2) + ((i & 2) << 1); + sock = (i & 1) << 1; + if (isa_identify(port, sock) == IS_I82365DF) { + add_socket(port, sock, IS_VLSI); + add_pcic(1, IS_VLSI); + } + } + } else { + for (i = 0; i < 4; i += 2) { + port = base + 2*(i>>2); + sock = (i & 3); + id = isa_identify(port, sock); + if (id < 0) continue; + + for (j = ns = 0; j < 2; j++) { + /* Does the socket exist? */ + if ((ignore == i+j) || (isa_identify(port, sock+j) < 0)) + continue; + /* Check for bad socket decode */ + for (k = 0; k <= sockets; k++) + i365_set(socket+k, I365_MEM(0)+I365_W_OFF, k); + for (k = 0; k <= sockets; k++) + if (i365_get(socket+k, I365_MEM(0)+I365_W_OFF) != k) + break; + if (k <= sockets) break; + add_socket(port, sock+j, id); ns++; + } + if (ns != 0) add_pcic(ns, id); + } + } +} + +#endif + +/*====================================================================== + + The card status event handler. This may either be interrupt + driven or polled. It monitors mainly for card insert and eject + events; there are various other kinds of events that can be + monitored (ready/busy, status change, etc), but they are almost + never used. + +======================================================================*/ + +static void pcic_interrupt(int irq, void *dev, struct pt_regs *regs) +{ + int i, j, csc; + u_int events, active; +#ifdef CONFIG_ISA + u_long flags = 0; +#endif + + DEBUG(2, "i82365: pcic_interrupt(%d)\n", irq); + + for (j = 0; j < 20; j++) { + active = 0; + for (i = 0; i < sockets; i++) { + socket_info_t *s = &socket[i]; + if ((s->cs_irq != irq) && (s->cap.pci_irq != irq)) + continue; + ISA_LOCK(s, flags); + csc = i365_get(s, I365_CSC); +#ifdef CONFIG_PCI + if ((s->flags & IS_CARDBUS) && + (cb_readl(s, CB_SOCKET_EVENT) & CB_SE_CCD)) { + cb_writel(s, CB_SOCKET_EVENT, CB_SE_CCD); + csc |= I365_CSC_DETECT; + } +#endif + if ((csc == 0) || (!s->handler) || + (i365_get(s, I365_IDENT) & 0x70)) { + ISA_UNLOCK(s, flags); + continue; + } + events = (csc & I365_CSC_DETECT) ? SS_DETECT : 0; + if (i365_get(s, I365_INTCTL) & I365_PC_IOCARD) { + events |= (csc & I365_CSC_STSCHG) ? SS_STSCHG : 0; + } else { + events |= (csc & I365_CSC_BVD1) ? SS_BATDEAD : 0; + events |= (csc & I365_CSC_BVD2) ? SS_BATWARN : 0; + events |= (csc & I365_CSC_READY) ? SS_READY : 0; + } + ISA_UNLOCK(s, flags); + DEBUG(1, "i82365: socket %d event 0x%04x\n", i, events); + if (events) + s->handler(s->info, events); + active |= events; + } + if (!active) break; + } + if (j == 20) + printk(KERN_NOTICE "i82365: infinite loop in interrupt " + "handler: active = 0x%04x\n", active); + + DEBUG(2, "i82365: interrupt done\n"); +} /* pcic_interrupt */ + +static void pcic_interrupt_wrapper(u_long data) +{ + pcic_interrupt(0, NULL, NULL); + poll_timer.expires = jiffies + poll_interval; + add_timer(&poll_timer); +} + +/*====================================================================*/ + +static int pcic_register_callback(socket_info_t *s, ss_callback_t *call) +{ + if (call == NULL) { + s->handler = NULL; + MOD_DEC_USE_COUNT; + } else { + MOD_INC_USE_COUNT; + s->handler = call->handler; + s->info = call->info; + } + return 0; +} /* pcic_register_callback */ + +/*====================================================================*/ + +static int pcic_inquire_socket(socket_info_t *s, socket_cap_t *cap) +{ + *cap = s->cap; + return 0; +} + +/*====================================================================*/ + +static int i365_get_status(socket_info_t *s, u_int *value) +{ + u_int status; + + status = i365_get(s, I365_STATUS); + *value = ((status & I365_CS_DETECT) == I365_CS_DETECT) + ? SS_DETECT : 0; + if (i365_get(s, I365_INTCTL) & I365_PC_IOCARD) { + *value |= (status & I365_CS_STSCHG) ? 0 : SS_STSCHG; + } else { + *value |= (status & I365_CS_BVD1) ? 0 : SS_BATDEAD; + *value |= (status & I365_CS_BVD2) ? 0 : SS_BATWARN; + } + *value |= (status & I365_CS_WRPROT) ? SS_WRPROT : 0; + *value |= (status & I365_CS_READY) ? SS_READY : 0; + *value |= (status & I365_CS_POWERON) ? SS_POWERON : 0; + +#ifdef CONFIG_PCI + if (s->flags & IS_CARDBUS) { + status = cb_readl(s, CB_SOCKET_STATE); + *value |= (status & CB_SS_32BIT) ? SS_CARDBUS : 0; + *value |= (status & CB_SS_3VCARD) ? SS_3VCARD : 0; + *value |= (status & CB_SS_XVCARD) ? SS_XVCARD : 0; + *value |= (status & CB_SS_VSENSE) ? 0 : SS_PENDING; + } else if (s->flags & IS_O2MICRO) { + status = i365_get(s, O2_MODE_B); + *value |= (status & O2_MODE_B_VS1) ? 0 : SS_3VCARD; + *value |= (status & O2_MODE_B_VS2) ? 0 : SS_XVCARD; + } +#endif + if ((s->flags & IS_CIRRUS) && + ((s->flags & IS_PCI) || has_vsense)) { + socket_info_t *t = (s->psock) ? s : s+1; + status = pd67_ext_get(t, PD67_EXTERN_DATA); + *value |= (status & PD67_EXD_VS1(s->psock)) ? 0 : SS_3VCARD; + *value |= (status & PD67_EXD_VS2(s->psock)) ? 0 : SS_XVCARD; + } +#ifdef CONFIG_ISA + if (s->type == IS_VG469) { + status = i365_get(s, VG469_VSENSE); + if (s->psock & 1) { + *value |= (status & VG469_VSENSE_B_VS1) ? 0 : SS_3VCARD; + *value |= (status & VG469_VSENSE_B_VS2) ? 0 : SS_XVCARD; + } else { + *value |= (status & VG469_VSENSE_A_VS1) ? 0 : SS_3VCARD; + *value |= (status & VG469_VSENSE_A_VS2) ? 0 : SS_XVCARD; + } + } +#endif + /* For now, ignore cards with unsupported voltage keys */ + if (*value & SS_XVCARD) + *value &= ~(SS_DETECT|SS_3VCARD|SS_XVCARD); + DEBUG(1, "i82365: GetStatus(%d) = %#4.4x\n", s-socket, *value); + return 0; +} /* i365_get_status */ + +/*====================================================================*/ + +static int i365_get_socket(socket_info_t *s, socket_state_t *state) +{ + u_char reg, vcc, vpp; + + reg = i365_get(s, I365_POWER); + state->flags = (reg & I365_PWR_AUTO) ? SS_PWR_AUTO : 0; + state->flags |= (reg & I365_PWR_OUT) ? SS_OUTPUT_ENA : 0; + vcc = reg & I365_VCC_MASK; vpp = reg & I365_VPP1_MASK; + state->Vcc = state->Vpp = 0; +#ifdef CONFIG_PCI + if ((s->flags & IS_CARDBUS) && !(s->flags & IS_TOPIC)) { + cb_get_power(s, state); + } else +#endif + { + if ((s->flags & IS_CIRRUS) && (reg & I365_VCC_5V)) { + state->Vcc = (i365_get(s, PD67_MISC_CTL_1) & + PD67_MC1_VCC_3V) ? 33 : 50; + } else if ((s->flags & IS_VG_PWR) && (reg & I365_VCC_5V)) { + state->Vcc = (i365_get(s, VG469_VSELECT) & + VG469_VSEL_VCC) ? 33 : 50; + } else if ((s->flags & IS_DF_PWR) || (s->flags & IS_TOPIC)) { + if (vcc == I365_VCC_3V) state->Vcc = 33; + if (vcc == I365_VCC_5V) state->Vcc = 50; + } else { + if (reg & I365_VCC_5V) state->Vcc = 50; + } + if (vpp == I365_VPP1_5V) + state->Vpp = (s->flags & IS_DF_PWR) ? 50 : state->Vcc; + if (vpp == I365_VPP1_12V) state->Vpp = 120; + } + + /* IO card, RESET flags, IO interrupt */ + reg = i365_get(s, I365_INTCTL); + state->flags |= (reg & I365_PC_RESET) ? 0 : SS_RESET; + state->flags |= (reg & I365_PC_IOCARD) ? SS_IOCARD : 0; +#ifdef CONFIG_PCI + if (cb_get_irq_mode(s) != 0) + state->io_irq = s->cap.pci_irq; + else +#endif + state->io_irq = reg & I365_IRQ_MASK; + + /* Card status change mask */ + reg = i365_get(s, I365_CSCINT); + state->csc_mask = (reg & I365_CSC_DETECT) ? SS_DETECT : 0; + if (state->flags & SS_IOCARD) { + state->csc_mask |= (reg & I365_CSC_STSCHG) ? SS_STSCHG : 0; + } else { + state->csc_mask |= (reg & I365_CSC_BVD1) ? SS_BATDEAD : 0; + state->csc_mask |= (reg & I365_CSC_BVD2) ? SS_BATWARN : 0; + state->csc_mask |= (reg & I365_CSC_READY) ? SS_READY : 0; + } + + DEBUG(2, "i82365: GetSocket(%d) = flags %#3.3x, Vcc %d, Vpp %d, " + "io_irq %d, csc_mask %#2.2x\n", s-socket, state->flags, + state->Vcc, state->Vpp, state->io_irq, state->csc_mask); + return 0; +} /* i365_get_socket */ + +/*====================================================================*/ + +static int i365_set_socket(socket_info_t *s, socket_state_t *state) +{ + u_char reg; + + DEBUG(2, "i82365: SetSocket(%d, flags %#3.3x, Vcc %d, Vpp %d, " + "io_irq %d, csc_mask %#2.2x)\n", s-socket, state->flags, + state->Vcc, state->Vpp, state->io_irq, state->csc_mask); + + /* First set global controller options */ +#ifdef CONFIG_PCI + if (s->cap.pci_irq) + cb_set_irq_mode(s, pci_csc, (s->cap.pci_irq == state->io_irq)); + s->bcr &= ~CB_BCR_CB_RESET; +#endif + set_bridge_state(s); + + /* IO card, RESET flag, IO interrupt */ + reg = s->intr | ((state->io_irq == s->cap.pci_irq) ? + s->pci_irq_code : state->io_irq); + reg |= (state->flags & SS_RESET) ? 0 : I365_PC_RESET; + reg |= (state->flags & SS_IOCARD) ? I365_PC_IOCARD : 0; + i365_set(s, I365_INTCTL, reg); + + reg = I365_PWR_NORESET; + if (state->flags & SS_PWR_AUTO) reg |= I365_PWR_AUTO; + if (state->flags & SS_OUTPUT_ENA) reg |= I365_PWR_OUT; + +#ifdef CONFIG_PCI + if ((s->flags & IS_CARDBUS) && !(s->flags & IS_TOPIC)) { + cb_set_power(s, state); + reg |= i365_get(s, I365_POWER) & (I365_VCC_MASK|I365_VPP1_MASK); + } else +#endif + { + int new = s->flags & (IS_TOPIC|IS_CIRRUS|IS_VG_PWR|IS_DF_PWR); + int vcc3 = (state->Vcc == 33), df = (s->flags & IS_DF_PWR); + + if (state->Vcc == 50) { + reg |= I365_VCC_5V; + } else if (new && vcc3) { + reg |= ((s->flags & (IS_TOPIC|IS_DF_PWR)) ? + I365_VCC_3V : I365_VCC_5V); + } else if (state->Vcc) + return -EINVAL; + if (s->flags & IS_CIRRUS) + i365_bflip(s, PD67_MISC_CTL_1, PD67_MC1_VCC_3V, vcc3); + if (s->flags & IS_VG_PWR) + i365_bflip(s, VG469_VSELECT, VG469_VSEL_VCC, vcc3); + + if (state->Vpp == 120) { + reg |= I365_VPP1_12V | (new ? 0 : I365_VPP2_12V); + } else if (state->Vpp == (df ? 50 : state->Vcc)) { + reg |= I365_VPP1_5V | (new ? 0 : I365_VPP2_5V); + } else if (state->Vpp) + return -EINVAL; + } + + if (reg != i365_get(s, I365_POWER)) + i365_set(s, I365_POWER, reg); + + /* Card status change interrupt mask */ + reg = (s->cap.pci_irq ? s->pci_irq_code : s->cs_irq) << 4; + if (state->csc_mask & SS_DETECT) reg |= I365_CSC_DETECT; + if (state->flags & SS_IOCARD) { + if (state->csc_mask & SS_STSCHG) reg |= I365_CSC_STSCHG; + } else { + if (state->csc_mask & SS_BATDEAD) reg |= I365_CSC_BVD1; + if (state->csc_mask & SS_BATWARN) reg |= I365_CSC_BVD2; + if (state->csc_mask & SS_READY) reg |= I365_CSC_READY; + } + i365_set(s, I365_CSCINT, reg); + i365_get(s, I365_CSC); +#ifdef CONFIG_PCI + if (s->flags & IS_CARDBUS) { + if (s->cs_irq || (pci_csc && s->cap.pci_irq)) + cb_writel(s, CB_SOCKET_MASK, CB_SM_CCD); + cb_writel(s, CB_SOCKET_EVENT, -1); + } +#endif + + return 0; +} /* i365_set_socket */ + +/*====================================================================*/ + +static int i365_get_io_map(socket_info_t *s, struct pccard_io_map *io) +{ + u_char map, ioctl, addr; + + map = io->map; + if (map > 1) return -EINVAL; + io->start = i365_get_pair(s, I365_IO(map)+I365_W_START); + io->stop = i365_get_pair(s, I365_IO(map)+I365_W_STOP); + ioctl = i365_get(s, I365_IOCTL); + addr = i365_get(s, I365_ADDRWIN); + io->speed = (ioctl & I365_IOCTL_WAIT(map)) ? cycle_time : 0; + io->flags = (addr & I365_ENA_IO(map)) ? MAP_ACTIVE : 0; + io->flags |= (ioctl & I365_IOCTL_0WS(map)) ? MAP_0WS : 0; + io->flags |= (ioctl & I365_IOCTL_16BIT(map)) ? MAP_16BIT : 0; + io->flags |= (ioctl & I365_IOCTL_IOCS16(map)) ? MAP_AUTOSZ : 0; + DEBUG(3, "i82365: GetIOMap(%d, %d) = %#2.2x, %d ns, %#4.4x-%#4.4x\n", + s-socket, map, io->flags, io->speed, io->start, io->stop); + return 0; +} /* i365_get_io_map */ + +/*====================================================================*/ + +static int i365_set_io_map(socket_info_t *s, struct pccard_io_map *io) +{ + u_char map, ioctl; + + DEBUG(3, "i82365: SetIOMap(%d, %d, %#2.2x, %d ns, %#4.4x-%#4.4x)\n", + s-socket, io->map, io->flags, io->speed, io->start, io->stop); + map = io->map; + if ((map > 1) || (io->start > 0xffff) || (io->stop > 0xffff) || + (io->stop < io->start)) return -EINVAL; + /* Turn off the window before changing anything */ + if (i365_get(s, I365_ADDRWIN) & I365_ENA_IO(map)) + i365_bclr(s, I365_ADDRWIN, I365_ENA_IO(map)); + i365_set_pair(s, I365_IO(map)+I365_W_START, io->start); + i365_set_pair(s, I365_IO(map)+I365_W_STOP, io->stop); + ioctl = i365_get(s, I365_IOCTL) & ~I365_IOCTL_MASK(map); + if (io->speed) ioctl |= I365_IOCTL_WAIT(map); + if (io->flags & MAP_0WS) ioctl |= I365_IOCTL_0WS(map); + if (io->flags & MAP_16BIT) ioctl |= I365_IOCTL_16BIT(map); + if (io->flags & MAP_AUTOSZ) ioctl |= I365_IOCTL_IOCS16(map); + i365_set(s, I365_IOCTL, ioctl); + /* Turn on the window if necessary */ + if (io->flags & MAP_ACTIVE) + i365_bset(s, I365_ADDRWIN, I365_ENA_IO(map)); + return 0; +} /* i365_set_io_map */ + +/*====================================================================*/ + +static int i365_get_mem_map(socket_info_t *s, struct pccard_mem_map *mem) +{ + u_short base, i; + u_char map, addr; + + map = mem->map; + if (map > 4) return -EINVAL; + addr = i365_get(s, I365_ADDRWIN); + mem->flags = (addr & I365_ENA_MEM(map)) ? MAP_ACTIVE : 0; + base = I365_MEM(map); + + i = i365_get_pair(s, base+I365_W_START); + mem->flags |= (i & I365_MEM_16BIT) ? MAP_16BIT : 0; + mem->flags |= (i & I365_MEM_0WS) ? MAP_0WS : 0; + mem->sys_start = ((u_long)(i & 0x0fff) << 12); + + i = i365_get_pair(s, base+I365_W_STOP); + mem->speed = (i & I365_MEM_WS0) ? 1 : 0; + mem->speed += (i & I365_MEM_WS1) ? 2 : 0; + mem->speed *= cycle_time; + mem->sys_stop = ((u_long)(i & 0x0fff) << 12) + 0x0fff; + + i = i365_get_pair(s, base+I365_W_OFF); + mem->flags |= (i & I365_MEM_WRPROT) ? MAP_WRPROT : 0; + mem->flags |= (i & I365_MEM_REG) ? MAP_ATTRIB : 0; + mem->card_start = ((u_int)(i & 0x3fff) << 12) + mem->sys_start; + mem->card_start &= 0x3ffffff; + +#ifdef CONFIG_PCI + /* Take care of high byte, for PCI controllers */ + if (s->type == IS_PD6729) { + addr = pd67_ext_get(s, PD67_MEM_PAGE(map)) << 24; + mem->sys_stop += addr; mem->sys_start += addr; + } else if (s->flags & IS_CARDBUS) { + addr = i365_get(s, CB_MEM_PAGE(map)) << 24; + mem->sys_stop += addr; mem->sys_start += addr; + } +#endif + + DEBUG(3, "i82365: GetMemMap(%d, %d) = %#2.2x, %d ns, %#5.5lx-%#5." + "5lx, %#5.5x\n", s-socket, mem->map, mem->flags, mem->speed, + mem->sys_start, mem->sys_stop, mem->card_start); + return 0; +} /* i365_get_mem_map */ + +/*====================================================================*/ + +static int i365_set_mem_map(socket_info_t *s, struct pccard_mem_map *mem) +{ + u_short base, i; + u_char map; + + DEBUG(3, "i82365: SetMemMap(%d, %d, %#2.2x, %d ns, %#5.5lx-%#5.5" + "lx, %#5.5x)\n", s-socket, mem->map, mem->flags, mem->speed, + mem->sys_start, mem->sys_stop, mem->card_start); + + map = mem->map; + if ((map > 4) || (mem->card_start > 0x3ffffff) || + (mem->sys_start > mem->sys_stop) || (mem->speed > 1000)) + return -EINVAL; + if (!(s->flags & (IS_PCI|IS_CARDBUS)) && + ((mem->sys_start > 0xffffff) || (mem->sys_stop > 0xffffff))) + return -EINVAL; + + /* Turn off the window before changing anything */ + if (i365_get(s, I365_ADDRWIN) & I365_ENA_MEM(map)) + i365_bclr(s, I365_ADDRWIN, I365_ENA_MEM(map)); + +#ifdef CONFIG_PCI + /* Take care of high byte, for PCI controllers */ + if (s->type == IS_PD6729) { + pd67_ext_set(s, PD67_MEM_PAGE(map), (mem->sys_start >> 24)); + } else if (s->flags & IS_CARDBUS) + i365_set(s, CB_MEM_PAGE(map), mem->sys_start >> 24); +#endif + + base = I365_MEM(map); + i = (mem->sys_start >> 12) & 0x0fff; + if (mem->flags & MAP_16BIT) i |= I365_MEM_16BIT; + if (mem->flags & MAP_0WS) i |= I365_MEM_0WS; + i365_set_pair(s, base+I365_W_START, i); + + i = (mem->sys_stop >> 12) & 0x0fff; + switch (mem->speed / cycle_time) { + case 0: break; + case 1: i |= I365_MEM_WS0; break; + case 2: i |= I365_MEM_WS1; break; + default: i |= I365_MEM_WS1 | I365_MEM_WS0; break; + } + i365_set_pair(s, base+I365_W_STOP, i); + + i = ((mem->card_start - mem->sys_start) >> 12) & 0x3fff; + if (mem->flags & MAP_WRPROT) i |= I365_MEM_WRPROT; + if (mem->flags & MAP_ATTRIB) i |= I365_MEM_REG; + i365_set_pair(s, base+I365_W_OFF, i); + + /* Turn on the window if necessary */ + if (mem->flags & MAP_ACTIVE) + i365_bset(s, I365_ADDRWIN, I365_ENA_MEM(map)); + return 0; +} /* i365_set_mem_map */ + +/*====================================================================== + + The few things that are strictly for Cardbus cards goes here. + +======================================================================*/ + +#ifdef CONFIG_CARDBUS + +static int cb_get_status(socket_info_t *s, u_int *value) +{ + u_int state = cb_readl(s, CB_SOCKET_STATE); + *value = (state & CB_SS_32BIT) ? SS_CARDBUS : 0; + *value |= (state & CB_SS_CCD) ? 0 : SS_DETECT; + *value |= (state & CB_SS_CSTSCHG) ? SS_STSCHG : 0; + *value |= (state & CB_SS_PWRCYCLE) ? (SS_POWERON|SS_READY) : 0; + *value |= (state & CB_SS_3VCARD) ? SS_3VCARD : 0; + *value |= (state & CB_SS_XVCARD) ? SS_XVCARD : 0; + *value |= (state & CB_SS_VSENSE) ? 0 : SS_PENDING; + DEBUG(1, "yenta: GetStatus(%d) = %#4.4x\n", s-socket, *value); + return 0; +} /* cb_get_status */ + +static int cb_get_socket(socket_info_t *s, socket_state_t *state) +{ + u_short bcr; + + cb_get_power(s, state); + pci_readw(s, CB_BRIDGE_CONTROL, &bcr); + state->flags |= (bcr & CB_BCR_CB_RESET) ? SS_RESET : 0; + if (cb_get_irq_mode(s) != 0) + state->io_irq = s->cap.pci_irq; + else + state->io_irq = i365_get(s, I365_INTCTL) & I365_IRQ_MASK; + DEBUG(2, "yenta: GetSocket(%d) = flags %#3.3x, Vcc %d, Vpp %d" + ", io_irq %d, csc_mask %#2.2x\n", s-socket, state->flags, + state->Vcc, state->Vpp, state->io_irq, state->csc_mask); + return 0; +} /* cb_get_socket */ + +static int cb_set_socket(socket_info_t *s, socket_state_t *state) +{ + u_int reg; + + DEBUG(2, "yenta: SetSocket(%d, flags %#3.3x, Vcc %d, Vpp %d, " + "io_irq %d, csc_mask %#2.2x)\n", s-socket, state->flags, + state->Vcc, state->Vpp, state->io_irq, state->csc_mask); + + /* First set global controller options */ + if (s->cap.pci_irq) + cb_set_irq_mode(s, pci_csc, (s->cap.pci_irq == state->io_irq)); + s->bcr &= ~CB_BCR_CB_RESET; + s->bcr |= (state->flags & SS_RESET) ? CB_BCR_CB_RESET : 0; + set_bridge_state(s); + + cb_set_power(s, state); + + /* Handle IO interrupt using ISA routing */ + reg = s->intr; + if (state->io_irq != s->cap.pci_irq) reg |= state->io_irq; + i365_set(s, I365_INTCTL, reg); + + /* Handle CSC mask */ + if (!s->cs_irq && (!pci_csc || !s->cap.pci_irq)) + return 0; + reg = (s->cs_irq << 4); + if (state->csc_mask & SS_DETECT) reg |= I365_CSC_DETECT; + i365_set(s, I365_CSCINT, reg); + i365_get(s, I365_CSC); + cb_writel(s, CB_SOCKET_MASK, CB_SM_CCD); + cb_writel(s, CB_SOCKET_EVENT, -1); + + return 0; +} /* cb_set_socket */ + +static int cb_get_bridge(socket_info_t *s, struct cb_bridge_map *m) +{ + u_char map = m->map; + + if (map > 1) return -EINVAL; + m->flags &= MAP_IOSPACE; + map += (m->flags & MAP_IOSPACE) ? 2 : 0; + pci_readl(s, CB_MEM_BASE(map), &m->start); + pci_readl(s, CB_MEM_LIMIT(map), &m->stop); + if (m->start || m->stop) { + m->flags |= MAP_ACTIVE; + m->stop |= (map > 1) ? 3 : 0x0fff; + } + if (map > 1) { + u_short bcr; + pci_readw(s, CB_BRIDGE_CONTROL, &bcr); + m->flags |= (bcr & CB_BCR_PREFETCH(map)) ? MAP_PREFETCH : 0; + } + DEBUG(3, "yenta: GetBridge(%d, %d) = %#2.2x, %#4.4x-%#4.4x\n", + s-socket, map, m->flags, m->start, m->stop); + return 0; +} + +static int cb_set_bridge(socket_info_t *s, struct cb_bridge_map *m) +{ + u_char map; + + DEBUG(3, "yenta: SetBridge(%d, %d, %#2.2x, %#4.4x-%#4.4x)\n", + s-socket, m->map, m->flags, m->start, m->stop); + map = m->map; + if (!(s->flags & IS_CARDBUS) || (map > 1) || (m->stop < m->start)) + return -EINVAL; + if (m->flags & MAP_IOSPACE) { + if ((m->stop > 0xffff) || (m->start & 3) || + ((m->stop & 3) != 3)) + return -EINVAL; + map += 2; + } else { + if ((m->start & 0x0fff) || ((m->stop & 0x0fff) != 0x0fff)) + return -EINVAL; + s->bcr &= ~CB_BCR_PREFETCH(map); + s->bcr |= (m->flags & MAP_PREFETCH) ? CB_BCR_PREFETCH(map) : 0; + pci_writew(s, CB_BRIDGE_CONTROL, s->bcr); + } + if (m->flags & MAP_ACTIVE) { + pci_writel(s, CB_MEM_BASE(map), m->start); + pci_writel(s, CB_MEM_LIMIT(map), m->stop); + } else { + pci_writel(s, CB_MEM_LIMIT(map), 0); + pci_writel(s, CB_MEM_BASE(map), 0); + } + return 0; +} + +#endif /* CONFIG_CARDBUS */ + +/*====================================================================== + + Routines for accessing socket information and register dumps via + /proc/bus/pccard/... + +======================================================================*/ + +#ifdef HAS_PROC_BUS + +static int proc_read_info(char *buf, char **start, off_t pos, + int count, int *eof, void *data) +{ + socket_info_t *s = data; + char *p = buf; + p += sprintf(p, "type: %s\npsock: %d\n", + pcic[s->type].name, s->psock); +#ifdef CONFIG_PCI + if (s->flags & (IS_PCI|IS_CARDBUS)) + p += sprintf(p, "bus: %02x\ndevfn: %02x.%1x\n", + s->bus, PCI_SLOT(s->devfn), PCI_FUNC(s->devfn)); + if (s->flags & IS_CARDBUS) + p += sprintf(p, "cardbus: %02x\n", s->cap.cardbus); +#endif + return (p - buf); +} + +static int proc_read_exca(char *buf, char **start, off_t pos, + int count, int *eof, void *data) +{ + socket_info_t *s = data; + char *p = buf; + int i, top; + +#ifdef CONFIG_ISA + u_long flags = 0; +#endif + ISA_LOCK(s, flags); + top = 0x40; + if (s->flags & IS_CARDBUS) + top = (s->flags & IS_CIRRUS) ? 0x140 : 0x50; + for (i = 0; i < top; i += 4) { + if (i == 0x50) { + p += sprintf(p, "\n"); + i = 0x100; + } + p += sprintf(p, "%02x %02x %02x %02x%s", + i365_get(s,i), i365_get(s,i+1), + i365_get(s,i+2), i365_get(s,i+3), + ((i % 16) == 12) ? "\n" : " "); + } + ISA_UNLOCK(s, flags); + return (p - buf); +} + +#ifdef CONFIG_PCI +static int proc_read_pci(char *buf, char **start, off_t pos, + int count, int *eof, void *data) +{ + socket_info_t *s = data; + char *p = buf; + u_int a, b, c, d; + int i; + + for (i = 0; i < 0xc0; i += 0x10) { + pci_readl(s, i, &a); + pci_readl(s, i+4, &b); + pci_readl(s, i+8, &c); + pci_readl(s, i+12, &d); + p += sprintf(p, "%08x %08x %08x %08x\n", a, b, c, d); + } + return (p - buf); +} + +static int proc_read_cardbus(char *buf, char **start, off_t pos, + int count, int *eof, void *data) +{ + socket_info_t *s = data; + char *p = buf; + int i, top; + + top = (s->flags & IS_O2MICRO) ? 0x30 : 0x20; + for (i = 0; i < top; i += 0x10) + p += sprintf(p, "%08x %08x %08x %08x\n", + cb_readl(s,i+0x00), cb_readl(s,i+0x04), + cb_readl(s,i+0x08), cb_readl(s,i+0x0c)); + return (p - buf); +} +#endif + +static void pcic_proc_setup(socket_info_t *s, struct proc_dir_entry *base) +{ + create_proc_read_entry("info", 0, base, proc_read_info, s); + create_proc_read_entry("exca", 0, base, proc_read_exca, s); +#ifdef CONFIG_PCI + if (s->flags & (IS_PCI|IS_CARDBUS)) + create_proc_read_entry("pci", 0, base, proc_read_pci, s); + if (s->flags & IS_CARDBUS) + create_proc_read_entry("cardbus", 0, base, proc_read_cardbus, s); +#endif + s->proc = base; +} + +static void pcic_proc_remove(socket_info_t *s) +{ + struct proc_dir_entry *base = s->proc; + if (base == NULL) return; + remove_proc_entry("info", base); + remove_proc_entry("exca", base); +#ifdef CONFIG_PCI + if (s->flags & (IS_PCI|IS_CARDBUS)) + remove_proc_entry("pci", base); + if (s->flags & IS_CARDBUS) + remove_proc_entry("cardbus", base); +#endif +} + +#endif /* HAS_PROC_BUS */ + +/*====================================================================*/ + +typedef int (*subfn_t)(socket_info_t *, void *); + +static subfn_t pcic_service_table[] = { + (subfn_t)&pcic_register_callback, + (subfn_t)&pcic_inquire_socket, + (subfn_t)&i365_get_status, + (subfn_t)&i365_get_socket, + (subfn_t)&i365_set_socket, + (subfn_t)&i365_get_io_map, + (subfn_t)&i365_set_io_map, + (subfn_t)&i365_get_mem_map, + (subfn_t)&i365_set_mem_map, +#ifdef CONFIG_CARDBUS + (subfn_t)&cb_get_bridge, + (subfn_t)&cb_set_bridge, +#else + NULL, NULL, +#endif +#ifdef HAS_PROC_BUS + (subfn_t)&pcic_proc_setup +#endif +}; + +#define NFUNC (sizeof(pcic_service_table)/sizeof(subfn_t)) + +static int pcic_service(u_int sock, u_int cmd, void *arg) +{ + socket_info_t *s = &socket[sock]; + subfn_t fn; + int ret; +#ifdef CONFIG_ISA + u_long flags = 0; +#endif + + if (cmd >= NFUNC) + return -EINVAL; + + if (s->flags & IS_ALIVE) { + if (cmd == SS_GetStatus) + *(u_int *)arg = 0; + return -EINVAL; + } + + fn = pcic_service_table[cmd]; +#ifdef CONFIG_CARDBUS + if ((s->flags & IS_CARDBUS) && + (cb_readl(s, CB_SOCKET_STATE) & CB_SS_32BIT)) { + if (cmd == SS_GetStatus) + fn = (subfn_t)&cb_get_status; + else if (cmd == SS_GetSocket) + fn = (subfn_t)&cb_get_socket; + else if (cmd == SS_SetSocket) + fn = (subfn_t)&cb_set_socket; + } +#endif + + ISA_LOCK(s, flags); + ret = (fn == NULL) ? -EINVAL : fn(s, arg); + ISA_UNLOCK(s, flags); + return ret; +} /* pcic_service */ + +/*====================================================================*/ + +static int __init init_i82365(void) +{ + servinfo_t serv; + CardServices(GetCardServicesInfo, &serv); + if (serv.Revision != CS_RELEASE_CODE) { + printk(KERN_NOTICE "i82365: Card Services release " + "does not match!\n"); + return -EINVAL; + } + DEBUG(0, "%s\n", version); + +#ifdef CONFIG_PCI + if (pcic[IS_UNK_CARDBUS].flags != (IS_CARDBUS|IS_UNKNOWN)) { + printk(KERN_NOTICE "i82365: bad pcic_id enumeration!\n"); + return -EINVAL; + } +#endif + + printk(KERN_INFO "Intel ISA/PCI/CardBus PCIC probe:\n"); + sockets = 0; + +#ifdef CONFIG_PCI + if (do_pci_probe && pcibios_present()) { + pci_probe(PCI_CLASS_BRIDGE_CARDBUS); + pci_probe(PCI_CLASS_BRIDGE_PCMCIA); + } +#endif + +#ifdef CONFIG_ISA + isa_probe(i365_base); + if (!sockets || extra_sockets) + isa_probe(i365_base+2); +#endif + + if (sockets == 0) { + printk(KERN_INFO " no bridges found.\n"); + return -ENODEV; + } + + /* Set up interrupt handler(s) */ +#ifdef CONFIG_ISA + if (grab_irq != 0) + request_irq(cs_irq, pcic_interrupt, 0, "i82365", socket); +#endif +#ifdef CONFIG_PCI + if (pci_csc) { + u_int i, irq, mask = 0; + for (i = 0; i < sockets; i++) { + irq = socket[i].cap.pci_irq; + if (irq && !(mask & (1<<irq))) + request_irq(irq, pcic_interrupt, SA_SHIRQ, "i82365", socket); + mask |= (1<<irq); + } + } +#endif + + if (register_ss_entry(sockets, &pcic_service) != 0) + printk(KERN_NOTICE "i82365: register_ss_entry() failed\n"); + + /* Finally, schedule a polling interrupt */ + if (poll_interval != 0) { + poll_timer.expires = jiffies + poll_interval; + add_timer(&poll_timer); + } + + return 0; + +} /* init_i82365 */ + +static void __exit exit_i82365(void) +{ + int i; +#ifdef HAS_PROC_BUS + for (i = 0; i < sockets; i++) + pcic_proc_remove(&socket[i]); +#endif + unregister_ss_entry(&pcic_service); + if (poll_interval != 0) + del_timer(&poll_timer); +#ifdef CONFIG_ISA + if (grab_irq != 0) + free_irq(cs_irq, socket); +#endif +#ifdef CONFIG_PCI + if (pci_csc) { + u_int irq, mask = 0; + for (i = 0; i < sockets; i++) { + irq = socket[i].cap.pci_irq; + if (irq && !(mask & (1<<irq))) + free_irq(irq, socket); + mask |= (1<<irq); + } + } +#endif + for (i = 0; i < sockets; i++) { + socket_info_t *s = &socket[i]; + /* Turn off all interrupt sources! */ + i365_set(s, I365_CSCINT, 0); +#ifdef CONFIG_PCI + if (s->flags & IS_CARDBUS) + cb_writel(s, CB_SOCKET_MASK, 0); + if (s->cb_virt) { + iounmap(s->cb_virt); + release_mem_region(s->cb_phys, 0x1000); + } else +#endif + release_region(s->ioaddr, 2); + } +} /* exit_i82365 */ + +module_init(init_i82365); +module_exit(exit_i82365); diff --git a/linux/pcmcia-cs/modules/i82365.h b/linux/pcmcia-cs/modules/i82365.h new file mode 100644 index 0000000..27ee583 --- /dev/null +++ b/linux/pcmcia-cs/modules/i82365.h @@ -0,0 +1,154 @@ +/* + * i82365.h 1.21 2001/08/24 12:15:33 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in + * which case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use + * your version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + */ + +#ifndef _LINUX_I82365_H +#define _LINUX_I82365_H + +/* register definitions for the Intel 82365SL PCMCIA controller */ + +/* Offsets for PCIC registers */ +#define I365_IDENT 0x00 /* Identification and revision */ +#define I365_STATUS 0x01 /* Interface status */ +#define I365_POWER 0x02 /* Power and RESETDRV control */ +#define I365_INTCTL 0x03 /* Interrupt and general control */ +#define I365_CSC 0x04 /* Card status change */ +#define I365_CSCINT 0x05 /* Card status change interrupt control */ +#define I365_ADDRWIN 0x06 /* Address window enable */ +#define I365_IOCTL 0x07 /* I/O control */ +#define I365_GENCTL 0x16 /* Card detect and general control */ +#define I365_GBLCTL 0x1E /* Global control register */ + +/* Offsets for I/O and memory window registers */ +#define I365_IO(map) (0x08+((map)<<2)) +#define I365_MEM(map) (0x10+((map)<<3)) +#define I365_W_START 0 +#define I365_W_STOP 2 +#define I365_W_OFF 4 + +/* Flags for I365_STATUS */ +#define I365_CS_BVD1 0x01 +#define I365_CS_STSCHG 0x01 +#define I365_CS_BVD2 0x02 +#define I365_CS_SPKR 0x02 +#define I365_CS_DETECT 0x0C +#define I365_CS_WRPROT 0x10 +#define I365_CS_READY 0x20 /* Inverted */ +#define I365_CS_POWERON 0x40 +#define I365_CS_GPI 0x80 + +/* Flags for I365_POWER */ +#define I365_PWR_OFF 0x00 /* Turn off the socket */ +#define I365_PWR_OUT 0x80 /* Output enable */ +#define I365_PWR_NORESET 0x40 /* Disable RESETDRV on resume */ +#define I365_PWR_AUTO 0x20 /* Auto pwr switch enable */ +#define I365_VCC_MASK 0x18 /* Mask for turning off Vcc */ +/* There are different layouts for B-step and DF-step chips: the B + step has independent Vpp1/Vpp2 control, and the DF step has only + Vpp1 control, plus 3V control */ +#define I365_VCC_5V 0x10 /* Vcc = 5.0v */ +#define I365_VCC_3V 0x18 /* Vcc = 3.3v */ +#define I365_VPP2_MASK 0x0c /* Mask for turning off Vpp2 */ +#define I365_VPP2_5V 0x04 /* Vpp2 = 5.0v */ +#define I365_VPP2_12V 0x08 /* Vpp2 = 12.0v */ +#define I365_VPP1_MASK 0x03 /* Mask for turning off Vpp1 */ +#define I365_VPP1_5V 0x01 /* Vpp2 = 5.0v */ +#define I365_VPP1_12V 0x02 /* Vpp2 = 12.0v */ + +/* Flags for I365_INTCTL */ +#define I365_RING_ENA 0x80 +#define I365_PC_RESET 0x40 +#define I365_PC_IOCARD 0x20 +#define I365_INTR_ENA 0x10 +#define I365_IRQ_MASK 0x0F + +/* Flags for I365_CSC and I365_CSCINT*/ +#define I365_CSC_BVD1 0x01 +#define I365_CSC_STSCHG 0x01 +#define I365_CSC_BVD2 0x02 +#define I365_CSC_READY 0x04 +#define I365_CSC_DETECT 0x08 +#define I365_CSC_ANY 0x0F +#define I365_CSC_GPI 0x10 + +/* Flags for I365_ADDRWIN */ +#define I365_ADDR_MEMCS16 0x20 +#define I365_ENA_IO(map) (0x40 << (map)) +#define I365_ENA_MEM(map) (0x01 << (map)) + +/* Flags for I365_IOCTL */ +#define I365_IOCTL_MASK(map) (0x0F << (map<<2)) +#define I365_IOCTL_WAIT(map) (0x08 << (map<<2)) +#define I365_IOCTL_0WS(map) (0x04 << (map<<2)) +#define I365_IOCTL_IOCS16(map) (0x02 << (map<<2)) +#define I365_IOCTL_16BIT(map) (0x01 << (map<<2)) + +/* Flags for I365_GENCTL */ +#define I365_CTL_16DELAY 0x01 +#define I365_CTL_RESET 0x02 +#define I365_CTL_GPI_ENA 0x04 +#define I365_CTL_GPI_CTL 0x08 +#define I365_CTL_RESUME 0x10 +#define I365_CTL_SW_IRQ 0x20 + +/* Flags for I365_GBLCTL */ +#define I365_GBL_PWRDOWN 0x01 +#define I365_GBL_CSC_LEV 0x02 +#define I365_GBL_WRBACK 0x04 +#define I365_GBL_IRQ_0_LEV 0x08 +#define I365_GBL_IRQ_1_LEV 0x10 + +/* Flags for memory window registers */ +#define I365_MEM_16BIT 0x8000 /* In memory start high byte */ +#define I365_MEM_0WS 0x4000 +#define I365_MEM_WS1 0x8000 /* In memory stop high byte */ +#define I365_MEM_WS0 0x4000 +#define I365_MEM_WRPROT 0x8000 /* In offset high byte */ +#define I365_MEM_REG 0x4000 + +#define I365_REG(slot, reg) (((slot) << 6) | (reg)) + +/* Default ISA interrupt mask */ +#define I365_ISA_IRQ_MASK 0xdeb8 /* irq's 3-5,7,9-12,14,15 */ + +/* Device ID's for PCI-to-PCMCIA bridges */ + +#ifndef PCI_VENDOR_ID_INTEL +#define PCI_VENDOR_ID_INTEL 0x8086 +#endif +#ifndef PCI_DEVICE_ID_INTEL_82092AA_0 +#define PCI_DEVICE_ID_INTEL_82092AA_0 0x1221 +#endif +#ifndef PCI_VENDOR_ID_OMEGA +#define PCI_VENDOR_ID_OMEGA 0x119b +#endif +#ifndef PCI_DEVICE_ID_OMEGA_82C092G +#define PCI_DEVICE_ID_OMEGA_82C092G 0x1221 +#endif + +#endif /* _LINUX_I82365_H */ diff --git a/linux/pcmcia-cs/modules/o2micro.h b/linux/pcmcia-cs/modules/o2micro.h new file mode 100644 index 0000000..fd15234 --- /dev/null +++ b/linux/pcmcia-cs/modules/o2micro.h @@ -0,0 +1,160 @@ +/* + * o2micro.h 1.20 2002/03/03 14:16:57 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in + * which case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use + * your version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + */ + +#ifndef _LINUX_O2MICRO_H +#define _LINUX_O2MICRO_H + +#ifndef PCI_VENDOR_ID_O2 +#define PCI_VENDOR_ID_O2 0x1217 +#endif +#ifndef PCI_DEVICE_ID_O2_6729 +#define PCI_DEVICE_ID_O2_6729 0x6729 +#endif +#ifndef PCI_DEVICE_ID_O2_6730 +#define PCI_DEVICE_ID_O2_6730 0x673a +#endif +#ifndef PCI_DEVICE_ID_O2_6832 +#define PCI_DEVICE_ID_O2_6832 0x6832 +#endif +#ifndef PCI_DEVICE_ID_O2_6836 +#define PCI_DEVICE_ID_O2_6836 0x6836 +#endif +#ifndef PCI_DEVICE_ID_O2_6812 +#define PCI_DEVICE_ID_O2_6812 0x6872 +#endif +#ifndef PCI_DEVICE_ID_O2_6922 +#define PCI_DEVICE_ID_O2_6922 0x6825 +#endif +#ifndef PCI_DEVICE_ID_O2_6933 +#define PCI_DEVICE_ID_O2_6933 0x6933 +#endif +#ifndef PCI_DEVICE_ID_O2_6912 +#define PCI_DEVICE_ID_O2_6912 0x6972 +#endif + +/* Additional PCI configuration registers */ + +#define O2_MUX_CONTROL 0x90 /* 32 bit */ +#define O2_MUX_RING_OUT 0x0000000f +#define O2_MUX_SKTB_ACTV 0x000000f0 +#define O2_MUX_SCTA_ACTV_ENA 0x00000100 +#define O2_MUX_SCTB_ACTV_ENA 0x00000200 +#define O2_MUX_SER_IRQ_ROUTE 0x0000e000 +#define O2_MUX_SER_PCI 0x00010000 + +#define O2_MUX_SKTA_TURBO 0x000c0000 /* for 6833, 6860 */ +#define O2_MUX_SKTB_TURBO 0x00300000 +#define O2_MUX_AUX_VCC_3V 0x00400000 +#define O2_MUX_PCI_VCC_5V 0x00800000 +#define O2_MUX_PME_MUX 0x0f000000 + +/* Additional ExCA registers */ + +#define O2_MODE_A 0x38 +#define O2_MODE_A_2 0x26 /* for 6833B, 6860C */ +#define O2_MODE_A_CD_PULSE 0x04 +#define O2_MODE_A_SUSP_EDGE 0x08 +#define O2_MODE_A_HOST_SUSP 0x10 +#define O2_MODE_A_PWR_MASK 0x60 +#define O2_MODE_A_QUIET 0x80 + +#define O2_MODE_B 0x39 +#define O2_MODE_B_2 0x2e /* for 6833B, 6860C */ +#define O2_MODE_B_IDENT 0x03 +#define O2_MODE_B_ID_BSTEP 0x00 +#define O2_MODE_B_ID_CSTEP 0x01 +#define O2_MODE_B_ID_O2 0x02 +#define O2_MODE_B_VS1 0x04 +#define O2_MODE_B_VS2 0x08 +#define O2_MODE_B_IRQ15_RI 0x80 + +#define O2_MODE_C 0x3a +#define O2_MODE_C_DREQ_MASK 0x03 +#define O2_MODE_C_DREQ_INPACK 0x01 +#define O2_MODE_C_DREQ_WP 0x02 +#define O2_MODE_C_DREQ_BVD2 0x03 +#define O2_MODE_C_ZVIDEO 0x08 +#define O2_MODE_C_IREQ_SEL 0x30 +#define O2_MODE_C_MGMT_SEL 0xc0 + +#define O2_MODE_D 0x3b +#define O2_MODE_D_IRQ_MODE 0x03 +#define O2_MODE_D_IRQ_PCPCI 0x00 +#define O2_MODE_D_IRQ_PCIWAY 0x02 +#define O2_MODE_D_IRQ_PCI 0x03 +#define O2_MODE_D_PCI_CLKRUN 0x04 +#define O2_MODE_D_CB_CLKRUN 0x08 +#define O2_MODE_D_SKT_ACTV 0x20 +#define O2_MODE_D_PCI_FIFO 0x40 /* for OZ6729, OZ6730 */ +#define O2_MODE_D_W97_IRQ 0x40 +#define O2_MODE_D_ISA_IRQ 0x80 + +#define O2_MHPG_DMA 0x3c +#define O2_MHPG_CHANNEL 0x07 +#define O2_MHPG_CINT_ENA 0x08 +#define O2_MHPG_CSC_ENA 0x10 + +#define O2_FIFO_ENA 0x3d +#define O2_FIFO_ZVIDEO_3 0x08 +#define O2_FIFO_PCI_FIFO 0x10 +#define O2_FIFO_POSTWR 0x40 +#define O2_FIFO_BUFFER 0x80 + +#define O2_MODE_E 0x3e +#define O2_MODE_E_MHPG_DMA 0x01 +#define O2_MODE_E_SPKR_OUT 0x02 +#define O2_MODE_E_LED_OUT 0x08 +#define O2_MODE_E_SKTA_ACTV 0x10 + +/* Data structure for tracking vendor-specific state */ +typedef struct o2micro_state_t { + u_char mode_a; /* O2_MODE_A */ + u_char mode_b; /* O2_MODE_B */ + u_char mode_c; /* O2_MODE_C */ + u_char mode_d; /* O2_MODE_D */ + u_char mhpg; /* O2_MHPG_DMA */ + u_char fifo; /* O2_FIFO_ENA */ + u_char mode_e; /* O2_MODE_E */ +} o2micro_state_t; + +#define O2MICRO_PCIC_ID \ + IS_OZ6729, IS_OZ6730, IS_OZ6832, IS_OZ6836, IS_OZ6812, \ + IS_OZ6922, IS_OZ6933, IS_OZ6912 + +#define O2MICRO_PCIC_INFO \ + { "O2Micro OZ6729", IS_O2MICRO|IS_PCI|IS_VG_PWR, ID(O2, 6729) }, \ + { "O2Micro OZ6730", IS_O2MICRO|IS_PCI|IS_VG_PWR, ID(O2, 6730) }, \ + { "O2Micro OZ6832/33", IS_O2MICRO|IS_CARDBUS, ID(O2, 6832) }, \ + { "O2Micro OZ6836/60", IS_O2MICRO|IS_CARDBUS, ID(O2, 6836) }, \ + { "O2Micro OZ6812", IS_O2MICRO|IS_CARDBUS, ID(O2, 6812) }, \ + { "O2Micro OZ6922", IS_O2MICRO|IS_CARDBUS, ID(O2, 6922) }, \ + { "O2Micro OZ6933", IS_O2MICRO|IS_CARDBUS, ID(O2, 6933) }, \ + { "O2Micro OZ6912", IS_O2MICRO|IS_CARDBUS, ID(O2, 6912) } + +#endif /* _LINUX_O2MICRO_H */ diff --git a/linux/pcmcia-cs/modules/pci_fixup.c b/linux/pcmcia-cs/modules/pci_fixup.c new file mode 100644 index 0000000..b892f63 --- /dev/null +++ b/linux/pcmcia-cs/modules/pci_fixup.c @@ -0,0 +1,674 @@ +/*====================================================================== + + Kernel fixups for PCI device support + + pci_fixup.c 1.33 2002/10/12 19:02:59 + + PCI bus fixups: various bits of code that don't really belong in + the PCMCIA subsystem, but may or may not be available from the + kernel, depending on kernel version. The basic idea is to make + 2.0.* and 2.2.* kernels look like they have the 2.3.* features. + +======================================================================*/ + +#define __NO_VERSION__ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/pci.h> +#include <asm/io.h> + +/* We use these for setting up CardBus bridges */ +#include "yenta.h" +#include "i82365.h" + +#define VERSION KERNEL_VERSION +#if (LINUX_VERSION_CODE < VERSION(2,3,24)) + +/* Default memory base addresses for CardBus controllers */ +static u_int cb_mem_base[] = { 0x0, 0x68000000, 0xf8000000 }; +MODULE_PARM(cb_mem_base, "i"); + +/* PCI bus number overrides for CardBus controllers */ +#define INT_MODULE_PARM(n, v) static int n = v; MODULE_PARM(n, "i") +INT_MODULE_PARM(cb_bus_base, 0); +INT_MODULE_PARM(cb_bus_step, 2); +INT_MODULE_PARM(cb_pci_irq, 0); + +#endif + +/* (exported) mask of interrupts reserved for PCI devices */ +u32 pci_irq_mask = 0; + +/*====================================================================== + + Basic PCI services missing from older kernels: device lookup, etc + +======================================================================*/ + +#if (LINUX_VERSION_CODE < VERSION(2,1,0)) +struct pci_dev *pci_devices = NULL; +struct pci_bus pci_root = { + parent: NULL, + children: NULL, + next: NULL, + self: NULL, + devices: NULL, + number: 0 +}; +#endif + +#if (LINUX_VERSION_CODE < VERSION(2,1,93)) + +struct pci_dev *pci_find_slot(u_int bus, u_int devfn) +{ + struct pci_dev *dev; + for (dev = pci_devices; dev; dev = dev->next) + if ((dev->devfn == devfn) && (bus == dev->bus->number)) + return dev; +#if (LINUX_VERSION_CODE > VERSION(2,1,0)) + return NULL; +#else + { + struct pci_bus *b; + u8 hdr; + u32 id, class; + + if (pcibios_read_config_byte(bus, devfn & ~7, PCI_HEADER_TYPE, &hdr)) + return NULL; + if (PCI_FUNC(devfn) && !(hdr & 0x80)) + return NULL; + pcibios_read_config_dword(bus, devfn, PCI_VENDOR_ID, &id); + if ((id == 0) || (id == 0xffffffff)) + return NULL; + dev = kmalloc(sizeof *dev, GFP_ATOMIC); + if (!dev) + return NULL; + memset(dev, 0, sizeof *dev); + dev->devfn = devfn; + pcibios_read_config_byte(bus, devfn, PCI_INTERRUPT_LINE, &dev->irq); + dev->vendor = id & 0xffff; + dev->device = id >> 16; + pcibios_read_config_dword(bus, devfn, PCI_CLASS_REVISION, &class); + if (dev->irq == 255) + dev->irq = 0; + dev->class = class >> 8; + for (b = &pci_root; b; b = b->next) + if (b->number == bus) break; + if (!b) { + b = kmalloc(sizeof *b, GFP_ATOMIC); + if (!b) { + kfree(dev); + return NULL; + } + memset(b, 0, sizeof *b); + b->number = bus; + b->next = pci_root.next; + pci_root.next = b; + } + dev->bus = b; + return dev; + } +#endif +} + +struct pci_dev *pci_find_class(u_int class, struct pci_dev *from) +{ + static u16 index = 0; + u8 bus, devfn; + if (from == NULL) + index = 0; + if (pcibios_find_class(class, index++, &bus, &devfn) == 0) + return pci_find_slot(bus, devfn); + else + return NULL; +} + +#endif /* (LINUX_VERSION_CODE < VERSION(2,1,93)) */ + +/*====================================================================== + + PCI Interrupt Routing Table parser + + This only needs to be done once per boot: we scan the BIOS for + the routing table, and then look for devices that have interrupt + assignments that the kernel doesn't know about. If we find any, + we update their pci_dev structures and write the PCI interrupt + line registers. + +======================================================================*/ + +#if (LINUX_VERSION_CODE < VERSION(2,3,24)) && defined(__i386__) + +#pragma pack(1) + +struct slot_entry { + u8 bus, devfn; + struct pirq_pin { + u8 link; + u16 irq_map; + } pin[4]; + u8 slot; + u8 reserved; +}; + +struct routing_table { + u32 signature; + u8 minor, major; + u16 size; + u8 bus, devfn; + u16 pci_mask; + u32 compat; + u32 miniport; + u8 reserved[11]; + u8 checksum; + struct slot_entry entry[0]; +}; + +#pragma pack() + +/* + The meaning of the link bytes in the routing table is vendor + specific. We need code to get and set the routing information. +*/ + +static u8 pIIx_link(struct pci_dev *router, u8 link) +{ + u8 pirq; + /* link should be 0x60, 0x61, 0x62, 0x63 */ + pci_read_config_byte(router, link, &pirq); + return (pirq < 16) ? pirq : 0; +} + +static void pIIx_init(struct pci_dev *router, u8 link, u8 irq) +{ + pci_write_config_byte(router, link, irq); +} + +static u8 via_link(struct pci_dev *router, u8 link) +{ + u8 pirq = 0; + /* link should be 1, 2, 3, 5 */ + if (link < 6) + pci_read_config_byte(router, 0x55 + (link>>1), &pirq); + return (link & 1) ? (pirq >> 4) : (pirq & 15); +} + +static void via_init(struct pci_dev *router, u8 link, u8 irq) +{ + u8 pirq; + pci_read_config_byte(router, 0x55 + (link>>1), &pirq); + pirq &= (link & 1) ? 0x0f : 0xf0; + pirq |= (link & 1) ? (irq << 4) : (irq & 15); + pci_write_config_byte(router, 0x55 + (link>>1), pirq); +} + +static u8 opti_link(struct pci_dev *router, u8 link) +{ + u8 pirq = 0; + /* link should be 0x02, 0x12, 0x22, 0x32 */ + if ((link & 0xcf) == 0x02) + pci_read_config_byte(router, 0xb8 + (link >> 5), &pirq); + return (link & 0x10) ? (pirq >> 4) : (pirq & 15); +} + +static void opti_init(struct pci_dev *router, u8 link, u8 irq) +{ + u8 pirq; + pci_read_config_byte(router, 0xb8 + (link >> 5), &pirq); + pirq &= (link & 0x10) ? 0x0f : 0xf0; + pirq |= (link & 0x10) ? (irq << 4) : (irq & 15); + pci_write_config_byte(router, 0xb8 + (link >> 5), pirq); +} + +static u8 ali_link(struct pci_dev *router, u8 link) +{ + /* No, you're not dreaming */ + static const u8 map[] = + { 0, 9, 3, 10, 4, 5, 7, 6, 1, 11, 0, 12, 0, 14, 0, 15 }; + u8 pirq; + /* link should be 0x01..0x08 */ + pci_read_config_byte(router, 0x48 + ((link-1)>>1), &pirq); + return (link & 1) ? map[pirq&15] : map[pirq>>4]; +} + +static void ali_init(struct pci_dev *router, u8 link, u8 irq) +{ + /* Inverse of map in ali_link */ + static const u8 map[] = + { 0, 8, 0, 2, 4, 5, 7, 6, 0, 1, 3, 9, 11, 0, 13, 15 }; + u8 pirq; + pci_read_config_byte(router, 0x48 + ((link-1)>>1), &pirq); + pirq &= (link & 1) ? 0x0f : 0xf0; + pirq |= (link & 1) ? (map[irq] << 4) : (map[irq] & 15); + pci_write_config_byte(router, 0x48 + ((link-1)>>1), pirq); +} + +static u8 cyrix_link(struct pci_dev *router, u8 link) +{ + u8 pirq; + /* link should be 1, 2, 3, 4 */ + link--; + pci_read_config_byte(router, 0x5c + (link>>1), &pirq); + return ((link & 1) ? pirq >> 4 : pirq & 15); +} + +static void cyrix_init(struct pci_dev *router, u8 link, u8 irq) +{ + u8 pirq; + link--; + pci_read_config_byte(router, 0x5c + (link>>1), &pirq); + pirq &= (link & 1) ? 0x0f : 0xf0; + pirq |= (link & 1) ? (irq << 4) : (irq & 15); + pci_write_config_byte(router, 0x5c + (link>>1), pirq); +} + +/* + A table of all the PCI interrupt routers for which we know how to + interpret the link bytes. +*/ + +#ifndef PCI_DEVICE_ID_INTEL_82371FB_0 +#define PCI_DEVICE_ID_INTEL_82371FB_0 0x122e +#endif +#ifndef PCI_DEVICE_ID_INTEL_82371SB_0 +#define PCI_DEVICE_ID_INTEL_82371SB_0 0x7000 +#endif +#ifndef PCI_DEVICE_ID_INTEL_82371AB_0 +#define PCI_DEVICE_ID_INTEL_82371AB_0 0x7110 +#endif +#ifndef PCI_DEVICE_ID_INTEL_82443MX_1 +#define PCI_DEVICE_ID_INTEL_82443MX_1 0x7198 +#endif +#ifndef PCI_DEVICE_ID_INTEL_82443MX_1 +#define PCI_DEVICE_ID_INTEL_82443MX_1 0x7198 +#endif +#ifndef PCI_DEVICE_ID_INTEL_82801AA_0 +#define PCI_DEVICE_ID_INTEL_82801AA_0 0x2410 +#endif +#ifndef PCI_DEVICE_ID_INTEL_82801AB_0 +#define PCI_DEVICE_ID_INTEL_82801AB_0 0x2420 +#endif +#ifndef PCI_DEVICE_ID_INTEL_82801BA_0 +#define PCI_DEVICE_ID_INTEL_82801BA_0 0x2440 +#endif +#ifndef PCI_DEVICE_ID_INTEL_82801BAM_0 +#define PCI_DEVICE_ID_INTEL_82801BAM_0 0x244c +#endif +#ifndef PCI_DEVICE_ID_VIA_82C586_0 +#define PCI_DEVICE_ID_VIA_82C586_0 0x0586 +#endif +#ifndef PCI_DEVICE_ID_VIA_82C596 +#define PCI_DEVICE_ID_VIA_82C596 0x0596 +#endif +#ifndef PCI_DEVICE_ID_VIA_82C686 +#define PCI_DEVICE_ID_VIA_82C686 0x0686 +#endif +#ifndef PCI_DEVICE_ID_SI +#define PCI_DEVICE_ID_SI 0x1039 +#endif +#ifndef PCI_DEVICE_ID_SI_503 +#define PCI_DEVICE_ID_SI_503 0x0008 +#endif +#ifndef PCI_DEVICE_ID_SI_496 +#define PCI_DEVICE_ID_SI_496 0x0496 +#endif + +#define ID(a,b) PCI_VENDOR_ID_##a,PCI_DEVICE_ID_##a##_##b + +struct router { + u16 vendor, device; + u8 (*xlate)(struct pci_dev *, u8); + void (*init)(struct pci_dev *, u8, u8); +} router_table[] = { + { ID(INTEL, 82371FB_0), &pIIx_link, &pIIx_init }, + { ID(INTEL, 82371SB_0), &pIIx_link, &pIIx_init }, + { ID(INTEL, 82371AB_0), &pIIx_link, &pIIx_init }, + { ID(INTEL, 82443MX_1), &pIIx_link, &pIIx_init }, + { ID(INTEL, 82801AA_0), &pIIx_link, &pIIx_init }, + { ID(INTEL, 82801AB_0), &pIIx_link, &pIIx_init }, + { ID(INTEL, 82801BA_0), &pIIx_link, &pIIx_init }, + { ID(INTEL, 82801BAM_0), &pIIx_link, &pIIx_init }, + { ID(VIA, 82C586_0), &via_link, &via_init }, + { ID(VIA, 82C596), &via_link, &via_init }, + { ID(VIA, 82C686), &via_link, &via_init }, + { ID(OPTI, 82C700), &opti_link, &opti_init }, + { ID(AL, M1533), &ali_link, &ali_init }, + { ID(SI, 503), &pIIx_link, &pIIx_init }, + { ID(SI, 496), &pIIx_link, &pIIx_init }, + { ID(CYRIX, 5530_LEGACY), &cyrix_link, &cyrix_init } +}; +#define ROUTER_COUNT (sizeof(router_table)/sizeof(router_table[0])) + +/* Global variables for current interrupt routing table */ +static struct routing_table *pirq = NULL; +static struct pci_dev *router_dev = NULL; +static struct router *router_info = NULL; + +#ifndef __va +#define __va(x) (x) +#endif + +static void scan_pirq_table(void) +{ + struct routing_table *r; + struct pci_dev *router, *dev; + u8 pin, fn, *p; + int i, j; + struct slot_entry *e; + + /* Scan the BIOS for the routing table signature */ + for (p = (u8 *)__va(0xf0000); p < (u8 *)__va(0xfffff); p += 16) + if ((p[0] == '$') && (p[1] == 'P') && + (p[2] == 'I') && (p[3] == 'R')) break; + if (p >= (u8 *)__va(0xfffff)) + return; + + pirq = r = (struct routing_table *)p; + printk(KERN_INFO "PCI routing table version %d.%d at %#06x\n", + r->major, r->minor, (u32)r & 0xfffff); + for (i = j = 0; i < 16; i++) + j += (r->pci_mask >> i) & 1; + if (j > 4) + printk(KERN_NOTICE " bogus PCI irq mask %#04x!\n", + r->pci_mask); + else + pci_irq_mask |= r->pci_mask; + + router_dev = router = pci_find_slot(r->bus, r->devfn); + if (router) { + for (i = 0; i < ROUTER_COUNT; i++) { + if ((router->vendor == router_table[i].vendor) && + (router->device == router_table[i].device)) + break; + if (((r->compat & 0xffff) == router_table[i].vendor) && + ((r->compat >> 16) == router_table[i].device)) + break; + } + if (i == ROUTER_COUNT) + printk(KERN_INFO " unknown PCI interrupt router %04x:%04x\n", + router->vendor, router->device); + else + router_info = &router_table[i]; + } + + for (e = r->entry; (u8 *)e < p+r->size; e++) { + for (fn = 0; fn < 8; fn++) { + dev = pci_find_slot(e->bus, e->devfn | fn); + if ((dev == NULL) || (dev->irq != 0)) continue; + pci_read_config_byte(dev, PCI_INTERRUPT_PIN, &pin); + if ((pin == 0) || (pin == 255)) continue; + if (router_info) { + dev->irq = router_info->xlate(router, e->pin[pin-1].link); + } else { + /* Fallback: see if only one irq possible */ + int map = e->pin[pin-1].irq_map; + if (map && (!(map & (map-1)))) + dev->irq = ffs(map)-1; + } + if (dev->irq) { + printk(KERN_INFO " %02x:%02x.%1x -> irq %d\n", + e->bus, PCI_SLOT(dev->devfn), + PCI_FUNC(dev->devfn), dev->irq); + pci_write_config_byte(dev, PCI_INTERRUPT_LINE, + dev->irq); + } + } + } +} + +#endif /* (LINUX_VERSION_CODE < VERSION(2,3,24)) && defined(__i386__) */ + +/*====================================================================== + + PCI device enabler + + This is not at all generic... it is mostly a hack to correctly + configure CardBus bridges. + +======================================================================*/ + +#if (LINUX_VERSION_CODE < VERSION(2,3,24)) + +static int check_cb_mapping(u_int phys) +{ + /* A few sanity checks to validate the bridge mapping */ + char *virt = ioremap(phys, 0x1000); + int ret = ((readb(virt+0x800+I365_IDENT) & 0x70) || + (readb(virt+0x800+I365_CSC) && + readb(virt+0x800+I365_CSC) && + readb(virt+0x800+I365_CSC))); + int state = readl(virt+CB_SOCKET_STATE) >> 16; + ret |= (state & ~0x3000) || !(state & 0x3000); + ret |= readl(virt+CB_SOCKET_FORCE); + iounmap(virt); + return ret; +} + +static void setup_cb_bridge(struct pci_dev *dev) +{ + u8 bus, sub; + u32 phys; + int i; + + /* This is nasty, but where else can we put it? */ + if (PCI_FUNC(dev->devfn) == 0) { + struct pci_dev *sib; + sib = pci_find_slot(dev->bus->number, dev->devfn+1); + if (sib) { + u8 a, b; + u32 c, d; + /* Check for bad PCI bus numbering */ + pci_read_config_byte(dev, CB_CARDBUS_BUS, &a); + pci_read_config_byte(sib, CB_CARDBUS_BUS, &b); + if (a == b) { + pci_write_config_byte(dev, CB_CARDBUS_BUS, 0); + pci_write_config_byte(sib, CB_CARDBUS_BUS, 0); + } + /* check for bad register mapping */ + pci_read_config_dword(dev, PCI_BASE_ADDRESS_0, &c); + pci_read_config_dword(sib, PCI_BASE_ADDRESS_0, &d); + if ((c != 0) && (c == d)) { + pci_write_config_dword(dev, PCI_BASE_ADDRESS_0, 0); + pci_write_config_dword(sib, PCI_BASE_ADDRESS_0, 0); + } + } + } + + /* Assign PCI bus numbers, if needed */ + pci_read_config_byte(dev, CB_CARDBUS_BUS, &bus); + pci_read_config_byte(dev, CB_SUBORD_BUS, &sub); + if ((cb_bus_base > 0) || (bus == 0)) { + if (cb_bus_base <= 0) cb_bus_base = 0x20; + bus = cb_bus_base; + sub = cb_bus_base+cb_bus_step; + cb_bus_base += cb_bus_step+1; + pci_write_config_byte(dev, CB_CARDBUS_BUS, bus); + pci_write_config_byte(dev, CB_SUBORD_BUS, sub); + } + + /* Create pci_bus structure for the CardBus, if needed */ + { + struct pci_bus *child, *parent = dev->bus; + for (child = parent->children; child; child = child->next) + if (child->number == bus) break; + if (!child) { + child = kmalloc(sizeof(struct pci_bus), GFP_KERNEL); + memset(child, 0, sizeof(struct pci_bus)); + child->self = dev; + child->primary = bus; + child->number = child->secondary = bus; + child->subordinate = sub; + child->parent = parent; +#if (LINUX_VERSION_CODE >= VERSION(2,3,15)) + child->ops = parent->ops; +#endif + child->next = parent->children; + parent->children = child; + } + } + + /* Map the CardBus bridge registers, if needed */ + pci_write_config_dword(dev, CB_LEGACY_MODE_BASE, 0); + pci_read_config_dword(dev, PCI_BASE_ADDRESS_0, &phys); + if ((phys == 0) || (cb_mem_base[0] != 0)) { + /* Make sure the bridge is awake so we can test it */ + pci_set_power_state(dev, 0); + for (i = 0; i < sizeof(cb_mem_base)/sizeof(u_int); i++) { + phys = cb_mem_base[i]; + if (phys == 0) continue; + pci_write_config_dword(dev, PCI_BASE_ADDRESS_0, phys); + if ((i == 0) || (check_cb_mapping(phys) == 0)) break; + } + if (i == sizeof(cb_mem_base)/sizeof(u_int)) { + pci_write_config_dword(dev, PCI_BASE_ADDRESS_0, 0); + } else { + cb_mem_base[0] = cb_mem_base[i] + 0x1000; + } + } +} + +#ifdef __i386__ + +static u8 pirq_init(struct pci_dev *router, struct pirq_pin *pin) +{ + u16 map = pin->irq_map; + u8 irq = 0; + if (pirq->pci_mask) + map &= pirq->pci_mask; + if (cb_pci_irq) + map = 1<<cb_pci_irq; + /* Be conservative: only init irq if the mask is unambiguous */ + if (map && (!(map & (map-1)))) { + irq = ffs(map)-1; + router_info->init(router, pin->link, irq); + pci_irq_mask |= (1<<irq); + } + return irq; +} + +static void setup_cb_bridge_irq(struct pci_dev *dev) +{ + struct slot_entry *e; + u8 pin; + u32 phys; + char *virt; + + pci_read_config_byte(dev, PCI_INTERRUPT_PIN, &pin); + pci_read_config_dword(dev, PCI_BASE_ADDRESS_0, &phys); + if (!pin || !phys) + return; + virt = ioremap(phys, 0x1000); + if (virt) { + /* Disable any pending interrupt sources */ + writel(0, virt+CB_SOCKET_MASK); + writel(-1, virt+CB_SOCKET_EVENT); + iounmap(virt); + } + for (e = pirq->entry; (u8 *)e < (u8 *)pirq + pirq->size; e++) { + if ((e->bus != dev->bus->number) || + (e->devfn != (dev->devfn & ~7))) + continue; + dev->irq = pirq_init(router_dev, &e->pin[pin-1]); + pci_write_config_byte(dev, PCI_INTERRUPT_LINE, dev->irq); + break; + } +} + +#endif + +int pci_enable_device(struct pci_dev *dev) +{ + if ((dev->class >> 8) == PCI_CLASS_BRIDGE_CARDBUS) { + setup_cb_bridge(dev); + } +#ifdef __i386__ + /* In certain cases, if the interrupt can be deduced, but was + unrouted when the pirq table was scanned, we'll try to set it + up now. */ + if (!dev->irq && pirq && (router_info) && + ((dev->class >> 8) == PCI_CLASS_BRIDGE_CARDBUS)) { + setup_cb_bridge_irq(dev); + } +#endif + return 0; +} + +int pci_set_power_state(struct pci_dev *dev, int state) +{ + u16 tmp, cmd; + u32 base, bus; + u8 a, b, pmcs; + pci_read_config_byte(dev, PCI_STATUS, &a); + if (a & PCI_STATUS_CAPLIST) { + pci_read_config_byte(dev, PCI_CB_CAPABILITY_POINTER, &b); + while (b != 0) { + pci_read_config_byte(dev, b+PCI_CAPABILITY_ID, &a); + if (a == PCI_CAPABILITY_PM) { + pmcs = b + PCI_PM_CONTROL_STATUS; + /* Make sure we're in D0 state */ + pci_read_config_word(dev, pmcs, &tmp); + if (!(tmp & PCI_PMCS_PWR_STATE_MASK)) break; + pci_read_config_dword(dev, PCI_BASE_ADDRESS_0, &base); + pci_read_config_dword(dev, CB_PRIMARY_BUS, &bus); + pci_read_config_word(dev, PCI_COMMAND, &cmd); + pci_write_config_word(dev, pmcs, PCI_PMCS_PWR_STATE_D0); + pci_write_config_dword(dev, PCI_BASE_ADDRESS_0, base); + pci_write_config_dword(dev, CB_PRIMARY_BUS, bus); + pci_write_config_word(dev, PCI_COMMAND, cmd); + break; + } + pci_read_config_byte(dev, b+PCI_NEXT_CAPABILITY, &b); + } + } + return 0; +} + +#endif /* (LINUX_VERSION_CODE < VERSION(2,3,24)) */ + +/*====================================================================== + + General setup and cleanup entry points + +======================================================================*/ + +void pci_fixup_init(void) +{ + struct pci_dev *p; + +#if (LINUX_VERSION_CODE < VERSION(2,3,24)) && defined(__i386__) + scan_pirq_table(); + pci_for_each_dev(p) + if (((p->class >> 8) == PCI_CLASS_BRIDGE_CARDBUS) && + (p->irq == 0)) break; + if (p && !pirq) + printk(KERN_INFO "No PCI interrupt routing table!\n"); + if (!pirq && cb_pci_irq) + printk(KERN_INFO "cb_pci_irq will be ignored.\n"); +#endif + + pci_for_each_dev(p) + pci_irq_mask |= (1<<p->irq); + +#ifdef __alpha__ +#define PIC 0x4d0 + pci_irq_mask |= inb(PIC) | (inb(PIC+1) << 8); +#endif +} + +void pci_fixup_done(void) +{ +#if (LINUX_VERSION_CODE < VERSION(2,1,0)) + struct pci_dev *d, *dn; + struct pci_bus *b, *bn; + for (d = pci_devices; d; d = dn) { + dn = d->next; + kfree(d); + } + for (b = pci_root.next; b; b = bn) { + bn = b->next; + kfree(b); + } +#endif +} diff --git a/linux/pcmcia-cs/modules/ricoh.h b/linux/pcmcia-cs/modules/ricoh.h new file mode 100644 index 0000000..de62f8b --- /dev/null +++ b/linux/pcmcia-cs/modules/ricoh.h @@ -0,0 +1,161 @@ +/* + * ricoh.h 1.16 2002/08/13 15:17:14 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in + * which case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use + * your version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + */ + +#ifndef _LINUX_RICOH_H +#define _LINUX_RICOH_H + +#define RF5C_MODE_CTL 0x1f /* Mode control */ +#define RF5C_PWR_CTL 0x2f /* Mixed voltage control */ +#define RF5C_CHIP_ID 0x3a /* Chip identification */ +#define RF5C_MODE_CTL_3 0x3b /* Mode control 3 */ + +/* I/O window address offset */ +#define RF5C_IO_OFF(w) (0x36+((w)<<1)) + +/* Flags for RF5C_MODE_CTL */ +#define RF5C_MODE_ATA 0x01 /* ATA mode */ +#define RF5C_MODE_LED_ENA 0x02 /* IRQ 12 is LED */ +#define RF5C_MODE_CA21 0x04 +#define RF5C_MODE_CA22 0x08 +#define RF5C_MODE_CA23 0x10 +#define RF5C_MODE_CA24 0x20 +#define RF5C_MODE_CA25 0x40 +#define RF5C_MODE_3STATE_BIT7 0x80 + +/* Flags for RF5C_PWR_CTL */ +#define RF5C_PWR_VCC_3V 0x01 +#define RF5C_PWR_IREQ_HIGH 0x02 +#define RF5C_PWR_INPACK_ENA 0x04 +#define RF5C_PWR_5V_DET 0x08 +#define RF5C_PWR_TC_SEL 0x10 /* Terminal Count: irq 11 or 15 */ +#define RF5C_PWR_DREQ_LOW 0x20 +#define RF5C_PWR_DREQ_OFF 0x00 /* DREQ steering control */ +#define RF5C_PWR_DREQ_INPACK 0x40 +#define RF5C_PWR_DREQ_SPKR 0x80 +#define RF5C_PWR_DREQ_IOIS16 0xc0 + +/* Values for RF5C_CHIP_ID */ +#define RF5C_CHIP_RF5C296 0x32 +#define RF5C_CHIP_RF5C396 0xb2 + +/* Flags for RF5C_MODE_CTL_3 */ +#define RF5C_MCTL3_DISABLE 0x01 /* Disable PCMCIA interface */ +#define RF5C_MCTL3_DMA_ENA 0x02 + +/* Register definitions for Ricoh PCI-to-CardBus bridges */ + +#ifndef PCI_VENDOR_ID_RICOH +#define PCI_VENDOR_ID_RICOH 0x1180 +#endif +#ifndef PCI_DEVICE_ID_RICOH_RL5C465 +#define PCI_DEVICE_ID_RICOH_RL5C465 0x0465 +#endif +#ifndef PCI_DEVICE_ID_RICOH_RL5C466 +#define PCI_DEVICE_ID_RICOH_RL5C466 0x0466 +#endif +#ifndef PCI_DEVICE_ID_RICOH_RL5C475 +#define PCI_DEVICE_ID_RICOH_RL5C475 0x0475 +#endif +#ifndef PCI_DEVICE_ID_RICOH_RL5C476 +#define PCI_DEVICE_ID_RICOH_RL5C476 0x0476 +#endif +#ifndef PCI_DEVICE_ID_RICOH_RL5C477 +#define PCI_DEVICE_ID_RICOH_RL5C477 0x0477 +#endif +#ifndef PCI_DEVICE_ID_RICOH_RL5C478 +#define PCI_DEVICE_ID_RICOH_RL5C478 0x0478 +#endif + +/* Extra bits in CB_BRIDGE_CONTROL */ +#define RL5C46X_BCR_3E0_ENA 0x0800 +#define RL5C46X_BCR_3E2_ENA 0x1000 + +/* Bridge Configuration Register */ +#define RL5C4XX_CONFIG 0x80 /* 16 bit */ +#define RL5C4XX_CONFIG_IO_1_MODE 0x0200 +#define RL5C4XX_CONFIG_IO_0_MODE 0x0100 +#define RL5C4XX_CONFIG_PREFETCH 0x0001 + +/* Misc Control Register */ +#define RL5C4XX_MISC 0x82 /* 16 bit */ +#define RL5C4XX_MISC_HW_SUSPEND_ENA 0x0002 +#define RL5C4XX_MISC_VCCEN_POL 0x0100 +#define RL5C4XX_MISC_VPPEN_POL 0x0200 +#define RL5C46X_MISC_SUSPEND 0x0001 +#define RL5C46X_MISC_PWR_SAVE_2 0x0004 +#define RL5C46X_MISC_IFACE_BUSY 0x0008 +#define RL5C46X_MISC_B_LOCK 0x0010 +#define RL5C46X_MISC_A_LOCK 0x0020 +#define RL5C46X_MISC_PCI_LOCK 0x0040 +#define RL5C47X_MISC_IFACE_BUSY 0x0004 +#define RL5C47X_MISC_PCI_INT_MASK 0x0018 +#define RL5C47X_MISC_PCI_INT_DIS 0x0020 +#define RL5C47X_MISC_SUBSYS_WR 0x0040 +#define RL5C47X_MISC_SRIRQ_ENA 0x0080 +#define RL5C47X_MISC_5V_DISABLE 0x0400 +#define RL5C47X_MISC_LED_POL 0x0800 + +/* 16-bit Interface Control Register */ +#define RL5C4XX_16BIT_CTL 0x84 /* 16 bit */ +#define RL5C4XX_16CTL_IO_TIMING 0x0100 +#define RL5C4XX_16CTL_MEM_TIMING 0x0200 +#define RL5C46X_16CTL_LEVEL_1 0x0010 +#define RL5C46X_16CTL_LEVEL_2 0x0020 + +/* 16-bit IO and memory timing registers */ +#define RL5C4XX_16BIT_IO_0 0x88 /* 16 bit */ +#define RL5C4XX_16BIT_MEM_0 0x8a /* 16 bit */ +#define RL5C4XX_SETUP_MASK 0x0007 +#define RL5C4XX_SETUP_SHIFT 0 +#define RL5C4XX_CMD_MASK 0x01f0 +#define RL5C4XX_CMD_SHIFT 4 +#define RL5C4XX_HOLD_MASK 0x1c00 +#define RL5C4XX_HOLD_SHIFT 10 + +/* Data structure for tracking vendor-specific state */ +typedef struct ricoh_state_t { + u_short config; /* RL5C4XX_CONFIG */ + u_short misc; /* RL5C4XX_MISC */ + u_short ctl; /* RL5C4XX_16BIT_CTL */ + u_short io; /* RL5C4XX_16BIT_IO_0 */ + u_short mem; /* RL5C4XX_16BIT_MEM_0 */ +} ricoh_state_t; + +#define RICOH_PCIC_ID \ + IS_RL5C465, IS_RL5C466, IS_RL5C475, IS_RL5C476, IS_RL5C477, IS_RL5C478 + +#define RICOH_PCIC_INFO \ + { "Ricoh RL5C465", IS_RICOH|IS_CARDBUS, ID(RICOH, RL5C465) }, \ + { "Ricoh RL5C466", IS_RICOH|IS_CARDBUS, ID(RICOH, RL5C466) }, \ + { "Ricoh RL5C475", IS_RICOH|IS_CARDBUS, ID(RICOH, RL5C475) }, \ + { "Ricoh RL5C476", IS_RICOH|IS_CARDBUS, ID(RICOH, RL5C476) }, \ + { "Ricoh RL5C477", IS_RICOH|IS_CARDBUS, ID(RICOH, RL5C477) }, \ + { "Ricoh RL5C478", IS_RICOH|IS_CARDBUS, ID(RICOH, RL5C478) } + +#endif /* _LINUX_RICOH_H */ diff --git a/linux/pcmcia-cs/modules/rsrc_mgr.c b/linux/pcmcia-cs/modules/rsrc_mgr.c new file mode 100644 index 0000000..2becb2f --- /dev/null +++ b/linux/pcmcia-cs/modules/rsrc_mgr.c @@ -0,0 +1,877 @@ +/*====================================================================== + + Resource management routines + + rsrc_mgr.c 1.94 2003/12/12 17:12:53 + + The contents of this file are subject to the Mozilla Public + License Version 1.1 (the "License"); you may not use this file + except in compliance with the License. You may obtain a copy of + the License at http://www.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS + IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + implied. See the License for the specific language governing + rights and limitations under the License. + + The initial developer of the original code is David A. Hinds + <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + + Alternatively, the contents of this file may be used under the + terms of the GNU General Public License version 2 (the "GPL"), in + which case the provisions of the GPL are applicable instead of the + above. If you wish to allow the use of your version of this file + only under the terms of the GPL and not to allow others to use + your version of this file under the MPL, indicate your decision + by deleting the provisions above and replace them with the notice + and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this + file under either the MPL or the GPL. + +======================================================================*/ + +#define __NO_VERSION__ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/ioport.h> +#include <linux/timer.h> +#include <linux/spinlock.h> +#include <asm/irq.h> +#include <asm/io.h> + +#include <pcmcia/cs_types.h> +#include <pcmcia/ss.h> +#include <pcmcia/cs.h> +#include <pcmcia/bulkmem.h> +#include <pcmcia/cistpl.h> +#include "cs_internal.h" + +/*====================================================================*/ + +/* Parameters that can be set with 'insmod' */ + +#define INT_MODULE_PARM(n, v) static int n = v; MODULE_PARM(n, "i") + +INT_MODULE_PARM(probe_mem, 1); /* memory probe? */ +#ifdef CONFIG_ISA +INT_MODULE_PARM(probe_io, 1); /* IO port probe? */ +INT_MODULE_PARM(mem_limit, 0x10000); +#endif + +/*====================================================================== + + The resource_map_t structures are used to track what resources are + available for allocation for PC Card devices. + +======================================================================*/ + +typedef struct resource_map_t { + u_long base, num; + struct resource_map_t *next; +} resource_map_t; + +/* Memory resource database */ +static resource_map_t mem_db = { 0, 0, &mem_db }; + +/* IO port resource database */ +static resource_map_t io_db = { 0, 0, &io_db }; + +#ifdef CONFIG_ISA + +typedef struct irq_info_t { + u_int Attributes; + int time_share, dyn_share; + struct socket_info_t *Socket; +} irq_info_t; + +/* Table of ISA IRQ assignments */ +static irq_info_t irq_table[16] = { { 0, 0, 0 }, /* etc */ }; + +#endif + +/*====================================================================== + + Linux resource management extensions + +======================================================================*/ + +#ifndef CONFIG_PNP_BIOS +#define check_io_region(b,n) (0) +#endif + +#if defined(CONFIG_PNP_BIOS) || !defined(HAVE_MEMRESERVE) + +#ifdef USE_SPIN_LOCKS +static spinlock_t rsrc_lock = SPIN_LOCK_UNLOCKED; +#endif + +typedef struct resource_entry_t { + u_long base, num; + char *name; + struct resource_entry_t *next; +} resource_entry_t; + +/* Ordered linked lists of allocated IO and memory blocks */ +#ifdef CONFIG_PNP_BIOS +static resource_entry_t io_list = { 0, 0, NULL, NULL }; +#endif +#ifndef HAVE_MEMRESERVE +static resource_entry_t mem_list = { 0, 0, NULL, NULL }; +#endif + +static resource_entry_t *find_gap(resource_entry_t *root, + resource_entry_t *entry) +{ + resource_entry_t *p; + + if (entry->base > entry->base+entry->num-1) + return NULL; + for (p = root; ; p = p->next) { + if ((p != root) && (p->base+p->num-1 >= entry->base)) { + p = NULL; + break; + } + if ((p->next == NULL) || + (p->next->base > entry->base+entry->num-1)) + break; + } + return p; +} + +static int register_my_resource(resource_entry_t *list, + u_long base, u_long num, char *name) +{ + u_long flags; + resource_entry_t *p, *entry; + + entry = kmalloc(sizeof(resource_entry_t), GFP_ATOMIC); + if (!entry) return -ENOMEM; + entry->base = base; + entry->num = num; + entry->name = name; + + spin_lock_irqsave(&rsrc_lock, flags); + p = find_gap(list, entry); + if (p == NULL) { + spin_unlock_irqrestore(&rsrc_lock, flags); + kfree(entry); + return -EBUSY; + } + entry->next = p->next; + p->next = entry; + spin_unlock_irqrestore(&rsrc_lock, flags); + return 0; +} + +static void release_my_resource(resource_entry_t *list, + u_long base, u_long num) +{ + u_long flags; + resource_entry_t *p, *q; + + spin_lock_irqsave(&rsrc_lock, flags); + for (p = list; ; p = q) { + q = p->next; + if (q == NULL) break; + if ((q->base == base) && (q->num == num)) { + p->next = q->next; + kfree(q); + spin_unlock_irqrestore(&rsrc_lock, flags); + return; + } + } + spin_unlock_irqrestore(&rsrc_lock, flags); + return; +} + +static int check_my_resource(resource_entry_t *list, + u_long base, u_long num) +{ + if (register_my_resource(list, base, num, NULL) != 0) + return -EBUSY; + release_my_resource(list, base, num); + return 0; +} + +#ifdef CONFIG_PNP_BIOS +int check_io_region(u_long base, u_long num) +{ + return check_my_resource(&io_list, base, num); +} +void request_io_region(u_long base, u_long num, char *name) +{ + register_my_resource(&io_list, base, num, name); +} +void release_io_region(u_long base, u_long num) +{ + release_my_resource(&io_list, base, num); +} +#ifdef HAS_PROC_BUS +int proc_read_io(char *buf, char **start, off_t pos, + int count, int *eof, void *data) +{ + resource_entry_t *r; + u_long flags; + char *p = buf; + + spin_lock_irqsave(&rsrc_lock, flags); + for (r = io_list.next; r; r = r->next) + p += sprintf(p, "%04lx-%04lx : %s\n", r->base, + r->base+r->num-1, r->name); + spin_unlock_irqrestore(&rsrc_lock, flags); + return (p - buf); +} +#endif +#endif + +#ifndef HAVE_MEMRESERVE +int check_mem_region(u_long base, u_long num) +{ + return check_my_resource(&mem_list, base, num); +} +void request_mem_region(u_long base, u_long num, char *name) +{ + register_my_resource(&mem_list, base, num, name); +} +void release_mem_region(u_long base, u_long num) +{ + release_my_resource(&mem_list, base, num); +} +#ifdef HAS_PROC_BUS +int proc_read_mem(char *buf, char **start, off_t pos, + int count, int *eof, void *data) +{ + resource_entry_t *r; + u_long flags; + char *p = buf; + + spin_lock_irqsave(&rsrc_lock, flags); + for (r = mem_list.next; r; r = r->next) + p += sprintf(p, "%08lx-%08lx : %s\n", r->base, + r->base+r->num-1, r->name); + spin_unlock_irqrestore(&rsrc_lock, flags); + return (p - buf); +} +#endif +#endif + +#endif /* defined(CONFIG_PNP_BIOS) || !defined(HAVE_MEMRESERVE) */ + +/*====================================================================== + + These manage the internal databases of available resources. + +======================================================================*/ + +static int add_interval(resource_map_t *map, u_long base, u_long num) +{ + resource_map_t *p, *q; + + for (p = map; ; p = p->next) { + if ((p != map) && (p->base+p->num-1 >= base)) + return -1; + if ((p->next == map) || (p->next->base > base+num-1)) + break; + } + q = kmalloc(sizeof(resource_map_t), GFP_KERNEL); + if (!q) return CS_OUT_OF_RESOURCE; + q->base = base; q->num = num; + q->next = p->next; p->next = q; + return CS_SUCCESS; +} + +/*====================================================================*/ + +static int sub_interval(resource_map_t *map, u_long base, u_long num) +{ + resource_map_t *p, *q; + + for (p = map; ; p = q) { + q = p->next; + if (q == map) + break; + if ((q->base+q->num > base) && (base+num > q->base)) { + if (q->base >= base) { + if (q->base+q->num <= base+num) { + /* Delete whole block */ + p->next = q->next; + kfree(q); + /* don't advance the pointer yet */ + q = p; + } else { + /* Cut off bit from the front */ + q->num = q->base + q->num - base - num; + q->base = base + num; + } + } else if (q->base+q->num <= base+num) { + /* Cut off bit from the end */ + q->num = base - q->base; + } else { + /* Split the block into two pieces */ + p = kmalloc(sizeof(resource_map_t), GFP_KERNEL); + if (!p) return CS_OUT_OF_RESOURCE; + p->base = base+num; + p->num = q->base+q->num - p->base; + q->num = base - q->base; + p->next = q->next ; q->next = p; + } + } + } + return CS_SUCCESS; +} + +/*====================================================================== + + These routines examine a region of IO or memory addresses to + determine what ranges might be genuinely available. + +======================================================================*/ + +#ifdef CONFIG_ISA +static void do_io_probe(ioaddr_t base, ioaddr_t num) +{ + + ioaddr_t i, j, bad, any; + u_char *b, hole, most; + + printk(KERN_INFO "cs: IO port probe 0x%04x-0x%04x:", + base, base+num-1); + + /* First, what does a floating port look like? */ + b = kmalloc(256, GFP_KERNEL); + if (!b) { + printk(KERN_INFO " kmalloc failed!\n"); + return; + } + memset(b, 0, 256); + for (i = base, most = 0; i < base+num; i += 8) { + if (check_region(i, 8) || check_io_region(i, 8)) + continue; + hole = inb(i); + for (j = 1; j < 8; j++) + if (inb(i+j) != hole) break; + if ((j == 8) && (++b[hole] > b[most])) + most = hole; + if (b[most] == 127) break; + } + kfree(b); + + bad = any = 0; + for (i = base; i < base+num; i += 8) { + if (check_region(i, 8) || check_io_region(i, 8)) + continue; + for (j = 0; j < 8; j++) + if (inb(i+j) != most) break; + if (j < 8) { + if (!any) + printk(" excluding"); + if (!bad) + bad = any = i; + } else { + if (bad) { + sub_interval(&io_db, bad, i-bad); + printk(" %#04x-%#04x", bad, i-1); + bad = 0; + } + } + } + if (bad) { + if ((num > 16) && (bad == base) && (i == base+num)) { + printk(" nothing: probe failed.\n"); + return; + } else { + sub_interval(&io_db, bad, i-bad); + printk(" %#04x-%#04x", bad, i-1); + } + } + + printk(any ? "\n" : " clean.\n"); +} + +static int io_scan; /* = 0 */ + +static void invalidate_io(void) +{ + io_scan = 0; +} + +static void validate_io(void) +{ + resource_map_t *m, *n; + if (!probe_io || io_scan++) + return; + for (m = io_db.next; m != &io_db; m = n) { + n = m->next; + do_io_probe(m->base, m->num); + } +} + +#else /* CONFIG_ISA */ + +#define validate_io() do { } while (0) +#define invalidate_io() do { } while (0) + +#endif /* CONFIG_ISA */ + +/*====================================================================== + + The memory probe. If the memory list includes a 64K-aligned block + below 1MB, we probe in 64K chunks, and as soon as we accumulate at + least mem_limit free space, we quit. + +======================================================================*/ + +static int do_mem_probe(u_long base, u_long num, + int (*is_valid)(u_long), int (*do_cksum)(u_long)) +{ + u_long i, j, bad, fail, step; + + printk(KERN_INFO "cs: memory probe 0x%06lx-0x%06lx:", + base, base+num-1); + bad = fail = 0; + step = (num < 0x20000) ? 0x2000 : ((num>>4) & ~0x1fff); + for (i = j = base; i < base+num; i = j + step) { + if (!fail) { + for (j = i; j < base+num; j += step) + if ((check_mem_region(j, step) == 0) && is_valid(j)) + break; + fail = ((i == base) && (j == base+num)); + } + if (fail) { + for (j = i; j < base+num; j += 2*step) + if ((check_mem_region(j, 2*step) == 0) && + do_cksum(j) && do_cksum(j+step)) + break; + } + if (i != j) { + if (!bad) printk(" excluding"); + printk(" %#05lx-%#05lx", i, j-1); + sub_interval(&mem_db, i, j-i); + bad += j-i; + } + } + printk(bad ? "\n" : " clean.\n"); + return (num - bad); +} + +#ifdef CONFIG_ISA + +static u_long inv_probe(int (*is_valid)(u_long), + int (*do_cksum)(u_long), + resource_map_t *m) +{ + u_long ok; + if (m == &mem_db) + return 0; + ok = inv_probe(is_valid, do_cksum, m->next); + if (ok) { + if (m->base >= 0x100000) + sub_interval(&mem_db, m->base, m->num); + return ok; + } + if (m->base < 0x100000) + return 0; + return do_mem_probe(m->base, m->num, is_valid, do_cksum); +} + +static int hi_scan, lo_scan; /* = 0 */ + +static void invalidate_mem(void) +{ + hi_scan = lo_scan = 0; +} + +void validate_mem(int (*is_valid)(u_long), int (*do_cksum)(u_long), + int force_low) +{ + resource_map_t *m, mm; + static u_char order[] = { 0xd0, 0xe0, 0xc0, 0xf0 }; + u_long b, i, ok = 0; + + if (!probe_mem) return; + /* We do up to four passes through the list */ + if (!force_low) { + if (hi_scan++ || (inv_probe(is_valid, do_cksum, mem_db.next) > 0)) + return; + printk(KERN_NOTICE "cs: warning: no high memory space " + "available!\n"); + } + if (lo_scan++) return; + for (m = mem_db.next; m != &mem_db; m = mm.next) { + mm = *m; + /* Only probe < 1 MB */ + if (mm.base >= 0x100000) continue; + if ((mm.base | mm.num) & 0xffff) { + ok += do_mem_probe(mm.base, mm.num, is_valid, do_cksum); + continue; + } + /* Special probe for 64K-aligned block */ + for (i = 0; i < 4; i++) { + b = order[i] << 12; + if ((b >= mm.base) && (b+0x10000 <= mm.base+mm.num)) { + if (ok >= mem_limit) + sub_interval(&mem_db, b, 0x10000); + else + ok += do_mem_probe(b, 0x10000, is_valid, do_cksum); + } + } + } +} + +#else /* CONFIG_ISA */ + +#define invalidate_mem() do { } while (0) + +void validate_mem(int (*is_valid)(u_long), int (*do_cksum)(u_long), + int force_low) +{ + resource_map_t *m, *n; + static int done = 0; + + if (!probe_mem || done++) + return; + for (m = mem_db.next; m != &mem_db; m = n) + n = m->next; + if (do_mem_probe(m->base, m->num, is_valid, do_cksum)) + return; +} + +#endif /* CONFIG_ISA */ + +/*====================================================================== + + These find ranges of I/O ports or memory addresses that are not + currently allocated by other devices. + + The 'align' field should reflect the number of bits of address + that need to be preserved from the initial value of *base. It + should be a power of two, greater than or equal to 'num'. A value + of 0 means that all bits of *base are significant. *base should + also be strictly less than 'align'. + +======================================================================*/ + +int find_io_region(ioaddr_t *base, ioaddr_t num, ioaddr_t align, + char *name) +{ + ioaddr_t try; + resource_map_t *m; + + validate_io(); + for (m = io_db.next; m != &io_db; m = m->next) { + try = (m->base & ~(align-1)) + *base; + for (try = (try >= m->base) ? try : try+align; + (try >= m->base) && (try+num <= m->base+m->num); + try += align) { + if ((check_region(try, num) == 0) && + (check_io_region(try, num) == 0)) { + *base = try; + if (name) request_region(try, num, name); + return 0; + } + if (!align) break; + } + } + return -1; +} + +int find_mem_region(u_long *base, u_long num, u_long align, + int force_low, char *name) +{ + u_long try; + resource_map_t *m; + + while (1) { + for (m = mem_db.next; m != &mem_db; m = m->next) { + /* first pass >1MB, second pass <1MB */ + if ((force_low != 0) ^ (m->base < 0x100000)) continue; + try = (m->base & ~(align-1)) + *base; + for (try = (try >= m->base) ? try : try+align; + (try >= m->base) && (try+num <= m->base+m->num); + try += align) { + if (check_mem_region(try, num) == 0) { + if (name) request_mem_region(try, num, name); + *base = try; + return 0; + } + if (!align) break; + } + } + if (force_low) break; + force_low++; + } + return -1; +} + +/*====================================================================== + + This checks to see if an interrupt is available, with support + for interrupt sharing. We don't support reserving interrupts + yet. If the interrupt is available, we allocate it. + +======================================================================*/ + +#ifdef CONFIG_ISA + +static void fake_irq(int i, void *d, struct pt_regs *r) { } +static inline int check_irq(int irq) +{ + if (request_irq(irq, fake_irq, 0, "bogus", NULL) != 0) + return -1; + free_irq(irq, NULL); + return 0; +} + +int try_irq(u_int Attributes, int irq, int specific) +{ + irq_info_t *info = &irq_table[irq]; + if (info->Attributes & RES_ALLOCATED) { + switch (Attributes & IRQ_TYPE) { + case IRQ_TYPE_EXCLUSIVE: + return CS_IN_USE; + case IRQ_TYPE_TIME: + if ((info->Attributes & RES_IRQ_TYPE) + != RES_IRQ_TYPE_TIME) + return CS_IN_USE; + if (Attributes & IRQ_FIRST_SHARED) + return CS_BAD_ATTRIBUTE; + info->Attributes |= RES_IRQ_TYPE_TIME | RES_ALLOCATED; + info->time_share++; + break; + case IRQ_TYPE_DYNAMIC_SHARING: + if ((info->Attributes & RES_IRQ_TYPE) + != RES_IRQ_TYPE_DYNAMIC) + return CS_IN_USE; + if (Attributes & IRQ_FIRST_SHARED) + return CS_BAD_ATTRIBUTE; + info->Attributes |= RES_IRQ_TYPE_DYNAMIC | RES_ALLOCATED; + info->dyn_share++; + break; + } + } else { + if ((info->Attributes & RES_RESERVED) && !specific) + return CS_IN_USE; + if (check_irq(irq) != 0) + return CS_IN_USE; + switch (Attributes & IRQ_TYPE) { + case IRQ_TYPE_EXCLUSIVE: + info->Attributes |= RES_ALLOCATED; + break; + case IRQ_TYPE_TIME: + if (!(Attributes & IRQ_FIRST_SHARED)) + return CS_BAD_ATTRIBUTE; + info->Attributes |= RES_IRQ_TYPE_TIME | RES_ALLOCATED; + info->time_share = 1; + break; + case IRQ_TYPE_DYNAMIC_SHARING: + if (!(Attributes & IRQ_FIRST_SHARED)) + return CS_BAD_ATTRIBUTE; + info->Attributes |= RES_IRQ_TYPE_DYNAMIC | RES_ALLOCATED; + info->dyn_share = 1; + break; + } + } + return 0; +} + +#endif + +/*====================================================================*/ + +#ifdef CONFIG_ISA + +void undo_irq(u_int Attributes, int irq) +{ + irq_info_t *info; + + info = &irq_table[irq]; + switch (Attributes & IRQ_TYPE) { + case IRQ_TYPE_EXCLUSIVE: + info->Attributes &= RES_RESERVED; + break; + case IRQ_TYPE_TIME: + info->time_share--; + if (info->time_share == 0) + info->Attributes &= RES_RESERVED; + break; + case IRQ_TYPE_DYNAMIC_SHARING: + info->dyn_share--; + if (info->dyn_share == 0) + info->Attributes &= RES_RESERVED; + break; + } +} + +#endif + +/*====================================================================== + + The various adjust_* calls form the external interface to the + resource database. + +======================================================================*/ + +static int adjust_memory(adjust_t *adj) +{ + u_long base, num; + int i, ret; + + base = adj->resource.memory.Base; + num = adj->resource.memory.Size; + if ((num == 0) || (base+num-1 < base)) + return CS_BAD_SIZE; + + ret = CS_SUCCESS; + switch (adj->Action) { + case ADD_MANAGED_RESOURCE: + ret = add_interval(&mem_db, base, num); + break; + case REMOVE_MANAGED_RESOURCE: + ret = sub_interval(&mem_db, base, num); + if (ret == CS_SUCCESS) { + invalidate_mem(); + for (i = 0; i < sockets; i++) { + release_cis_mem(socket_table[i]); +#ifdef CONFIG_CARDBUS + cb_release_cis_mem(socket_table[i]); +#endif + } + } + break; + default: + ret = CS_UNSUPPORTED_FUNCTION; + } + + return ret; +} + +/*====================================================================*/ + +static int adjust_io(adjust_t *adj) +{ + int base, num; + + base = adj->resource.io.BasePort; + num = adj->resource.io.NumPorts; + if ((base < 0) || (base > 0xffff)) + return CS_BAD_BASE; + if ((num <= 0) || (base+num > 0x10000) || (base+num <= base)) + return CS_BAD_SIZE; + + switch (adj->Action) { + case ADD_MANAGED_RESOURCE: + if (add_interval(&io_db, base, num) != 0) + return CS_IN_USE; + break; + case REMOVE_MANAGED_RESOURCE: + sub_interval(&io_db, base, num); + invalidate_io(); + break; + default: + return CS_UNSUPPORTED_FUNCTION; + break; + } + + return CS_SUCCESS; +} + +/*====================================================================*/ + +static int adjust_irq(adjust_t *adj) +{ +#ifdef CONFIG_ISA + int irq; + irq_info_t *info; + + irq = adj->resource.irq.IRQ; + if ((irq < 0) || (irq > 15)) + return CS_BAD_IRQ; + info = &irq_table[irq]; + + switch (adj->Action) { + case ADD_MANAGED_RESOURCE: + if (info->Attributes & RES_REMOVED) + info->Attributes &= ~(RES_REMOVED|RES_ALLOCATED); + else + if (adj->Attributes & RES_ALLOCATED) + return CS_IN_USE; + if (adj->Attributes & RES_RESERVED) + info->Attributes |= RES_RESERVED; + else + info->Attributes &= ~RES_RESERVED; + break; + case REMOVE_MANAGED_RESOURCE: + if (info->Attributes & RES_REMOVED) + return 0; + if (info->Attributes & RES_ALLOCATED) + return CS_IN_USE; + info->Attributes |= RES_ALLOCATED|RES_REMOVED; + info->Attributes &= ~RES_RESERVED; + break; + default: + return CS_UNSUPPORTED_FUNCTION; + break; + } +#endif + return CS_SUCCESS; +} + +/*====================================================================*/ + +int adjust_resource_info(client_handle_t handle, adjust_t *adj) +{ + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + + switch (adj->Resource) { + case RES_MEMORY_RANGE: + return adjust_memory(adj); + break; + case RES_IO_RANGE: + return adjust_io(adj); + break; + case RES_IRQ: + return adjust_irq(adj); + break; + } + return CS_UNSUPPORTED_FUNCTION; +} + +/*====================================================================*/ + +void release_resource_db(void) +{ + resource_map_t *p, *q; +#if defined(CONFIG_PNP_BIOS) || !defined(HAVE_MEMRESERVE) + resource_entry_t *u, *v; +#endif + + for (p = mem_db.next; p != &mem_db; p = q) { + q = p->next; + kfree(p); + } + for (p = io_db.next; p != &io_db; p = q) { + q = p->next; + kfree(p); + } +#ifdef CONFIG_PNP_BIOS + for (u = io_list.next; u; u = v) { + v = u->next; + kfree(u); + } +#endif +#ifndef HAVE_MEMRESERVE + for (u = mem_list.next; u; u = v) { + v = u->next; + kfree(u); + } +#endif +} diff --git a/linux/pcmcia-cs/modules/smc34c90.h b/linux/pcmcia-cs/modules/smc34c90.h new file mode 100644 index 0000000..0f3ddc0 --- /dev/null +++ b/linux/pcmcia-cs/modules/smc34c90.h @@ -0,0 +1,58 @@ +/* + * smc34c90.h 1.10 2001/08/24 12:15:34 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in + * which case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use + * your version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + */ + +#ifndef _LINUX_SMC34C90_H +#define _LINUX_SMC34C90_H + +#ifndef PCI_VENDOR_ID_SMC +#define PCI_VENDOR_ID_SMC 0x10b3 +#endif + +#ifndef PCI_DEVICE_ID_SMC_34C90 +#define PCI_DEVICE_ID_SMC_34C90 0xb106 +#endif + +/* Register definitions for SMC 34C90 PCI-to-CardBus bridge */ + +/* EEPROM Information Register */ +#define SMC34C90_EEINFO 0x0088 +#define SMC34C90_EEINFO_ONE_SOCKET 0x0001 +#define SMC34C90_EEINFO_5V_ONLY 0x0002 +#define SMC34C90_EEINFO_ISA_IRQ 0x0004 +#define SMC34C90_EEINFO_ZV_PORT 0x0008 +#define SMC34C90_EEINFO_RING 0x0010 +#define SMC34C90_EEINFO_LED 0x0020 + +#define SMC_PCIC_ID \ + IS_SMC34C90 + +#define SMC_PCIC_INFO \ + { "SMC 34C90", IS_CARDBUS, ID(SMC, 34C90) } + +#endif /* _LINUX_SMC34C90_H */ diff --git a/linux/pcmcia-cs/modules/ti113x.h b/linux/pcmcia-cs/modules/ti113x.h new file mode 100644 index 0000000..c224d7a --- /dev/null +++ b/linux/pcmcia-cs/modules/ti113x.h @@ -0,0 +1,264 @@ +/* + * ti113x.h 1.32 2003/02/13 06:28:09 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in + * which case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use + * your version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + */ + +#ifndef _LINUX_TI113X_H +#define _LINUX_TI113X_H + +#ifndef PCI_VENDOR_ID_TI +#define PCI_VENDOR_ID_TI 0x104c +#endif + +#ifndef PCI_DEVICE_ID_TI_1130 +#define PCI_DEVICE_ID_TI_1130 0xac12 +#endif +#ifndef PCI_DEVICE_ID_TI_1031 +#define PCI_DEVICE_ID_TI_1031 0xac13 +#endif +#ifndef PCI_DEVICE_ID_TI_1131 +#define PCI_DEVICE_ID_TI_1131 0xac15 +#endif +#ifndef PCI_DEVICE_ID_TI_1210 +#define PCI_DEVICE_ID_TI_1210 0xac1a +#endif +#ifndef PCI_DEVICE_ID_TI_1211 +#define PCI_DEVICE_ID_TI_1211 0xac1e +#endif +#ifndef PCI_DEVICE_ID_TI_1220 +#define PCI_DEVICE_ID_TI_1220 0xac17 +#endif +#ifndef PCI_DEVICE_ID_TI_1221 +#define PCI_DEVICE_ID_TI_1221 0xac19 +#endif +#ifndef PCI_DEVICE_ID_TI_1250A +#define PCI_DEVICE_ID_TI_1250A 0xac16 +#endif +#ifndef PCI_DEVICE_ID_TI_1225 +#define PCI_DEVICE_ID_TI_1225 0xac1c +#endif +#ifndef PCI_DEVICE_ID_TI_1251A +#define PCI_DEVICE_ID_TI_1251A 0xac1d +#endif +#ifndef PCI_DEVICE_ID_TI_1251B +#define PCI_DEVICE_ID_TI_1251B 0xac1f +#endif +#ifndef PCI_DEVICE_ID_TI_1410 +#define PCI_DEVICE_ID_TI_1410 0xac50 +#endif +#ifndef PCI_DEVICE_ID_TI_1420 +#define PCI_DEVICE_ID_TI_1420 0xac51 +#endif +#ifndef PCI_DEVICE_ID_TI_1450 +#define PCI_DEVICE_ID_TI_1450 0xac1b +#endif +#ifndef PCI_DEVICE_ID_TI_1451 +#define PCI_DEVICE_ID_TI_1451 0xac52 +#endif +#ifndef PCI_DEVICE_ID_TI_1510 +#define PCI_DEVICE_ID_TI_1510 0xac56 +#endif +#ifndef PCI_DEVICE_ID_TI_1520 +#define PCI_DEVICE_ID_TI_1520 0xac55 +#endif +#ifndef PCI_DEVICE_ID_TI_1620 +#define PCI_DEVICE_ID_TI_1620 0xac54 +#endif +#ifndef PCI_DEVICE_ID_TI_4410 +#define PCI_DEVICE_ID_TI_4410 0xac41 +#endif +#ifndef PCI_DEVICE_ID_TI_4450 +#define PCI_DEVICE_ID_TI_4450 0xac40 +#endif +#ifndef PCI_DEVICE_ID_TI_4451 +#define PCI_DEVICE_ID_TI_4451 0xac42 +#endif +#ifndef PCI_DEVICE_ID_TI_4510 +#define PCI_DEVICE_ID_TI_4510 0xac44 +#endif +#ifndef PCI_DEVICE_ID_TI_4520 +#define PCI_DEVICE_ID_TI_4520 0xac46 +#endif +#ifndef PCI_DEVICE_ID_TI_7410 +#define PCI_DEVICE_ID_TI_7410 0xac49 +#endif +#ifndef PCI_DEVICE_ID_TI_7510 +#define PCI_DEVICE_ID_TI_7510 0xac47 +#endif +#ifndef PCI_DEVICE_ID_TI_7610 +#define PCI_DEVICE_ID_TI_7610 0xac48 +#endif + +/* Register definitions for TI 113X PCI-to-CardBus bridges */ + +/* System Control Register */ +#define TI113X_SYSTEM_CONTROL 0x80 /* 32 bit */ +#define TI113X_SCR_SMIROUTE 0x04000000 +#define TI113X_SCR_SMISTATUS 0x02000000 +#define TI113X_SCR_SMIENB 0x01000000 +#define TI113X_SCR_VCCPROT 0x00200000 +#define TI113X_SCR_REDUCEZV 0x00100000 +#define TI113X_SCR_CDREQEN 0x00080000 +#define TI113X_SCR_CDMACHAN 0x00070000 +#define TI113X_SCR_SOCACTIVE 0x00002000 +#define TI113X_SCR_PWRSTREAM 0x00000800 +#define TI113X_SCR_DELAYUP 0x00000400 +#define TI113X_SCR_DELAYDOWN 0x00000200 +#define TI113X_SCR_INTERROGATE 0x00000100 +#define TI113X_SCR_CLKRUN_SEL 0x00000080 +#define TI113X_SCR_PWRSAVINGS 0x00000040 +#define TI113X_SCR_SUBSYSRW 0x00000020 +#define TI113X_SCR_CB_DPAR 0x00000010 +#define TI113X_SCR_CDMA_EN 0x00000008 +#define TI113X_SCR_ASYNC_IRQ 0x00000004 +#define TI113X_SCR_KEEPCLK 0x00000002 +#define TI113X_SCR_CLKRUN_ENA 0x00000001 + +#define TI122X_SCR_SER_STEP 0xc0000000 +#define TI122X_SCR_INTRTIE 0x20000000 +#define TI122X_SCR_P2CCLK 0x08000000 +#define TI122X_SCR_CBRSVD 0x00400000 +#define TI122X_SCR_MRBURSTDN 0x00008000 +#define TI122X_SCR_MRBURSTUP 0x00004000 +#define TI122X_SCR_RIMUX 0x00000001 + +/* Multimedia Control Register */ +#define TI1250_MULTIMEDIA_CTL 0x84 /* 8 bit */ +#define TI1250_MMC_ZVOUTEN 0x80 +#define TI1250_MMC_PORTSEL 0x40 +#define TI1250_MMC_ZVEN1 0x02 +#define TI1250_MMC_ZVEN0 0x01 + +#define TI1250_GENERAL_STATUS 0x85 /* 8 bit */ +#define TI1250_GPIO0_CONTROL 0x88 /* 8 bit */ +#define TI1250_GPIO1_CONTROL 0x89 /* 8 bit */ +#define TI1250_GPIO2_CONTROL 0x8a /* 8 bit */ +#define TI1250_GPIO3_CONTROL 0x8b /* 8 bit */ +#define TI12XX_IRQMUX 0x8c /* 32 bit */ + +/* Retry Status Register */ +#define TI113X_RETRY_STATUS 0x90 /* 8 bit */ +#define TI113X_RSR_PCIRETRY 0x80 +#define TI113X_RSR_CBRETRY 0x40 +#define TI113X_RSR_TEXP_CBB 0x20 +#define TI113X_RSR_MEXP_CBB 0x10 +#define TI113X_RSR_TEXP_CBA 0x08 +#define TI113X_RSR_MEXP_CBA 0x04 +#define TI113X_RSR_TEXP_PCI 0x02 +#define TI113X_RSR_MEXP_PCI 0x01 + +/* Card Control Register */ +#define TI113X_CARD_CONTROL 0x91 /* 8 bit */ +#define TI113X_CCR_RIENB 0x80 +#define TI113X_CCR_ZVENABLE 0x40 +#define TI113X_CCR_PCI_IRQ_ENA 0x20 +#define TI113X_CCR_PCI_IREQ 0x10 +#define TI113X_CCR_PCI_CSC 0x08 +#define TI113X_CCR_SPKROUTEN 0x02 +#define TI113X_CCR_IFG 0x01 + +#define TI1220_CCR_PORT_SEL 0x20 +#define TI122X_CCR_AUD2MUX 0x04 + +/* Device Control Register */ +#define TI113X_DEVICE_CONTROL 0x92 /* 8 bit */ +#define TI113X_DCR_5V_FORCE 0x40 +#define TI113X_DCR_3V_FORCE 0x20 +#define TI113X_DCR_IMODE_MASK 0x06 +#define TI113X_DCR_IMODE_ISA 0x02 +#define TI113X_DCR_IMODE_SERIAL 0x04 + +#define TI12XX_DCR_IMODE_PCI_ONLY 0x00 +#define TI12XX_DCR_IMODE_ALL_SERIAL 0x06 + +/* Buffer Control Register */ +#define TI113X_BUFFER_CONTROL 0x93 /* 8 bit */ +#define TI113X_BCR_CB_READ_DEPTH 0x08 +#define TI113X_BCR_CB_WRITE_DEPTH 0x04 +#define TI113X_BCR_PCI_READ_DEPTH 0x02 +#define TI113X_BCR_PCI_WRITE_DEPTH 0x01 + +/* Diagnostic Register */ +#define TI1250_DIAGNOSTIC 0x93 /* 8 bit */ +#define TI1250_DIAG_TRUE_VALUE 0x80 +#define TI1250_DIAG_PCI_IREQ 0x40 +#define TI1250_DIAG_PCI_CSC 0x20 +#define TI1250_DIAG_ASYNC_CSC 0x01 + +/* DMA Registers */ +#define TI113X_DMA_0 0x94 /* 32 bit */ +#define TI113X_DMA_1 0x98 /* 32 bit */ + +/* ExCA IO offset registers */ +#define TI113X_IO_OFFSET(map) (0x36+((map)<<1)) + +/* Data structure for tracking vendor-specific state */ +typedef struct ti113x_state_t { + u32 sysctl; /* TI113X_SYSTEM_CONTROL */ + u8 cardctl; /* TI113X_CARD_CONTROL */ + u8 devctl; /* TI113X_DEVICE_CONTROL */ + u8 diag; /* TI1250_DIAGNOSTIC */ + u32 irqmux; /* TI12XX_IRQMUX */ +} ti113x_state_t; + +#define TI_PCIC_ID \ + IS_TI1130, IS_TI1131, IS_TI1031, IS_TI1210, IS_TI1211, \ + IS_TI1220, IS_TI1221, IS_TI1225, IS_TI1250A, IS_TI1251A, \ + IS_TI1251B, IS_TI1410, IS_TI1420, IS_TI1450, IS_TI1451, \ + IS_TI1510, IS_TI1520, IS_TI1620, IS_TI4410, IS_TI4450, \ + IS_TI4451, IS_TI4510, IS_TI4520, IS_TI7410, IS_TI7510, \ + IS_TI7610 + +#define TI_PCIC_INFO \ + { "TI 1130", IS_TI|IS_CARDBUS, ID(TI, 1130) }, \ + { "TI 1131", IS_TI|IS_CARDBUS, ID(TI, 1131) }, \ + { "TI 1031", IS_TI|IS_CARDBUS, ID(TI, 1031) }, \ + { "TI 1210", IS_TI|IS_CARDBUS, ID(TI, 1210) }, \ + { "TI 1211", IS_TI|IS_CARDBUS, ID(TI, 1211) }, \ + { "TI 1220", IS_TI|IS_CARDBUS, ID(TI, 1220) }, \ + { "TI 1221", IS_TI|IS_CARDBUS, ID(TI, 1221) }, \ + { "TI 1225", IS_TI|IS_CARDBUS, ID(TI, 1225) }, \ + { "TI 1250A", IS_TI|IS_CARDBUS, ID(TI, 1250A) }, \ + { "TI 1251A", IS_TI|IS_CARDBUS, ID(TI, 1251A) }, \ + { "TI 1251B", IS_TI|IS_CARDBUS, ID(TI, 1251B) }, \ + { "TI 1410", IS_TI|IS_CARDBUS, ID(TI, 1410) }, \ + { "TI 1420", IS_TI|IS_CARDBUS, ID(TI, 1420) }, \ + { "TI 1450", IS_TI|IS_CARDBUS, ID(TI, 1450) }, \ + { "TI 1451", IS_TI|IS_CARDBUS, ID(TI, 1451) }, \ + { "TI 1510", IS_TI|IS_CARDBUS, ID(TI, 1510) }, \ + { "TI 1520", IS_TI|IS_CARDBUS, ID(TI, 1520) }, \ + { "TI 1620", IS_TI|IS_CARDBUS, ID(TI, 1620) }, \ + { "TI 4410", IS_TI|IS_CARDBUS, ID(TI, 4410) }, \ + { "TI 4450", IS_TI|IS_CARDBUS, ID(TI, 4450) }, \ + { "TI 4451", IS_TI|IS_CARDBUS, ID(TI, 4451) }, \ + { "TI 4510", IS_TI|IS_CARDBUS, ID(TI, 4510) }, \ + { "TI 4520", IS_TI|IS_CARDBUS, ID(TI, 4520) }, \ + { "TI 7410", IS_TI|IS_CARDBUS, ID(TI, 7410) }, \ + { "TI 7510", IS_TI|IS_CARDBUS, ID(TI, 7510) }, \ + { "TI 7610", IS_TI|IS_CARDBUS, ID(TI, 7610) } + +#endif /* _LINUX_TI113X_H */ diff --git a/linux/pcmcia-cs/modules/topic.h b/linux/pcmcia-cs/modules/topic.h new file mode 100644 index 0000000..88662c4 --- /dev/null +++ b/linux/pcmcia-cs/modules/topic.h @@ -0,0 +1,123 @@ +/* + * topic.h 1.15 2002/02/27 01:21:09 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in + * which case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use + * your version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + * topic.h $Release$ 2002/02/27 01:21:09 + */ + +#ifndef _LINUX_TOPIC_H +#define _LINUX_TOPIC_H + +#ifndef PCI_VENDOR_ID_TOSHIBA +#define PCI_VENDOR_ID_TOSHIBA 0x1179 +#endif +#ifndef PCI_DEVICE_ID_TOSHIBA_TOPIC95_A +#define PCI_DEVICE_ID_TOSHIBA_TOPIC95_A 0x0603 +#endif +#ifndef PCI_DEVICE_ID_TOSHIBA_TOPIC95_B +#define PCI_DEVICE_ID_TOSHIBA_TOPIC95_B 0x060a +#endif +#ifndef PCI_DEVICE_ID_TOSHIBA_TOPIC97 +#define PCI_DEVICE_ID_TOSHIBA_TOPIC97 0x060f +#endif +#ifndef PCI_DEVICE_ID_TOSHIBA_TOPIC100 +#define PCI_DEVICE_ID_TOSHIBA_TOPIC100 0x0617 +#endif + +/* Register definitions for Toshiba ToPIC95 controllers */ + +#define TOPIC_SOCKET_CONTROL 0x0090 /* 32 bit */ +#define TOPIC_SCR_IRQSEL 0x00000001 + +#define TOPIC_SLOT_CONTROL 0x00a0 /* 8 bit */ +#define TOPIC_SLOT_SLOTON 0x80 +#define TOPIC_SLOT_SLOTEN 0x40 +#define TOPIC_SLOT_ID_LOCK 0x20 +#define TOPIC_SLOT_ID_WP 0x10 +#define TOPIC_SLOT_PORT_MASK 0x0c +#define TOPIC_SLOT_PORT_SHIFT 2 +#define TOPIC_SLOT_OFS_MASK 0x03 + +#define TOPIC_CARD_CONTROL 0x00a1 /* 8 bit */ +#define TOPIC_CCR_INTB 0x20 +#define TOPIC_CCR_INTA 0x10 +#define TOPIC_CCR_CLOCK 0x0c +#define TOPIC_CCR_PCICLK 0x0c +#define TOPIC_CCR_PCICLK_2 0x08 +#define TOPIC_CCR_CCLK 0x04 + +#define TOPIC97_INT_CONTROL 0x00a1 /* 8 bit */ +#define TOPIC97_ICR_INTB 0x20 +#define TOPIC97_ICR_INTA 0x10 +#define TOPIC97_ICR_STSIRQNP 0x04 +#define TOPIC97_ICR_IRQNP 0x02 +#define TOPIC97_ICR_IRQSEL 0x01 + +#define TOPIC_CARD_DETECT 0x00a3 /* 8 bit */ +#define TOPIC_CDR_MODE_PC32 0x80 +#define TOPIC_CDR_VS1 0x04 +#define TOPIC_CDR_VS2 0x02 +#define TOPIC_CDR_SW_DETECT 0x01 + +#define TOPIC_REGISTER_CONTROL 0x00a4 /* 32 bit */ +#define TOPIC_RCR_RESUME_RESET 0x80000000 +#define TOPIC_RCR_REMOVE_RESET 0x40000000 +#define TOPIC97_RCR_CLKRUN_ENA 0x20000000 +#define TOPIC97_RCR_TESTMODE 0x10000000 +#define TOPIC97_RCR_IOPLUP 0x08000000 +#define TOPIC_RCR_BUFOFF_PWROFF 0x02000000 +#define TOPIC_RCR_BUFOFF_SIGOFF 0x01000000 +#define TOPIC97_RCR_CB_DEV_MASK 0x0000f800 +#define TOPIC97_RCR_CB_DEV_SHIFT 11 +#define TOPIC97_RCR_RI_DISABLE 0x00000004 +#define TOPIC97_RCR_CAUDIO_OFF 0x00000002 +#define TOPIC_RCR_CAUDIO_INVERT 0x00000001 + +#define TOPIC_FUNCTION_CONTROL 0x3e +#define TOPIC_FCR_PWR_BUF_ENA 0x40 +#define TOPIC_FCR_CTR_ENA 0x08 +#define TOPIC_FCR_VS_ENA 0x02 +#define TOPIC_FCR_3V_ENA 0x01 + +/* Data structure for tracking vendor-specific state */ +typedef struct topic_state_t { + u_char slot; /* TOPIC_SLOT_CONTROL */ + u_char ccr; /* TOPIC_CARD_CONTROL */ + u_char cdr; /* TOPIC_CARD_DETECT */ + u_int rcr; /* TOPIC_REGISTER_CONTROL */ + u_char fcr; /* TOPIC_FUNCTION_CONTROL */ +} topic_state_t; + +#define TOPIC_PCIC_ID \ + IS_TOPIC95_A, IS_TOPIC95_B, IS_TOPIC97, IS_TOPIC100 + +#define TOPIC_PCIC_INFO \ + { "Toshiba ToPIC95-A", IS_CARDBUS|IS_TOPIC, ID(TOSHIBA, TOPIC95_A) }, \ + { "Toshiba ToPIC95-B", IS_CARDBUS|IS_TOPIC, ID(TOSHIBA, TOPIC95_B) }, \ + { "Toshiba ToPIC97", IS_CARDBUS|IS_TOPIC, ID(TOSHIBA, TOPIC97) }, \ + { "Toshiba ToPIC100", IS_CARDBUS|IS_TOPIC, ID(TOSHIBA, TOPIC100) } + +#endif /* _LINUX_TOPIC_H */ diff --git a/linux/pcmcia-cs/modules/vg468.h b/linux/pcmcia-cs/modules/vg468.h new file mode 100644 index 0000000..93dc00b --- /dev/null +++ b/linux/pcmcia-cs/modules/vg468.h @@ -0,0 +1,112 @@ +/* + * vg468.h 1.14 2001/08/24 12:15:34 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in + * which case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use + * your version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + */ + +#ifndef _LINUX_VG468_H +#define _LINUX_VG468_H + +/* Special bit in I365_IDENT used for Vadem chip detection */ +#define I365_IDENT_VADEM 0x08 + +/* Special definitions in I365_POWER */ +#define VG468_VPP2_MASK 0x0c +#define VG468_VPP2_5V 0x04 +#define VG468_VPP2_12V 0x08 + +/* Unique Vadem registers */ +#define VG469_VSENSE 0x1f /* Card voltage sense */ +#define VG469_VSELECT 0x2f /* Card voltage select */ +#define VG468_CTL 0x38 /* Control register */ +#define VG468_TIMER 0x39 /* Timer control */ +#define VG468_MISC 0x3a /* Miscellaneous */ +#define VG468_GPIO_CFG 0x3b /* GPIO configuration */ +#define VG469_EXT_MODE 0x3c /* Extended mode register */ +#define VG468_SELECT 0x3d /* Programmable chip select */ +#define VG468_SELECT_CFG 0x3e /* Chip select configuration */ +#define VG468_ATA 0x3f /* ATA control */ + +/* Flags for VG469_VSENSE */ +#define VG469_VSENSE_A_VS1 0x01 +#define VG469_VSENSE_A_VS2 0x02 +#define VG469_VSENSE_B_VS1 0x04 +#define VG469_VSENSE_B_VS2 0x08 + +/* Flags for VG469_VSELECT */ +#define VG469_VSEL_VCC 0x03 +#define VG469_VSEL_5V 0x00 +#define VG469_VSEL_3V 0x03 +#define VG469_VSEL_MAX 0x0c +#define VG469_VSEL_EXT_STAT 0x10 +#define VG469_VSEL_EXT_BUS 0x20 +#define VG469_VSEL_MIXED 0x40 +#define VG469_VSEL_ISA 0x80 + +/* Flags for VG468_CTL */ +#define VG468_CTL_SLOW 0x01 /* 600ns memory timing */ +#define VG468_CTL_ASYNC 0x02 /* Asynchronous bus clocking */ +#define VG468_CTL_TSSI 0x08 /* Tri-state some outputs */ +#define VG468_CTL_DELAY 0x10 /* Card detect debounce */ +#define VG468_CTL_INPACK 0x20 /* Obey INPACK signal? */ +#define VG468_CTL_POLARITY 0x40 /* VCCEN polarity */ +#define VG468_CTL_COMPAT 0x80 /* Compatibility stuff */ + +#define VG469_CTL_WS_COMPAT 0x04 /* Wait state compatibility */ +#define VG469_CTL_STRETCH 0x10 /* LED stretch */ + +/* Flags for VG468_TIMER */ +#define VG468_TIMER_ZEROPWR 0x10 /* Zero power control */ +#define VG468_TIMER_SIGEN 0x20 /* Power up */ +#define VG468_TIMER_STATUS 0x40 /* Activity timer status */ +#define VG468_TIMER_RES 0x80 /* Timer resolution */ +#define VG468_TIMER_MASK 0x0f /* Activity timer timeout */ + +/* Flags for VG468_MISC */ +#define VG468_MISC_GPIO 0x04 /* General-purpose IO */ +#define VG468_MISC_DMAWSB 0x08 /* DMA wait state control */ +#define VG469_MISC_LEDENA 0x10 /* LED enable */ +#define VG468_MISC_VADEMREV 0x40 /* Vadem revision control */ +#define VG468_MISC_UNLOCK 0x80 /* Unique register lock */ + +/* Flags for VG469_EXT_MODE_A */ +#define VG469_MODE_VPPST 0x03 /* Vpp steering control */ +#define VG469_MODE_INT_SENSE 0x04 /* Internal voltage sense */ +#define VG469_MODE_CABLE 0x08 +#define VG469_MODE_COMPAT 0x10 /* i82365sl B or DF step */ +#define VG469_MODE_TEST 0x20 +#define VG469_MODE_RIO 0x40 /* Steer RIO to INTR? */ + +/* Flags for VG469_EXT_MODE_B */ +#define VG469_MODE_B_3V 0x01 /* 3.3v for socket B */ + +/* Data structure for tracking vendor-specific state */ +typedef struct vg46x_state_t { + u_char ctl; /* VG468_CTL */ + u_char ema; /* VG468_EXT_MODE_A */ +} vg46x_state_t; + +#endif /* _LINUX_VG468_H */ diff --git a/linux/pcmcia-cs/modules/yenta.h b/linux/pcmcia-cs/modules/yenta.h new file mode 100644 index 0000000..525d8ec --- /dev/null +++ b/linux/pcmcia-cs/modules/yenta.h @@ -0,0 +1,156 @@ +/* + * yenta.h 1.20 2001/08/24 12:15:34 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in + * which case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use + * your version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + */ + +#ifndef _LINUX_YENTA_H +#define _LINUX_YENTA_H + +/* PCI Configuration Registers */ + +#define PCI_STATUS_CAPLIST 0x10 +#define PCI_CB_CAPABILITY_POINTER 0x14 /* 8 bit */ +#define PCI_CAPABILITY_ID 0x00 /* 8 bit */ +#define PCI_CAPABILITY_PM 0x01 +#define PCI_NEXT_CAPABILITY 0x01 /* 8 bit */ +#define PCI_PM_CAPABILITIES 0x02 /* 16 bit */ +#define PCI_PMCAP_PME_D3COLD 0x8000 +#define PCI_PMCAP_PME_D3HOT 0x4000 +#define PCI_PMCAP_PME_D2 0x2000 +#define PCI_PMCAP_PME_D1 0x1000 +#define PCI_PMCAP_PME_D0 0x0800 +#define PCI_PMCAP_D2_CAP 0x0400 +#define PCI_PMCAP_D1_CAP 0x0200 +#define PCI_PMCAP_DYN_DATA 0x0100 +#define PCI_PMCAP_DSI 0x0020 +#define PCI_PMCAP_AUX_PWR 0x0010 +#define PCI_PMCAP_PMECLK 0x0008 +#define PCI_PMCAP_VERSION_MASK 0x0007 +#define PCI_PM_CONTROL_STATUS 0x04 /* 16 bit */ +#define PCI_PMCS_PME_STATUS 0x8000 +#define PCI_PMCS_DATASCALE_MASK 0x6000 +#define PCI_PMCS_DATASCALE_SHIFT 13 +#define PCI_PMCS_DATASEL_MASK 0x1e00 +#define PCI_PMCS_DATASEL_SHIFT 9 +#define PCI_PMCS_PME_ENABLE 0x0100 +#define PCI_PMCS_PWR_STATE_MASK 0x0003 +#define PCI_PMCS_PWR_STATE_D0 0x0000 +#define PCI_PMCS_PWR_STATE_D1 0x0001 +#define PCI_PMCS_PWR_STATE_D2 0x0002 +#define PCI_PMCS_PWR_STATE_D3 0x0003 +#define PCI_PM_BRIDGE_EXT 0x06 /* 8 bit */ +#define PCI_PM_DATA 0x07 /* 8 bit */ + +#define CB_PRIMARY_BUS 0x18 /* 8 bit */ +#define CB_CARDBUS_BUS 0x19 /* 8 bit */ +#define CB_SUBORD_BUS 0x1a /* 8 bit */ +#define CB_LATENCY_TIMER 0x1b /* 8 bit */ + +#define CB_MEM_BASE(m) (0x1c + 8*(m)) +#define CB_MEM_LIMIT(m) (0x20 + 8*(m)) +#define CB_IO_BASE(m) (0x2c + 8*(m)) +#define CB_IO_LIMIT(m) (0x30 + 8*(m)) + +#define CB_BRIDGE_CONTROL 0x3e /* 16 bit */ +#define CB_BCR_PARITY_ENA 0x0001 +#define CB_BCR_SERR_ENA 0x0002 +#define CB_BCR_ISA_ENA 0x0004 +#define CB_BCR_VGA_ENA 0x0008 +#define CB_BCR_MABORT 0x0020 +#define CB_BCR_CB_RESET 0x0040 +#define CB_BCR_ISA_IRQ 0x0080 +#define CB_BCR_PREFETCH(m) (0x0100 << (m)) +#define CB_BCR_WRITE_POST 0x0400 + +#define CB_LEGACY_MODE_BASE 0x44 + +/* Memory mapped registers */ + +#define CB_SOCKET_EVENT 0x0000 +#define CB_SE_CSTSCHG 0x00000001 +#define CB_SE_CCD 0x00000006 +#define CB_SE_CCD1 0x00000002 +#define CB_SE_CCD2 0x00000004 +#define CB_SE_PWRCYCLE 0x00000008 + +#define CB_SOCKET_MASK 0x0004 +#define CB_SM_CSTSCHG 0x00000001 +#define CB_SM_CCD 0x00000006 +#define CB_SM_PWRCYCLE 0x00000008 + +#define CB_SOCKET_STATE 0x0008 +#define CB_SS_CSTSCHG 0x00000001 +#define CB_SS_CCD 0x00000006 +#define CB_SS_CCD1 0x00000002 +#define CB_SS_CCD2 0x00000004 +#define CB_SS_PWRCYCLE 0x00000008 +#define CB_SS_16BIT 0x00000010 +#define CB_SS_32BIT 0x00000020 +#define CB_SS_CINT 0x00000040 +#define CB_SS_BADCARD 0x00000080 +#define CB_SS_DATALOST 0x00000100 +#define CB_SS_BADVCC 0x00000200 +#define CB_SS_5VCARD 0x00000400 +#define CB_SS_3VCARD 0x00000800 +#define CB_SS_XVCARD 0x00001000 +#define CB_SS_YVCARD 0x00002000 +#define CB_SS_VSENSE 0x00003c86 +#define CB_SS_5VSOCKET 0x10000000 +#define CB_SS_3VSOCKET 0x20000000 +#define CB_SS_XVSOCKET 0x40000000 +#define CB_SS_YVSOCKET 0x80000000 + +#define CB_SOCKET_FORCE 0x000c +#define CB_SF_CVSTEST 0x00004000 + +#define CB_SOCKET_CONTROL 0x0010 +#define CB_SC_VPP_MASK 0x00000007 +#define CB_SC_VPP_OFF 0x00000000 +#define CB_SC_VPP_12V 0x00000001 +#define CB_SC_VPP_5V 0x00000002 +#define CB_SC_VPP_3V 0x00000003 +#define CB_SC_VPP_XV 0x00000004 +#define CB_SC_VPP_YV 0x00000005 +#define CB_SC_VCC_MASK 0x00000070 +#define CB_SC_VCC_OFF 0x00000000 +#define CB_SC_VCC_5V 0x00000020 +#define CB_SC_VCC_3V 0x00000030 +#define CB_SC_VCC_XV 0x00000040 +#define CB_SC_VCC_YV 0x00000050 +#define CB_SC_CCLK_STOP 0x00000080 + +#define CB_SOCKET_POWER 0x0020 +#define CB_SP_CLK_CTRL 0x00000001 +#define CB_SP_CLK_CTRL_ENA 0x00010000 +#define CB_SP_CLK_MODE 0x01000000 +#define CB_SP_ACCESS 0x02000000 + +/* Address bits 31..24 for memory windows for 16-bit cards, + accessable only by memory mapping the 16-bit register set */ +#define CB_MEM_PAGE(map) (0x40 + (map)) + +#endif /* _LINUX_YENTA_H */ diff --git a/linux/pcmcia-cs/wireless/hermes.c b/linux/pcmcia-cs/wireless/hermes.c new file mode 100644 index 0000000..d5ec3de --- /dev/null +++ b/linux/pcmcia-cs/wireless/hermes.c @@ -0,0 +1,552 @@ +/* hermes.c + * + * Driver core for the "Hermes" wireless MAC controller, as used in + * the Lucent Orinoco and Cabletron RoamAbout cards. It should also + * work on the hfa3841 and hfa3842 MAC controller chips used in the + * Prism II chipsets. + * + * This is not a complete driver, just low-level access routines for + * the MAC controller itself. + * + * Based on the prism2 driver from Absolute Value Systems' linux-wlan + * project, the Linux wvlan_cs driver, Lucent's HCF-Light + * (wvlan_hcf.c) library, and the NetBSD wireless driver (in no + * particular order). + * + * Copyright (C) 2000, David Gibson, Linuxcare Australia <hermes@gibson.dropbear.id.au> + * Copyright (C) 2001, David Gibson, IBM <hermes@gibson.dropbear.id.au> + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in + * which case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use your + * version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + */ + +#include <linux/config.h> + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/threads.h> +#include <linux/smp.h> +#include <asm/io.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <asm/errno.h> + +#include "hermes.h" + +MODULE_DESCRIPTION("Low-level driver helper for Lucent Hermes chipset and Prism II HFA384x wireless MAC controller"); +MODULE_AUTHOR("David Gibson <hermes@gibson.dropbear.id.au>"); +#ifdef MODULE_LICENSE +MODULE_LICENSE("Dual MPL/GPL"); +#endif + +/* These are maximum timeouts. Most often, card wil react much faster */ +#define CMD_BUSY_TIMEOUT (100) /* In iterations of ~1us */ +#define CMD_INIT_TIMEOUT (50000) /* in iterations of ~10us */ +#define CMD_COMPL_TIMEOUT (20000) /* in iterations of ~10us */ +#define ALLOC_COMPL_TIMEOUT (1000) /* in iterations of ~10us */ + +/* + * Debugging helpers + */ + +#define IO_TYPE(hw) ((hw)->io_space ? "IO " : "MEM ") +#define DMSG(stuff...) do {printk(KERN_DEBUG "hermes @ %s0x%x: " , IO_TYPE(hw), hw->iobase); \ + printk(stuff);} while (0) + +#undef HERMES_DEBUG +#ifdef HERMES_DEBUG +#include <stdarg.h> + +#define DEBUG(lvl, stuff...) if ( (lvl) <= HERMES_DEBUG) DMSG(stuff) + +#else /* ! HERMES_DEBUG */ + +#define DEBUG(lvl, stuff...) do { } while (0) + +#endif /* ! HERMES_DEBUG */ + + +/* + * Internal functions + */ + +/* Issue a command to the chip. Waiting for it to complete is the caller's + problem. + + Returns -EBUSY if the command register is busy, 0 on success. + + Callable from any context. +*/ +static int hermes_issue_cmd(hermes_t *hw, u16 cmd, u16 param0) +{ + int k = CMD_BUSY_TIMEOUT; + u16 reg; + + /* First wait for the command register to unbusy */ + reg = hermes_read_regn(hw, CMD); + while ( (reg & HERMES_CMD_BUSY) && k ) { + k--; + udelay(1); + reg = hermes_read_regn(hw, CMD); + } + if (reg & HERMES_CMD_BUSY) { + return -EBUSY; + } + + hermes_write_regn(hw, PARAM2, 0); + hermes_write_regn(hw, PARAM1, 0); + hermes_write_regn(hw, PARAM0, param0); + hermes_write_regn(hw, CMD, cmd); + + return 0; +} + +/* + * Function definitions + */ + +void hermes_struct_init(hermes_t *hw, ulong address, + int io_space, int reg_spacing) +{ + hw->iobase = address; + hw->io_space = io_space; + hw->reg_spacing = reg_spacing; + hw->inten = 0x0; + +#ifdef HERMES_DEBUG_BUFFER + hw->dbufp = 0; + memset(&hw->dbuf, 0xff, sizeof(hw->dbuf)); + memset(&hw->profile, 0, sizeof(hw->profile)); +#endif +} + +int hermes_init(hermes_t *hw) +{ + u16 status, reg; + int err = 0; + int k; + + /* We don't want to be interrupted while resetting the chipset */ + hw->inten = 0x0; + hermes_write_regn(hw, INTEN, 0); + hermes_write_regn(hw, EVACK, 0xffff); + + /* Normally it's a "can't happen" for the command register to + be busy when we go to issue a command because we are + serializing all commands. However we want to have some + chance of resetting the card even if it gets into a stupid + state, so we actually wait to see if the command register + will unbusy itself here. */ + k = CMD_BUSY_TIMEOUT; + reg = hermes_read_regn(hw, CMD); + while (k && (reg & HERMES_CMD_BUSY)) { + if (reg == 0xffff) /* Special case - the card has probably been removed, + so don't wait for the timeout */ + return -ENODEV; + + k--; + udelay(1); + reg = hermes_read_regn(hw, CMD); + } + + /* No need to explicitly handle the timeout - if we've timed + out hermes_issue_cmd() will probably return -EBUSY below */ + + /* According to the documentation, EVSTAT may contain + obsolete event occurrence information. We have to acknowledge + it by writing EVACK. */ + reg = hermes_read_regn(hw, EVSTAT); + hermes_write_regn(hw, EVACK, reg); + + /* We don't use hermes_docmd_wait here, because the reset wipes + the magic constant in SWSUPPORT0 away, and it gets confused */ + err = hermes_issue_cmd(hw, HERMES_CMD_INIT, 0); + if (err) + return err; + + reg = hermes_read_regn(hw, EVSTAT); + k = CMD_INIT_TIMEOUT; + while ( (! (reg & HERMES_EV_CMD)) && k) { + k--; + udelay(10); + reg = hermes_read_regn(hw, EVSTAT); + } + + hermes_write_regn(hw, SWSUPPORT0, HERMES_MAGIC); + + if (! hermes_present(hw)) { + DEBUG(0, "hermes @ 0x%x: Card removed during reset.\n", + hw->iobase); + err = -ENODEV; + goto out; + } + + if (! (reg & HERMES_EV_CMD)) { + printk(KERN_ERR "hermes @ %s0x%lx: " + "Timeout waiting for card to reset (reg=0x%04x)!\n", + IO_TYPE(hw), hw->iobase, reg); + err = -ETIMEDOUT; + goto out; + } + + status = hermes_read_regn(hw, STATUS); + + hermes_write_regn(hw, EVACK, HERMES_EV_CMD); + + if (status & HERMES_STATUS_RESULT) + err = -EIO; + + out: + return err; +} + +/* Issue a command to the chip, and (busy!) wait for it to + * complete. + * + * Returns: < 0 on internal error, 0 on success, > 0 on error returned by the firmware + * + * Callable from any context, but locking is your problem. */ +int hermes_docmd_wait(hermes_t *hw, u16 cmd, u16 parm0, + hermes_response_t *resp) +{ + int err; + int k; + u16 reg; + u16 status; + + err = hermes_issue_cmd(hw, cmd, parm0); + if (err) { + if (! hermes_present(hw)) { + printk(KERN_WARNING "hermes @ %s0x%lx: " + "Card removed while issuing command.\n", + IO_TYPE(hw), hw->iobase); + err = -ENODEV; + } else + printk(KERN_ERR "hermes @ %s0x%lx: Error %d issuing command.\n", + IO_TYPE(hw), hw->iobase, err); + goto out; + } + + reg = hermes_read_regn(hw, EVSTAT); + k = CMD_COMPL_TIMEOUT; + while ( (! (reg & HERMES_EV_CMD)) && k) { + k--; + udelay(10); + reg = hermes_read_regn(hw, EVSTAT); + } + + if (! hermes_present(hw)) { + printk(KERN_WARNING "hermes @ %s0x%lx: " + "Card removed while waiting for command completion.\n", + IO_TYPE(hw), hw->iobase); + err = -ENODEV; + goto out; + } + + if (! (reg & HERMES_EV_CMD)) { + printk(KERN_ERR "hermes @ %s0x%lx: " + "Timeout waiting for command completion.\n", + IO_TYPE(hw), hw->iobase); + err = -ETIMEDOUT; + goto out; + } + + status = hermes_read_regn(hw, STATUS); + if (resp) { + resp->status = status; + resp->resp0 = hermes_read_regn(hw, RESP0); + resp->resp1 = hermes_read_regn(hw, RESP1); + resp->resp2 = hermes_read_regn(hw, RESP2); + } + + hermes_write_regn(hw, EVACK, HERMES_EV_CMD); + + if (status & HERMES_STATUS_RESULT) + err = -EIO; + + out: + return err; +} + +int hermes_allocate(hermes_t *hw, u16 size, u16 *fid) +{ + int err = 0; + int k; + u16 reg; + + if ( (size < HERMES_ALLOC_LEN_MIN) || (size > HERMES_ALLOC_LEN_MAX) ) + return -EINVAL; + + err = hermes_docmd_wait(hw, HERMES_CMD_ALLOC, size, NULL); + if (err) { + return err; + } + + reg = hermes_read_regn(hw, EVSTAT); + k = ALLOC_COMPL_TIMEOUT; + while ( (! (reg & HERMES_EV_ALLOC)) && k) { + k--; + udelay(10); + reg = hermes_read_regn(hw, EVSTAT); + } + + if (! hermes_present(hw)) { + printk(KERN_WARNING "hermes @ %s0x%lx: " + "Card removed waiting for frame allocation.\n", + IO_TYPE(hw), hw->iobase); + return -ENODEV; + } + + if (! (reg & HERMES_EV_ALLOC)) { + printk(KERN_ERR "hermes @ %s0x%lx: " + "Timeout waiting for frame allocation\n", + IO_TYPE(hw), hw->iobase); + return -ETIMEDOUT; + } + + *fid = hermes_read_regn(hw, ALLOCFID); + hermes_write_regn(hw, EVACK, HERMES_EV_ALLOC); + + return 0; +} + + +/* Set up a BAP to read a particular chunk of data from card's internal buffer. + * + * Returns: < 0 on internal failure (errno), 0 on success, >0 on error + * from firmware + * + * Callable from any context */ +static int hermes_bap_seek(hermes_t *hw, int bap, u16 id, u16 offset) +{ + int sreg = bap ? HERMES_SELECT1 : HERMES_SELECT0; + int oreg = bap ? HERMES_OFFSET1 : HERMES_OFFSET0; + int k; + u16 reg; + + /* Paranoia.. */ + if ( (offset > HERMES_BAP_OFFSET_MAX) || (offset % 2) ) + return -EINVAL; + + k = HERMES_BAP_BUSY_TIMEOUT; + reg = hermes_read_reg(hw, oreg); + while ((reg & HERMES_OFFSET_BUSY) && k) { + k--; + udelay(1); + reg = hermes_read_reg(hw, oreg); + } + +#ifdef HERMES_DEBUG_BUFFER + hw->profile[HERMES_BAP_BUSY_TIMEOUT - k]++; + + if (k < HERMES_BAP_BUSY_TIMEOUT) { + struct hermes_debug_entry *e = + &hw->dbuf[(hw->dbufp++) % HERMES_DEBUG_BUFSIZE]; + e->bap = bap; + e->id = id; + e->offset = offset; + e->cycles = HERMES_BAP_BUSY_TIMEOUT - k; + } +#endif + + if (reg & HERMES_OFFSET_BUSY) + return -ETIMEDOUT; + + /* Now we actually set up the transfer */ + hermes_write_reg(hw, sreg, id); + hermes_write_reg(hw, oreg, offset); + + /* Wait for the BAP to be ready */ + k = HERMES_BAP_BUSY_TIMEOUT; + reg = hermes_read_reg(hw, oreg); + while ( (reg & (HERMES_OFFSET_BUSY | HERMES_OFFSET_ERR)) && k) { + k--; + udelay(1); + reg = hermes_read_reg(hw, oreg); + } + + if (reg & HERMES_OFFSET_BUSY) { + return -ETIMEDOUT; + } + + if (reg & HERMES_OFFSET_ERR) { + return -EIO; + } + + + return 0; +} + +/* Read a block of data from the chip's buffer, via the + * BAP. Synchronization/serialization is the caller's problem. len + * must be even. + * + * Returns: < 0 on internal failure (errno), 0 on success, > 0 on error from firmware + */ +int hermes_bap_pread(hermes_t *hw, int bap, void *buf, unsigned len, + u16 id, u16 offset) +{ + int dreg = bap ? HERMES_DATA1 : HERMES_DATA0; + int err = 0; + + if ( (len < 0) || (len % 2) ) + return -EINVAL; + + err = hermes_bap_seek(hw, bap, id, offset); + if (err) + goto out; + + /* Actually do the transfer */ + hermes_read_words(hw, dreg, buf, len/2); + + out: + return err; +} + +/* Write a block of data to the chip's buffer, via the + * BAP. Synchronization/serialization is the caller's problem. len + * must be even. + * + * Returns: < 0 on internal failure (errno), 0 on success, > 0 on error from firmware + */ +int hermes_bap_pwrite(hermes_t *hw, int bap, const void *buf, unsigned len, + u16 id, u16 offset) +{ + int dreg = bap ? HERMES_DATA1 : HERMES_DATA0; + int err = 0; + + if ( (len < 0) || (len % 2) ) + return -EINVAL; + + err = hermes_bap_seek(hw, bap, id, offset); + if (err) + goto out; + + /* Actually do the transfer */ + hermes_write_words(hw, dreg, buf, len/2); + + out: + return err; +} + +/* Read a Length-Type-Value record from the card. + * + * If length is NULL, we ignore the length read from the card, and + * read the entire buffer regardless. This is useful because some of + * the configuration records appear to have incorrect lengths in + * practice. + * + * Callable from user or bh context. */ +int hermes_read_ltv(hermes_t *hw, int bap, u16 rid, unsigned bufsize, + u16 *length, void *buf) +{ + int err = 0; + int dreg = bap ? HERMES_DATA1 : HERMES_DATA0; + u16 rlength, rtype; + unsigned nwords; + + if ( (bufsize < 0) || (bufsize % 2) ) + return -EINVAL; + + err = hermes_docmd_wait(hw, HERMES_CMD_ACCESS, rid, NULL); + if (err) + return err; + + err = hermes_bap_seek(hw, bap, rid, 0); + if (err) + return err; + + rlength = hermes_read_reg(hw, dreg); + + if (! rlength) + return -ENOENT; + + rtype = hermes_read_reg(hw, dreg); + + if (length) + *length = rlength; + + if (rtype != rid) + printk(KERN_WARNING "hermes @ %s0x%lx: " + "hermes_read_ltv(): rid (0x%04x) does not match type (0x%04x)\n", + IO_TYPE(hw), hw->iobase, rid, rtype); + if (HERMES_RECLEN_TO_BYTES(rlength) > bufsize) + printk(KERN_WARNING "hermes @ %s0x%lx: " + "Truncating LTV record from %d to %d bytes. " + "(rid=0x%04x, len=0x%04x)\n", + IO_TYPE(hw), hw->iobase, + HERMES_RECLEN_TO_BYTES(rlength), bufsize, rid, rlength); + + nwords = min((unsigned)rlength - 1, bufsize / 2); + hermes_read_words(hw, dreg, buf, nwords); + + return 0; +} + +int hermes_write_ltv(hermes_t *hw, int bap, u16 rid, + u16 length, const void *value) +{ + int dreg = bap ? HERMES_DATA1 : HERMES_DATA0; + int err = 0; + unsigned count; + + if (length == 0) + return -EINVAL; + + err = hermes_bap_seek(hw, bap, rid, 0); + if (err) + return err; + + hermes_write_reg(hw, dreg, length); + hermes_write_reg(hw, dreg, rid); + + count = length - 1; + + hermes_write_words(hw, dreg, value, count); + + err = hermes_docmd_wait(hw, HERMES_CMD_ACCESS | HERMES_CMD_WRITE, + rid, NULL); + + return err; +} + +EXPORT_SYMBOL(hermes_struct_init); +EXPORT_SYMBOL(hermes_init); +EXPORT_SYMBOL(hermes_docmd_wait); +EXPORT_SYMBOL(hermes_allocate); + +EXPORT_SYMBOL(hermes_bap_pread); +EXPORT_SYMBOL(hermes_bap_pwrite); +EXPORT_SYMBOL(hermes_read_ltv); +EXPORT_SYMBOL(hermes_write_ltv); + +static int __init init_hermes(void) +{ + return 0; +} + +static void __exit exit_hermes(void) +{ +} + +module_init(init_hermes); +module_exit(exit_hermes); diff --git a/linux/pcmcia-cs/wireless/hermes.h b/linux/pcmcia-cs/wireless/hermes.h new file mode 100644 index 0000000..b43fa0c --- /dev/null +++ b/linux/pcmcia-cs/wireless/hermes.h @@ -0,0 +1,456 @@ +/* hermes.h + * + * Driver core for the "Hermes" wireless MAC controller, as used in + * the Lucent Orinoco and Cabletron RoamAbout cards. It should also + * work on the hfa3841 and hfa3842 MAC controller chips used in the + * Prism I & II chipsets. + * + * This is not a complete driver, just low-level access routines for + * the MAC controller itself. + * + * Based on the prism2 driver from Absolute Value Systems' linux-wlan + * project, the Linux wvlan_cs driver, Lucent's HCF-Light + * (wvlan_hcf.c) library, and the NetBSD wireless driver. + * + * Copyright (C) 2000, David Gibson, Linuxcare Australia <hermes@gibson.dropbear.id.au> + * + * Portions taken from hfa384x.h, Copyright (C) 1999 AbsoluteValue Systems, Inc. All Rights Reserved. + * + * This file distributed under the GPL, version 2. + */ + +#ifndef _HERMES_H +#define _HERMES_H + +/* Notes on locking: + * + * As a module of low level hardware access routines, there is no + * locking. Users of this module should ensure that they serialize + * access to the hermes_t structure, and to the hardware +*/ + +#include <linux/delay.h> +#include <linux/if_ether.h> +#include <asm/byteorder.h> + +/* + * Limits and constants + */ +#define HERMES_ALLOC_LEN_MIN (4) +#define HERMES_ALLOC_LEN_MAX (2400) +#define HERMES_LTV_LEN_MAX (34) +#define HERMES_BAP_DATALEN_MAX (4096) +#define HERMES_BAP_OFFSET_MAX (4096) +#define HERMES_PORTID_MAX (7) +#define HERMES_NUMPORTS_MAX (HERMES_PORTID_MAX+1) +#define HERMES_PDR_LEN_MAX (260) /* in bytes, from EK */ +#define HERMES_PDA_RECS_MAX (200) /* a guess */ +#define HERMES_PDA_LEN_MAX (1024) /* in bytes, from EK */ +#define HERMES_SCANRESULT_MAX (35) +#define HERMES_CHINFORESULT_MAX (8) +#define HERMES_MAX_MULTICAST (16) +#define HERMES_MAGIC (0x7d1f) + +/* + * Hermes register offsets + */ +#define HERMES_CMD (0x00) +#define HERMES_PARAM0 (0x02) +#define HERMES_PARAM1 (0x04) +#define HERMES_PARAM2 (0x06) +#define HERMES_STATUS (0x08) +#define HERMES_RESP0 (0x0A) +#define HERMES_RESP1 (0x0C) +#define HERMES_RESP2 (0x0E) +#define HERMES_INFOFID (0x10) +#define HERMES_RXFID (0x20) +#define HERMES_ALLOCFID (0x22) +#define HERMES_TXCOMPLFID (0x24) +#define HERMES_SELECT0 (0x18) +#define HERMES_OFFSET0 (0x1C) +#define HERMES_DATA0 (0x36) +#define HERMES_SELECT1 (0x1A) +#define HERMES_OFFSET1 (0x1E) +#define HERMES_DATA1 (0x38) +#define HERMES_EVSTAT (0x30) +#define HERMES_INTEN (0x32) +#define HERMES_EVACK (0x34) +#define HERMES_CONTROL (0x14) +#define HERMES_SWSUPPORT0 (0x28) +#define HERMES_SWSUPPORT1 (0x2A) +#define HERMES_SWSUPPORT2 (0x2C) +#define HERMES_AUXPAGE (0x3A) +#define HERMES_AUXOFFSET (0x3C) +#define HERMES_AUXDATA (0x3E) + +/* + * CMD register bitmasks + */ +#define HERMES_CMD_BUSY (0x8000) +#define HERMES_CMD_AINFO (0x7f00) +#define HERMES_CMD_MACPORT (0x0700) +#define HERMES_CMD_RECL (0x0100) +#define HERMES_CMD_WRITE (0x0100) +#define HERMES_CMD_PROGMODE (0x0300) +#define HERMES_CMD_CMDCODE (0x003f) + +/* + * STATUS register bitmasks + */ +#define HERMES_STATUS_RESULT (0x7f00) +#define HERMES_STATUS_CMDCODE (0x003f) + +/* + * OFFSET register bitmasks + */ +#define HERMES_OFFSET_BUSY (0x8000) +#define HERMES_OFFSET_ERR (0x4000) +#define HERMES_OFFSET_DATAOFF (0x0ffe) + +/* + * Event register bitmasks (INTEN, EVSTAT, EVACK) + */ +#define HERMES_EV_TICK (0x8000) +#define HERMES_EV_WTERR (0x4000) +#define HERMES_EV_INFDROP (0x2000) +#define HERMES_EV_INFO (0x0080) +#define HERMES_EV_DTIM (0x0020) +#define HERMES_EV_CMD (0x0010) +#define HERMES_EV_ALLOC (0x0008) +#define HERMES_EV_TXEXC (0x0004) +#define HERMES_EV_TX (0x0002) +#define HERMES_EV_RX (0x0001) + +/* + * Command codes + */ +/*--- Controller Commands --------------------------*/ +#define HERMES_CMD_INIT (0x0000) +#define HERMES_CMD_ENABLE (0x0001) +#define HERMES_CMD_DISABLE (0x0002) +#define HERMES_CMD_DIAG (0x0003) + +/*--- Buffer Mgmt Commands --------------------------*/ +#define HERMES_CMD_ALLOC (0x000A) +#define HERMES_CMD_TX (0x000B) +#define HERMES_CMD_CLRPRST (0x0012) + +/*--- Regulate Commands --------------------------*/ +#define HERMES_CMD_NOTIFY (0x0010) +#define HERMES_CMD_INQUIRE (0x0011) + +/*--- Configure Commands --------------------------*/ +#define HERMES_CMD_ACCESS (0x0021) +#define HERMES_CMD_DOWNLD (0x0022) + +/*--- Debugging Commands -----------------------------*/ +#define HERMES_CMD_MONITOR (0x0038) +#define HERMES_MONITOR_ENABLE (0x000b) +#define HERMES_MONITOR_DISABLE (0x000f) + +/* + * Frame structures and constants + */ + +#define HERMES_DESCRIPTOR_OFFSET 0 +#define HERMES_802_11_OFFSET (14) +#define HERMES_802_3_OFFSET (14+32) +#define HERMES_802_2_OFFSET (14+32+14) + +struct hermes_rx_descriptor { + u16 status; + u32 time; + u8 silence; + u8 signal; + u8 rate; + u8 rxflow; + u32 reserved; +} __attribute__ ((packed)); + +#define HERMES_RXSTAT_ERR (0x0003) +#define HERMES_RXSTAT_BADCRC (0x0001) +#define HERMES_RXSTAT_UNDECRYPTABLE (0x0002) +#define HERMES_RXSTAT_MACPORT (0x0700) +#define HERMES_RXSTAT_PCF (0x1000) /* Frame was received in CF period */ +#define HERMES_RXSTAT_MSGTYPE (0xE000) +#define HERMES_RXSTAT_1042 (0x2000) /* RFC-1042 frame */ +#define HERMES_RXSTAT_TUNNEL (0x4000) /* bridge-tunnel encoded frame */ +#define HERMES_RXSTAT_WMP (0x6000) /* Wavelan-II Management Protocol frame */ + +struct hermes_tx_descriptor { + u16 status; + u16 reserved1; + u16 reserved2; + u32 sw_support; + u8 retry_count; + u8 tx_rate; + u16 tx_control; +} __attribute__ ((packed)); + +#define HERMES_TXSTAT_RETRYERR (0x0001) +#define HERMES_TXSTAT_AGEDERR (0x0002) +#define HERMES_TXSTAT_DISCON (0x0004) +#define HERMES_TXSTAT_FORMERR (0x0008) + +#define HERMES_TXCTRL_TX_OK (0x0002) /* ?? interrupt on Tx complete */ +#define HERMES_TXCTRL_TX_EX (0x0004) /* ?? interrupt on Tx exception */ +#define HERMES_TXCTRL_802_11 (0x0008) /* We supply 802.11 header */ +#define HERMES_TXCTRL_ALT_RTRY (0x0020) + +/* Inquiry constants and data types */ + +#define HERMES_INQ_TALLIES (0xF100) +#define HERMES_INQ_SCAN (0xF101) +#define HERMES_INQ_LINKSTATUS (0xF200) + +struct hermes_tallies_frame { + u16 TxUnicastFrames; + u16 TxMulticastFrames; + u16 TxFragments; + u16 TxUnicastOctets; + u16 TxMulticastOctets; + u16 TxDeferredTransmissions; + u16 TxSingleRetryFrames; + u16 TxMultipleRetryFrames; + u16 TxRetryLimitExceeded; + u16 TxDiscards; + u16 RxUnicastFrames; + u16 RxMulticastFrames; + u16 RxFragments; + u16 RxUnicastOctets; + u16 RxMulticastOctets; + u16 RxFCSErrors; + u16 RxDiscards_NoBuffer; + u16 TxDiscardsWrongSA; + u16 RxWEPUndecryptable; + u16 RxMsgInMsgFragments; + u16 RxMsgInBadMsgFragments; + /* Those last are probably not available in very old firmwares */ + u16 RxDiscards_WEPICVError; + u16 RxDiscards_WEPExcluded; +} __attribute__ ((packed)); + +/* Grabbed from wlan-ng - Thanks Mark... - Jean II + * This is the result of a scan inquiry command */ +/* Structure describing info about an Access Point */ +struct hermes_scan_apinfo { + u16 channel; /* Channel where the AP sits */ + u16 noise; /* Noise level */ + u16 level; /* Signal level */ + u8 bssid[ETH_ALEN]; /* MAC address of the Access Point */ + u16 beacon_interv; /* Beacon interval ? */ + u16 capabilities; /* Capabilities ? */ + u8 essid[32]; /* ESSID of the network */ + u8 rates[10]; /* Bit rate supported */ + u16 proberesp_rate; /* ???? */ +} __attribute__ ((packed)); +/* Container */ +struct hermes_scan_frame { + u16 rsvd; /* ??? */ + u16 scanreason; /* ??? */ + struct hermes_scan_apinfo aps[35]; /* Scan result */ +} __attribute__ ((packed)); +#define HERMES_LINKSTATUS_NOT_CONNECTED (0x0000) +#define HERMES_LINKSTATUS_CONNECTED (0x0001) +#define HERMES_LINKSTATUS_DISCONNECTED (0x0002) +#define HERMES_LINKSTATUS_AP_CHANGE (0x0003) +#define HERMES_LINKSTATUS_AP_OUT_OF_RANGE (0x0004) +#define HERMES_LINKSTATUS_AP_IN_RANGE (0x0005) +#define HERMES_LINKSTATUS_ASSOC_FAILED (0x0006) + +struct hermes_linkstatus { + u16 linkstatus; /* Link status */ +} __attribute__ ((packed)); + +// #define HERMES_DEBUG_BUFFER 1 +#define HERMES_DEBUG_BUFSIZE 4096 +struct hermes_debug_entry { + int bap; + u16 id, offset; + int cycles; +}; + +#ifdef __KERNEL__ + +/* Timeouts */ +#define HERMES_BAP_BUSY_TIMEOUT (500) /* In iterations of ~1us */ + +/* Basic control structure */ +typedef struct hermes { + unsigned long iobase; + int io_space; /* 1 if we IO-mapped IO, 0 for memory-mapped IO? */ +#define HERMES_IO 1 +#define HERMES_MEM 0 + int reg_spacing; +#define HERMES_16BIT_REGSPACING 0 +#define HERMES_32BIT_REGSPACING 1 + + u16 inten; /* Which interrupts should be enabled? */ + +#ifdef HERMES_DEBUG_BUFFER + struct hermes_debug_entry dbuf[HERMES_DEBUG_BUFSIZE]; + unsigned long dbufp; + unsigned long profile[HERMES_BAP_BUSY_TIMEOUT+1]; +#endif +} hermes_t; + +typedef struct hermes_response { + u16 status, resp0, resp1, resp2; +} hermes_response_t; + +/* Register access convenience macros */ +#define hermes_read_reg(hw, off) ((hw)->io_space ? \ + inw((hw)->iobase + ( (off) << (hw)->reg_spacing )) : \ + readw((hw)->iobase + ( (off) << (hw)->reg_spacing ))) +#define hermes_write_reg(hw, off, val) ((hw)->io_space ? \ + outw_p((val), (hw)->iobase + ( (off) << (hw)->reg_spacing )) : \ + writew((val), (hw)->iobase + ( (off) << (hw)->reg_spacing ))) + +#define hermes_read_regn(hw, name) (hermes_read_reg((hw), HERMES_##name)) +#define hermes_write_regn(hw, name, val) (hermes_write_reg((hw), HERMES_##name, (val))) + +/* Function prototypes */ +void hermes_struct_init(hermes_t *hw, ulong address, int io_space, int reg_spacing); +int hermes_init(hermes_t *hw); +int hermes_docmd_wait(hermes_t *hw, u16 cmd, u16 parm0, hermes_response_t *resp); +int hermes_allocate(hermes_t *hw, u16 size, u16 *fid); + +int hermes_bap_pread(hermes_t *hw, int bap, void *buf, unsigned len, + u16 id, u16 offset); +int hermes_bap_pwrite(hermes_t *hw, int bap, const void *buf, unsigned len, + u16 id, u16 offset); +int hermes_read_ltv(hermes_t *hw, int bap, u16 rid, unsigned buflen, + u16 *length, void *buf); +int hermes_write_ltv(hermes_t *hw, int bap, u16 rid, + u16 length, const void *value); + +/* Inline functions */ + +static inline int hermes_present(hermes_t *hw) +{ + return hermes_read_regn(hw, SWSUPPORT0) == HERMES_MAGIC; +} + +static inline void hermes_set_irqmask(hermes_t *hw, u16 events) +{ + hw->inten = events; + hermes_write_regn(hw, INTEN, events); +} + +static inline int hermes_enable_port(hermes_t *hw, int port) +{ + return hermes_docmd_wait(hw, HERMES_CMD_ENABLE | (port << 8), + 0, NULL); +} + +static inline int hermes_disable_port(hermes_t *hw, int port) +{ + return hermes_docmd_wait(hw, HERMES_CMD_DISABLE | (port << 8), + 0, NULL); +} + +/* Initiate an INQUIRE command (tallies or scan). The result will come as an + * information frame in __orinoco_ev_info() */ +static inline int hermes_inquire(hermes_t *hw, u16 rid) +{ + return hermes_docmd_wait(hw, HERMES_CMD_INQUIRE, rid, NULL); +} + +#define HERMES_BYTES_TO_RECLEN(n) ( (((n)+1)/2) + 1 ) +#define HERMES_RECLEN_TO_BYTES(n) ( ((n)-1) * 2 ) + +/* Note that for the next two, the count is in 16-bit words, not bytes */ +static inline void hermes_read_words(struct hermes *hw, int off, void *buf, unsigned count) +{ + off = off << hw->reg_spacing;; + + if (hw->io_space) { + insw(hw->iobase + off, buf, count); + } else { + unsigned i; + u16 *p; + + /* This needs to *not* byteswap (like insw()) but + * readw() does byteswap hence the conversion. I hope + * gcc is smart enough to fold away the two swaps on + * big-endian platforms. */ + for (i = 0, p = buf; i < count; i++) { + *p++ = cpu_to_le16(readw(hw->iobase + off)); + } + } +} + +static inline void hermes_write_words(struct hermes *hw, int off, const void *buf, unsigned count) +{ + off = off << hw->reg_spacing;; + + if (hw->io_space) { + outsw(hw->iobase + off, buf, count); + } else { + unsigned i; + const u16 *p; + + /* This needs to *not* byteswap (like outsw()) but + * writew() does byteswap hence the conversion. I + * hope gcc is smart enough to fold away the two swaps + * on big-endian platforms. */ + for (i = 0, p = buf; i < count; i++) { + writew(le16_to_cpu(*p++), hw->iobase + off); + } + } +} + +static inline void hermes_clear_words(struct hermes *hw, int off, unsigned count) +{ + unsigned i; + + off = off << hw->reg_spacing;; + + if (hw->io_space) { + for (i = 0; i < count; i++) + outw(0, hw->iobase + off); + } else { + for (i = 0; i < count; i++) + writew(0, hw->iobase + off); + } +} + +#define HERMES_READ_RECORD(hw, bap, rid, buf) \ + (hermes_read_ltv((hw),(bap),(rid), sizeof(*buf), NULL, (buf))) +#define HERMES_WRITE_RECORD(hw, bap, rid, buf) \ + (hermes_write_ltv((hw),(bap),(rid),HERMES_BYTES_TO_RECLEN(sizeof(*buf)),(buf))) + +static inline int hermes_read_wordrec(hermes_t *hw, int bap, u16 rid, u16 *word) +{ + u16 rec; + int err; + + err = HERMES_READ_RECORD(hw, bap, rid, &rec); + *word = le16_to_cpu(rec); + return err; +} + +static inline int hermes_write_wordrec(hermes_t *hw, int bap, u16 rid, u16 word) +{ + u16 rec = cpu_to_le16(word); + return HERMES_WRITE_RECORD(hw, bap, rid, &rec); +} + +#else /* ! __KERNEL__ */ + +/* These are provided for the benefit of userspace drivers and testing programs + which use ioperm() or iopl() */ + +#define hermes_read_reg(base, off) (inw((base) + (off))) +#define hermes_write_reg(base, off, val) (outw((val), (base) + (off))) + +#define hermes_read_regn(base, name) (hermes_read_reg((base), HERMES_##name)) +#define hermes_write_regn(base, name, val) (hermes_write_reg((base), HERMES_##name, (val))) + +/* Note that for the next two, the count is in 16-bit words, not bytes */ +#define hermes_read_data(base, off, buf, count) (insw((base) + (off), (buf), (count))) +#define hermes_write_data(base, off, buf, count) (outsw((base) + (off), (buf), (count))) + +#endif /* ! __KERNEL__ */ + +#endif /* _HERMES_H */ diff --git a/linux/pcmcia-cs/wireless/hermes_rid.h b/linux/pcmcia-cs/wireless/hermes_rid.h new file mode 100644 index 0000000..761c542 --- /dev/null +++ b/linux/pcmcia-cs/wireless/hermes_rid.h @@ -0,0 +1,153 @@ +#ifndef _HERMES_RID_H +#define _HERMES_RID_H + +/* + * Configuration RIDs + */ +#define HERMES_RID_CNFPORTTYPE 0xFC00 /* used */ +#define HERMES_RID_CNFOWNMACADDR 0xFC01 /* used */ +#define HERMES_RID_CNFDESIREDSSID 0xFC02 /* used */ +#define HERMES_RID_CNFOWNCHANNEL 0xFC03 /* used */ +#define HERMES_RID_CNFOWNSSID 0xFC04 /* used */ +#define HERMES_RID_CNFOWNATIMWINDOW 0xFC05 +#define HERMES_RID_CNFSYSTEMSCALE 0xFC06 /* used */ +#define HERMES_RID_CNFMAXDATALEN 0xFC07 +#define HERMES_RID_CNFWDSADDRESS 0xFC08 +#define HERMES_RID_CNFPMENABLED 0xFC09 /* used */ +#define HERMES_RID_CNFPMEPS 0xFC0A +#define HERMES_RID_CNFMULTICASTRECEIVE 0xFC0B /* used */ +#define HERMES_RID_CNFMAXSLEEPDURATION 0xFC0C /* used */ +#define HERMES_RID_CNFPMHOLDOVERDURATION 0xFC0D /* used */ +#define HERMES_RID_CNFOWNNAME 0xFC0E /* used */ +#define HERMES_RID_CNFOWNDTIMPERIOD 0xFC10 +#define HERMES_RID_CNFWDSADDRESS1 0xFC11 +#define HERMES_RID_CNFWDSADDRESS2 0xFC12 +#define HERMES_RID_CNFWDSADDRESS3 0xFC13 +#define HERMES_RID_CNFWDSADDRESS4 0xFC14 +#define HERMES_RID_CNFWDSADDRESS5 0xFC15 +#define HERMES_RID_CNFWDSADDRESS6 0xFC16 +#define HERMES_RID_CNFMULTICASTPMBUFFERING 0xFC17 +#define HERMES_RID_CNFWEPENABLED_AGERE 0xFC20 /* used */ +#define HERMES_RID_CNFMANDATORYBSSID_SYMBOL 0xFC21 +#define HERMES_RID_CNFWEPDEFAULTKEYID 0xFC23 /* used */ +#define HERMES_RID_CNFDEFAULTKEY0 0xFC24 /* used */ +#define HERMES_RID_CNFDEFAULTKEY1 0xFC25 /* used */ +#define HERMES_RID_CNFMWOROBUST_AGERE 0xFC25 /* used */ +#define HERMES_RID_CNFDEFAULTKEY2 0xFC26 /* used */ +#define HERMES_RID_CNFDEFAULTKEY3 0xFC27 /* used */ +#define HERMES_RID_CNFWEPFLAGS_INTERSIL 0xFC28 /* used */ +#define HERMES_RID_CNFWEPKEYMAPPINGTABLE 0xFC29 +#define HERMES_RID_CNFAUTHENTICATION 0xFC2A /* used */ +#define HERMES_RID_CNFMAXASSOCSTA 0xFC2B +#define HERMES_RID_CNFKEYLENGTH_SYMBOL 0xFC2B +#define HERMES_RID_CNFTXCONTROL 0xFC2C +#define HERMES_RID_CNFROAMINGMODE 0xFC2D +#define HERMES_RID_CNFHOSTAUTHENTICATION 0xFC2E +#define HERMES_RID_CNFRCVCRCERROR 0xFC30 +#define HERMES_RID_CNFMMLIFE 0xFC31 +#define HERMES_RID_CNFALTRETRYCOUNT 0xFC32 +#define HERMES_RID_CNFBEACONINT 0xFC33 +#define HERMES_RID_CNFAPPCFINFO 0xFC34 +#define HERMES_RID_CNFSTAPCFINFO 0xFC35 +#define HERMES_RID_CNFPRIORITYQUSAGE 0xFC37 +#define HERMES_RID_CNFTIMCTRL 0xFC40 +#define HERMES_RID_CNFTHIRTY2TALLY 0xFC42 +#define HERMES_RID_CNFENHSECURITY 0xFC43 +#define HERMES_RID_CNFGROUPADDRESSES 0xFC80 /* used */ +#define HERMES_RID_CNFCREATEIBSS 0xFC81 /* used */ +#define HERMES_RID_CNFFRAGMENTATIONTHRESHOLD 0xFC82 /* used */ +#define HERMES_RID_CNFRTSTHRESHOLD 0xFC83 /* used */ +#define HERMES_RID_CNFTXRATECONTROL 0xFC84 /* used */ +#define HERMES_RID_CNFPROMISCUOUSMODE 0xFC85 /* used */ +#define HERMES_RID_CNFBASICRATES_SYMBOL 0xFC8A +#define HERMES_RID_CNFPREAMBLE_SYMBOL 0xFC8C /* used */ +#define HERMES_RID_CNFFRAGMENTATIONTHRESHOLD0 0xFC90 +#define HERMES_RID_CNFFRAGMENTATIONTHRESHOLD1 0xFC91 +#define HERMES_RID_CNFFRAGMENTATIONTHRESHOLD2 0xFC92 +#define HERMES_RID_CNFFRAGMENTATIONTHRESHOLD3 0xFC93 +#define HERMES_RID_CNFFRAGMENTATIONTHRESHOLD4 0xFC94 +#define HERMES_RID_CNFFRAGMENTATIONTHRESHOLD5 0xFC95 +#define HERMES_RID_CNFFRAGMENTATIONTHRESHOLD6 0xFC96 +#define HERMES_RID_CNFRTSTHRESHOLD0 0xFC97 +#define HERMES_RID_CNFRTSTHRESHOLD1 0xFC98 +#define HERMES_RID_CNFRTSTHRESHOLD2 0xFC99 +#define HERMES_RID_CNFRTSTHRESHOLD3 0xFC9A +#define HERMES_RID_CNFRTSTHRESHOLD4 0xFC9B +#define HERMES_RID_CNFRTSTHRESHOLD5 0xFC9C +#define HERMES_RID_CNFRTSTHRESHOLD6 0xFC9D +#define HERMES_RID_CNFSHORTPREAMBLE 0xFCB0 +#define HERMES_RID_CNFWEPKEYS_AGERE 0xFCB0 /* used */ +#define HERMES_RID_CNFEXCLUDELONGPREAMBLE 0xFCB1 +#define HERMES_RID_CNFTXKEY_AGERE 0xFCB1 /* used */ +#define HERMES_RID_CNFAUTHENTICATIONRSPTO 0xFCB2 +#define HERMES_RID_CNFBASICRATES 0xFCB3 +#define HERMES_RID_CNFSUPPORTEDRATES 0xFCB4 +#define HERMES_RID_CNFTICKTIME 0xFCE0 /* used */ +#define HERMES_RID_CNFSCANREQUEST 0xFCE1 +#define HERMES_RID_CNFJOINREQUEST 0xFCE2 +#define HERMES_RID_CNFAUTHENTICATESTATION 0xFCE3 +#define HERMES_RID_CNFCHANNELINFOREQUEST 0xFCE4 + +/* + * Information RIDs + */ +#define HERMES_RID_MAXLOADTIME 0xFD00 +#define HERMES_RID_DOWNLOADBUFFER 0xFD01 +#define HERMES_RID_PRIID 0xFD02 +#define HERMES_RID_PRISUPRANGE 0xFD03 +#define HERMES_RID_CFIACTRANGES 0xFD04 +#define HERMES_RID_NICSERNUM 0xFD0A +#define HERMES_RID_NICID 0xFD0B +#define HERMES_RID_MFISUPRANGE 0xFD0C +#define HERMES_RID_CFISUPRANGE 0xFD0D +#define HERMES_RID_CHANNELLIST 0xFD10 /* used */ +#define HERMES_RID_REGULATORYDOMAINS 0xFD11 +#define HERMES_RID_TEMPTYPE 0xFD12 +#define HERMES_RID_CIS 0xFD13 +#define HERMES_RID_STAID 0xFD20 /* used */ +#define HERMES_RID_STASUPRANGE 0xFD21 +#define HERMES_RID_MFIACTRANGES 0xFD22 +#define HERMES_RID_CFIACTRANGES2 0xFD23 +#define HERMES_RID_SECONDARYVERSION_SYMBOL 0xFD24 /* used */ +#define HERMES_RID_PORTSTATUS 0xFD40 +#define HERMES_RID_CURRENTSSID 0xFD41 /* used */ +#define HERMES_RID_CURRENTBSSID 0xFD42 /* used */ +#define HERMES_RID_COMMSQUALITY 0xFD43 /* used */ +#define HERMES_RID_CURRENTTXRATE 0xFD44 /* used */ +#define HERMES_RID_CURRENTBEACONINTERVAL 0xFD45 +#define HERMES_RID_CURRENTSCALETHRESHOLDS 0xFD46 +#define HERMES_RID_PROTOCOLRSPTIME 0xFD47 +#define HERMES_RID_SHORTRETRYLIMIT 0xFD48 /* used */ +#define HERMES_RID_LONGRETRYLIMIT 0xFD49 /* used */ +#define HERMES_RID_MAXTRANSMITLIFETIME 0xFD4A /* used */ +#define HERMES_RID_MAXRECEIVELIFETIME 0xFD4B +#define HERMES_RID_CFPOLLABLE 0xFD4C +#define HERMES_RID_AUTHENTICATIONALGORITHMS 0xFD4D +#define HERMES_RID_PRIVACYOPTIONIMPLEMENTED 0xFD4F +#define HERMES_RID_CURRENTTXRATE1 0xFD80 +#define HERMES_RID_CURRENTTXRATE2 0xFD81 +#define HERMES_RID_CURRENTTXRATE3 0xFD82 +#define HERMES_RID_CURRENTTXRATE4 0xFD83 +#define HERMES_RID_CURRENTTXRATE5 0xFD84 +#define HERMES_RID_CURRENTTXRATE6 0xFD85 +#define HERMES_RID_OWNMACADDR 0xFD86 +#define HERMES_RID_SCANRESULTSTABLE 0xFD88 +#define HERMES_RID_PHYTYPE 0xFDC0 +#define HERMES_RID_CURRENTCHANNEL 0xFDC1 /* used */ +#define HERMES_RID_CURRENTPOWERSTATE 0xFDC2 +#define HERMES_RID_CCAMODE 0xFDC3 +#define HERMES_RID_SUPPORTEDDATARATES 0xFDC6 /* used */ +#define HERMES_RID_BUILDSEQ 0xFFFE +#define HERMES_RID_FWID 0xFFFF + +/* "ID" structure - used for ESSID and station nickname */ +struct hermes_idstring { + u16 len; + u16 val[16]; +} __attribute__ ((packed)); + +typedef struct hermes_multicast { + u8 addr[HERMES_MAX_MULTICAST][ETH_ALEN]; +} __attribute__ ((packed)) hermes_multicast_t; + +#endif diff --git a/linux/pcmcia-cs/wireless/ieee802_11.h b/linux/pcmcia-cs/wireless/ieee802_11.h new file mode 100644 index 0000000..07d626e --- /dev/null +++ b/linux/pcmcia-cs/wireless/ieee802_11.h @@ -0,0 +1,79 @@ +#ifndef _IEEE802_11_H +#define _IEEE802_11_H + +#define IEEE802_11_DATA_LEN 2304 +/* Maximum size for the MA-UNITDATA primitive, 802.11 standard section + 6.2.1.1.2. + + The figure in section 7.1.2 suggests a body size of up to 2312 + bytes is allowed, which is a bit confusing, I suspect this + represents the 2304 bytes of real data, plus a possible 8 bytes of + WEP IV and ICV. (this interpretation suggested by Ramiro Barreiro) */ + + +#define IEEE802_11_HLEN 30 +#define IEEE802_11_FRAME_LEN (IEEE802_11_DATA_LEN + IEEE802_11_HLEN) + +struct ieee802_11_hdr { + u16 frame_ctl; + u16 duration_id; + u8 addr1[ETH_ALEN]; + u8 addr2[ETH_ALEN]; + u8 addr3[ETH_ALEN]; + u16 seq_ctl; + u8 addr4[ETH_ALEN]; +} __attribute__ ((packed)); + +/* Frame control field constants */ +#define IEEE802_11_FCTL_VERS 0x0002 +#define IEEE802_11_FCTL_FTYPE 0x000c +#define IEEE802_11_FCTL_STYPE 0x00f0 +#define IEEE802_11_FCTL_TODS 0x0100 +#define IEEE802_11_FCTL_FROMDS 0x0200 +#define IEEE802_11_FCTL_MOREFRAGS 0x0400 +#define IEEE802_11_FCTL_RETRY 0x0800 +#define IEEE802_11_FCTL_PM 0x1000 +#define IEEE802_11_FCTL_MOREDATA 0x2000 +#define IEEE802_11_FCTL_WEP 0x4000 +#define IEEE802_11_FCTL_ORDER 0x8000 + +#define IEEE802_11_FTYPE_MGMT 0x0000 +#define IEEE802_11_FTYPE_CTL 0x0004 +#define IEEE802_11_FTYPE_DATA 0x0008 + +/* management */ +#define IEEE802_11_STYPE_ASSOC_REQ 0x0000 +#define IEEE802_11_STYPE_ASSOC_RESP 0x0010 +#define IEEE802_11_STYPE_REASSOC_REQ 0x0020 +#define IEEE802_11_STYPE_REASSOC_RESP 0x0030 +#define IEEE802_11_STYPE_PROBE_REQ 0x0040 +#define IEEE802_11_STYPE_PROBE_RESP 0x0050 +#define IEEE802_11_STYPE_BEACON 0x0080 +#define IEEE802_11_STYPE_ATIM 0x0090 +#define IEEE802_11_STYPE_DISASSOC 0x00A0 +#define IEEE802_11_STYPE_AUTH 0x00B0 +#define IEEE802_11_STYPE_DEAUTH 0x00C0 + +/* control */ +#define IEEE802_11_STYPE_PSPOLL 0x00A0 +#define IEEE802_11_STYPE_RTS 0x00B0 +#define IEEE802_11_STYPE_CTS 0x00C0 +#define IEEE802_11_STYPE_ACK 0x00D0 +#define IEEE802_11_STYPE_CFEND 0x00E0 +#define IEEE802_11_STYPE_CFENDACK 0x00F0 + +/* data */ +#define IEEE802_11_STYPE_DATA 0x0000 +#define IEEE802_11_STYPE_DATA_CFACK 0x0010 +#define IEEE802_11_STYPE_DATA_CFPOLL 0x0020 +#define IEEE802_11_STYPE_DATA_CFACKPOLL 0x0030 +#define IEEE802_11_STYPE_NULLFUNC 0x0040 +#define IEEE802_11_STYPE_CFACK 0x0050 +#define IEEE802_11_STYPE_CFPOLL 0x0060 +#define IEEE802_11_STYPE_CFACKPOLL 0x0070 + +#define IEEE802_11_SCTL_FRAG 0x000F +#define IEEE802_11_SCTL_SEQ 0xFFF0 + +#endif /* _IEEE802_11_H */ + diff --git a/linux/pcmcia-cs/wireless/orinoco.c b/linux/pcmcia-cs/wireless/orinoco.c new file mode 100644 index 0000000..ed7bb83 --- /dev/null +++ b/linux/pcmcia-cs/wireless/orinoco.c @@ -0,0 +1,4211 @@ +/* orinoco.c 0.13e - (formerly known as dldwd_cs.c and orinoco_cs.c) + * + * A driver for Hermes or Prism 2 chipset based PCMCIA wireless + * adaptors, with Lucent/Agere, Intersil or Symbol firmware. + * + * Copyright (C) 2000 David Gibson, Linuxcare Australia <hermes@gibson.dropbear.id.au> + * With some help from : + * Copyright (C) 2001 Jean Tourrilhes, HP Labs <jt@hpl.hp.com> + * Copyright (C) 2001 Benjamin Herrenschmidt <benh@kernel.crashing.org> + * + * Based on dummy_cs.c 1.27 2000/06/12 21:27:25 + * + * Portions based on wvlan_cs.c 1.0.6, Copyright Andreas Neuhaus <andy@fasta.fh-dortmund.de> + * http://www.fasta.fh-dortmund.de/users/andy/wvlan/ + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David + * A. Hinds are Copyright (C) 1999 David A. Hinds. All Rights + * Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in + * which case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use your + * version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. */ + +/* + * v0.01 -> v0.02 - 21/3/2001 - Jean II + * o Allow to use regular ethX device name instead of dldwdX + * o Warning on IBSS with ESSID=any for firmware 6.06 + * o Put proper range.throughput values (optimistic) + * o IWSPY support (IOCTL and stat gather in Rx path) + * o Allow setting frequency in Ad-Hoc mode + * o Disable WEP setting if !has_wep to work on old firmware + * o Fix txpower range + * o Start adding support for Samsung/Compaq firmware + * + * v0.02 -> v0.03 - 23/3/2001 - Jean II + * o Start adding Symbol support - need to check all that + * o Fix Prism2/Symbol WEP to accept 128 bits keys + * o Add Symbol WEP (add authentication type) + * o Add Prism2/Symbol rate + * o Add PM timeout (holdover duration) + * o Enable "iwconfig eth0 key off" and friends (toggle flags) + * o Enable "iwconfig eth0 power unicast/all" (toggle flags) + * o Try with an intel card. It report firmware 1.01, behave like + * an antiquated firmware, however on windows it says 2.00. Yuck ! + * o Workaround firmware bug in allocate buffer (Intel 1.01) + * o Finish external renaming to orinoco... + * o Testing with various Wavelan firmwares + * + * v0.03 -> v0.04 - 30/3/2001 - Jean II + * o Update to Wireless 11 -> add retry limit/lifetime support + * o Tested with a D-Link DWL 650 card, fill in firmware support + * o Warning on Vcc mismatch (D-Link 3.3v card in Lucent 5v only slot) + * o Fixed the Prims2 WEP bugs that I introduced in v0.03 :-( + * It work on D-Link *only* after a tcpdump. Weird... + * And still doesn't work on Intel card. Grrrr... + * o Update the mode after a setport3 + * o Add preamble setting for Symbol cards (not yet enabled) + * o Don't complain as much about Symbol cards... + * + * v0.04 -> v0.04b - 22/4/2001 - David Gibson + * o Removed the 'eth' parameter - always use ethXX as the + * interface name instead of dldwdXX. The other was racy + * anyway. + * o Clean up RID definitions in hermes.h, other cleanups + * + * v0.04b -> v0.04c - 24/4/2001 - Jean II + * o Tim Hurley <timster@seiki.bliztech.com> reported a D-Link card + * with vendor 02 and firmware 0.08. Added in the capabilities... + * o Tested Lucent firmware 7.28, everything works... + * + * v0.04c -> v0.05 - 3/5/2001 - Benjamin Herrenschmidt + * o Spin-off Pcmcia code. This file is renamed orinoco.c, + * and orinoco_cs.c now contains only the Pcmcia specific stuff + * o Add Airport driver support on top of orinoco.c (see airport.c) + * + * v0.05 -> v0.05a - 4/5/2001 - Jean II + * o Revert to old Pcmcia code to fix breakage of Ben's changes... + * + * v0.05a -> v0.05b - 4/5/2001 - Jean II + * o add module parameter 'ignore_cis_vcc' for D-Link @ 5V + * o D-Link firmware doesn't support multicast. We just print a few + * error messages, but otherwise everything works... + * o For David : set/getport3 works fine, just upgrade iwpriv... + * + * v0.05b -> v0.05c - 5/5/2001 - Benjamin Herrenschmidt + * o Adapt airport.c to latest changes in orinoco.c + * o Remove deferred power enabling code + * + * v0.05c -> v0.05d - 5/5/2001 - Jean II + * o Workaround to SNAP decapsulate frame from LinkSys AP + * original patch from : Dong Liu <dliu@research.bell-labs.com> + * (note : the memcmp bug was mine - fixed) + * o Remove set_retry stuff, no firmware support it (bloat--). + * + * v0.05d -> v0.06 - 25/5/2001 - Jean II + * Original patch from "Hong Lin" <alin@redhat.com>, + * "Ian Kinner" <ikinner@redhat.com> + * and "David Smith" <dsmith@redhat.com> + * o Init of priv->tx_rate_ctrl in firmware specific section. + * o Prism2/Symbol rate, upto should be 0xF and not 0x15. Doh ! + * o Spectrum card always need cor_reset (for every reset) + * o Fix cor_reset to not lose bit 7 in the register + * o flush_stale_links to remove zombie Pcmcia instances + * o Ack previous hermes event before reset + * Me (with my little hands) + * o Allow orinoco.c to call cor_reset via priv->card_reset_handler + * o Add priv->need_card_reset to toggle this feature + * o Fix various buglets when setting WEP in Symbol firmware + * Now, encryption is fully functional on Symbol cards. Youpi ! + * + * v0.06 -> v0.06b - 25/5/2001 - Jean II + * o IBSS on Symbol use port_mode = 4. Please don't ask... + * + * v0.06b -> v0.06c - 29/5/2001 - Jean II + * o Show first spy address in /proc/net/wireless for IBSS mode as well + * + * v0.06c -> v0.06d - 6/7/2001 - David Gibson + * o Change a bunch of KERN_INFO messages to KERN_DEBUG, as per Linus' + * wishes to reduce the number of unecessary messages. + * o Removed bogus message on CRC error. + * o Merged fixeds for v0.08 Prism 2 firmware from William Waghorn + * <willwaghorn@yahoo.co.uk> + * o Slight cleanup/re-arrangement of firmware detection code. + * + * v0.06d -> v0.06e - 1/8/2001 - David Gibson + * o Removed some redundant global initializers (orinoco_cs.c). + * o Added some module metadataa + * + * v0.06e -> v0.06f - 14/8/2001 - David Gibson + * o Wording fix to license + * o Added a 'use_alternate_encaps' module parameter for APs which need an + * oui of 00:00:00. We really need a better way of handling this, but + * the module flag is better than nothing for now. + * + * v0.06f -> v0.07 - 20/8/2001 - David Gibson + * o Removed BAP error retries from hermes_bap_seek(). For Tx we now + * let the upper layers handle the retry, we retry explicitly in the + * Rx path, but don't make as much noise about it. + * o Firmware detection cleanups. + * + * v0.07 -> v0.07a - 1/10/3001 - Jean II + * o Add code to read Symbol firmware revision, inspired by latest code + * in Spectrum24 by Lee John Keyser-Allen - Thanks Lee ! + * o Thanks to Jared Valentine <hidden@xmission.com> for "providing" me + * a 3Com card with a recent firmware, fill out Symbol firmware + * capabilities of latest rev (2.20), as well as older Symbol cards. + * o Disable Power Management in newer Symbol firmware, the API + * has changed (documentation needed). + * + * v0.07a -> v0.08 - 3/10/2001 - David Gibson + * o Fixed a possible buffer overrun found by the Stanford checker (in + * dldwd_ioctl_setiwencode()). Can only be called by root anyway, so not + * a big problem. + * o Turned has_big_wep on for Intersil cards. That's not true for all of + * them but we should at least let the capable ones try. + * o Wait for BUSY to clear at the beginning of hermes_bap_seek(). I + * realised that my assumption that the driver's serialization + * would prevent the BAP being busy on entry was possibly false, because + * things other than seeks may make the BAP busy. + * o Use "alternate" (oui 00:00:00) encapsulation by default. + * Setting use_old_encaps will mimic the old behaviour, but I think we + * will be able to eliminate this. + * o Don't try to make __initdata const (the version string). This can't + * work because of the way the __initdata sectioning works. + * o Added MODULE_LICENSE tags. + * o Support for PLX (transparent PCMCIA->PCI brdge) cards. + * o Changed to using the new type-facist min/max. + * + * v0.08 -> v0.08a - 9/10/2001 - David Gibson + * o Inserted some missing acknowledgements/info into the Changelog. + * o Fixed some bugs in the normalisation of signel level reporting. + * o Fixed bad bug in WEP key handling on Intersil and Symbol firmware, + * which led to an instant crash on big-endian machines. + * + * v0.08a -> v0.08b - 20/11/2001 - David Gibson + * o Lots of cleanup and bugfixes in orinoco_plx.c + * o Cleanup to handling of Tx rate setting. + * o Removed support for old encapsulation method. + * o Removed old "dldwd" names. + * o Split RID constants into a new file hermes_rid.h + * o Renamed RID constants to match linux-wlan-ng and prism2.o + * o Bugfixes in hermes.c + * o Poke the PLX's INTCSR register, so it actually starts + * generating interrupts. These cards might actually work now. + * o Update to wireless extensions v12 (Jean II) + * o Support for tallies and inquire command (Jean II) + * o Airport updates for newer PPC kernels (BenH) + * + * v0.08b -> v0.09 - 21/12/2001 - David Gibson + * o Some new PCI IDs for PLX cards. + * o Removed broken attempt to do ALLMULTI reception. Just use + * promiscuous mode instead + * o Preliminary work for list-AP (Jean II) + * o Airport updates from (BenH) + * o Eliminated racy hw_ready stuff + * o Fixed generation of fake events in irq handler. This should + * finally kill the EIO problems (Jean II & dgibson) + * o Fixed breakage of bitrate set/get on Agere firmware (Jean II) + * + * v0.09 -> v0.09a - 2/1/2002 - David Gibson + * o Fixed stupid mistake in multicast list handling, triggering + * a BUG() + * + * v0.09a -> v0.09b - 16/1/2002 - David Gibson + * o Fixed even stupider mistake in new interrupt handling, which + * seriously broke things on big-endian machines. + * o Removed a bunch of redundant includes and exports. + * o Removed a redundant MOD_{INC,DEC}_USE_COUNT pair in airport.c + * o Don't attempt to do hardware level multicast reception on + * Intersil firmware, just go promisc instead. + * o Typo fixed in hermes_issue_cmd() + * o Eliminated WIRELESS_SPY #ifdefs + * o Status code reported on Tx exceptions + * o Moved netif_wake_queue() from ALLOC interrupts to TX and TXEXC + * interrupts, which should fix the timeouts we're seeing. + * + * v0.09b -> v0.10 - 25 Feb 2002 - David Gibson + * o Removed nested structures used for header parsing, so the + * driver should now work without hackery on ARM + * o Fix for WEP handling on Intersil (Hawk Newton) + * o Eliminated the /proc/hermes/ethXX/regs debugging file. It + * was never very useful. + * o Make Rx errors less noisy. + * + * v0.10 -> v0.11 - 5 Apr 2002 - David Gibson + * o Laid the groundwork in hermes.[ch] for devices which map + * into PCI memory space rather than IO space. + * o Fixed bug in multicast handling (cleared multicast list when + * leaving promiscuous mode). + * o Relegated Tx error messages to debug. + * o Cleaned up / corrected handling of allocation lengths. + * o Set OWNSSID in IBSS mode for WinXP interoperability (jimc). + * o Change to using alloc_etherdev() for structure allocations. + * o Check for and drop undersized packets. + * o Fixed a race in stopping/waking the queue. This should fix + * the timeout problems (Pavel Roskin) + * o Reverted to netif_wake_queue() on the ALLOC event. + * o Fixes for recent Symbol firmwares which lack AP density + * (Pavel Roskin). + * + * v0.11 -> v0.11a - 29 Apr 2002 - David Gibson + * o Handle different register spacing, necessary for Prism 2.5 + * PCI adaptors (Steve Hill). + * o Cleaned up initialization of card structures in orinoco_cs + * and airport. Removed card->priv field. + * o Make response structure optional for hermes_docmd_wait() + * Pavel Roskin) + * o Added PCI id for Nortel emobility to orinoco_plx.c. + * o Cleanup to handling of Symbol's allocation bug. (Pavel Roskin) + * o Cleanups to firmware capability detection. + * o Arrange for orinoco_pci.c to override firmware detection. + * We should be able to support the PCI Intersil cards now. + * o Cleanup handling of reset_cor and hard_reset (Pavel Roskin). + * o Remove erroneous use of USER_BAP in the TxExc handler (Jouni + * Malinen). + * o Makefile changes for better integration into David Hinds + * pcmcia-cs package. + * + * v0.11a -> v0.11b - 1 May 2002 - David Gibson + * o Better error reporting in orinoco_plx_init_one() + * o Fixed multiple bad kfree() bugs introduced by the + * alloc_orinocodev() changes. + * + * v0.11b -> v0.12 - 19 Jun 2002 - David Gibson + * o Support changing the MAC address. + * o Correct display of Intersil firmware revision numbers. + * o Entirely revised locking scheme. Should be both simpler and + * better. + * o Merged some common code in orinoco_plx, orinoco_pci and + * airport by creating orinoco_default_{open,stop,reset}() + * which are used as the dev->open, dev->stop, priv->reset + * callbacks if none are specified when alloc_orinocodev() is + * called. + * o Removed orinoco_plx_interrupt() and orinoco_pci_interrupt(). + * They didn't do anything. + * + * v0.12 -> v0.12a - 4 Jul 2002 - David Gibson + * o Some rearrangement of code. + * o Numerous fixups to locking and rest handling, particularly + * for PCMCIA. + * o This allows open and stop net_device methods to be in + * orinoco.c now, rather than in the init modules. + * o In orinoco_cs.c link->priv now points to the struct + * net_device not to the struct orinoco_private. + * o Added a check for undersized SNAP frames, which could cause + * crashes. + * + * v0.12a -> v0.12b - 11 Jul 2002 - David Gibson + * o Fix hw->num_init testing code, so num_init is actually + * incremented. + * o Fix very stupid bug in orinoco_cs which broke compile with + * CONFIG_SMP. + * o Squashed a warning. + * + * v0.12b -> v0.12c - 26 Jul 2002 - David Gibson + * o Change to C9X style designated initializers. + * o Add support for 3Com AirConnect PCI. + * o No longer ignore the hard_reset argument to + * alloc_orinocodev(). Oops. + * + * v0.12c -> v0.13beta1 - 13 Sep 2002 - David Gibson + * o Revert the broken 0.12* locking scheme and go to a new yet + * simpler scheme. + * o Do firmware resets only in orinoco_init() and when waking + * the card from hard sleep. + * + * v0.13beta1 -> v0.13 - 27 Sep 2002 - David Gibson + * o Re-introduced full resets (via schedule_task()) on Tx + * timeout. + * + * v0.13 -> v0.13a - 30 Sep 2002 - David Gibson + * o Minor cleanups to info frame handling. Add basic support + * for linkstatus info frames. + * o Include required kernel headers in orinoco.h, to avoid + * compile problems. + * + * v0.13a -> v0.13b - 10 Feb 2003 - David Gibson + * o Implemented hard reset for Airport cards + * o Experimental suspend/resume implementation for orinoco_pci + * o Abolished /proc debugging support, replaced with a debugging + * iwpriv. Now it's ugly and simple instead of ugly and complex. + * o Bugfix in hermes.c if the firmware returned a record length + * of 0, we could go clobbering memory. + * o Bugfix in orinoco_stop() - it used to fail if hw_unavailable + * was set, which was usually true on PCMCIA hot removes. + * o Track LINKSTATUS messages, silently drop Tx packets before + * we are connected (avoids cofusing the firmware), and only + * give LINKSTATUS printk()s if the status has changed. + * + * v0.13b -> v0.13c - 11 Mar 2003 - David Gibson + * o Cleanup: use dev instead of priv in various places. + * o Bug fix: Don't ReleaseConfiguration on RESET_PHYSICAL event + * if we're in the middle of a (driver initiated) hard reset. + * o Bug fix: ETH_ZLEN is supposed to include the header + * (Dionysus Blazakis & Manish Karir) + * o Convert to using workqueues instead of taskqueues (and + * backwards compatibility macros for pre 2.5.41 kernels). + * o Drop redundant (I think...) MOD_{INC,DEC}_USE_COUNT in + * airport.c + * o New orinoco_tmd.c init module from Joerg Dorchain for + * TMD7160 based PCI to PCMCIA bridges (similar to + * orinoco_plx.c). + * + * v0.13c -> v0.13d - 22 Apr 2003 - David Gibson + * o Make hw_unavailable a counter, rather than just a flag, this + * is necessary to avoid some races (such as a card being + * removed in the middle of orinoco_reset(). + * o Restore Release/RequestConfiguration in the PCMCIA event handler + * when dealing with a driver initiated hard reset. This is + * necessary to prevent hangs due to a spurious interrupt while + * the reset is in progress. + * o Clear the 802.11 header when transmitting, even though we + * don't use it. This fixes a long standing bug on some + * firmwares, which seem to get confused if that isn't done. + * o Be less eager to de-encapsulate SNAP frames, only do so if + * the OUI is 00:00:00 or 00:00:f8, leave others alone. The old + * behaviour broke CDP (Cisco Discovery Protocol). + * o Use dev instead of priv for free_irq() as well as + * request_irq() (oops). + * o Attempt to reset rather than giving up if we get too many + * IRQs. + * o Changed semantics of __orinoco_down() so it can be called + * safely with hw_unavailable set. It also now clears the + * linkstatus (since we're going to have to reassociate). + * + * v0.13d -> v0.13e - 12 May 2003 - David Gibson + * o Support for post-2.5.68 return values from irq handler. + * o Fixed bug where underlength packets would be double counted + * in the rx_dropped statistics. + * o Provided a module parameter to suppress linkstatus messages. + * + * TODO + * o New wireless extensions API (patch from Moustafa + * Youssef, updated by Jim Carter and Pavel Roskin). + * o Handle de-encapsulation within network layer, provide 802.11 + * headers (patch from Thomas 'Dent' Mirlacher) + * o RF monitor mode support + * o Fix possible races in SPY handling. + * o Disconnect wireless extensions from fundamental configuration. + * o (maybe) Software WEP support (patch from Stano Meduna). + * o (maybe) Use multiple Tx buffers - driver handling queue + * rather than firmware. */ + +/* Locking and synchronization: + * + * The basic principle is that everything is serialized through a + * single spinlock, priv->lock. The lock is used in user, bh and irq + * context, so when taken outside hardirq context it should always be + * taken with interrupts disabled. The lock protects both the + * hardware and the struct orinoco_private. + * + * Another flag, priv->hw_unavailable indicates that the hardware is + * unavailable for an extended period of time (e.g. suspended, or in + * the middle of a hard reset). This flag is protected by the + * spinlock. All code which touches the hardware should check the + * flag after taking the lock, and if it is set, give up on whatever + * they are doing and drop the lock again. The orinoco_lock() + * function handles this (it unlocks and returns -EBUSY if + * hw_unavailable is non-zero). */ + +#include <linux/config.h> + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/ptrace.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/ioport.h> +#include <linux/netdevice.h> +#include <linux/if_arp.h> +#include <linux/etherdevice.h> +#include <linux/wireless.h> + +#include <asm/uaccess.h> +#include <asm/io.h> +#include <asm/system.h> + +#include "hermes.h" +#include "hermes_rid.h" +#include "orinoco.h" +#include "ieee802_11.h" + +/********************************************************************/ +/* Module information */ +/********************************************************************/ + +MODULE_AUTHOR("David Gibson <hermes@gibson.dropbear.id.au>"); +MODULE_DESCRIPTION("Driver for Lucent Orinoco, Prism II based and similar wireless cards"); +#ifdef MODULE_LICENSE +MODULE_LICENSE("Dual MPL/GPL"); +#endif + +/* Level of debugging. Used in the macros in orinoco.h */ +#ifdef ORINOCO_DEBUG +int orinoco_debug = ORINOCO_DEBUG; +MODULE_PARM(orinoco_debug, "i"); +EXPORT_SYMBOL(orinoco_debug); +#endif + +static int suppress_linkstatus; /* = 0 */ +MODULE_PARM(suppress_linkstatus, "i"); + +/********************************************************************/ +/* Compile time configuration and compatibility stuff */ +/********************************************************************/ + +/* Wireless extensions backwards compatibility */ +#ifndef SIOCIWFIRSTPRIV +#define SIOCIWFIRSTPRIV SIOCDEVPRIVATE +#endif /* SIOCIWFIRSTPRIV */ +#ifndef SIOCIWLASTPRIV +#define SIOCIWLASTPRIV SIOCDEVPRIVATE+0xF +#endif /* SIOCIWLASTPRIV */ + +/* We do this this way to avoid ifdefs in the actual code */ +#ifdef WIRELESS_SPY +#define SPY_NUMBER(priv) (priv->spy_number) +#else +#define SPY_NUMBER(priv) 0 +#endif /* WIRELESS_SPY */ + +/********************************************************************/ +/* Internal constants */ +/********************************************************************/ + +#define ORINOCO_MIN_MTU 256 +#define ORINOCO_MAX_MTU (IEEE802_11_DATA_LEN - ENCAPS_OVERHEAD) + +#define SYMBOL_MAX_VER_LEN (14) +#define USER_BAP 0 +#define IRQ_BAP 1 +#define MAX_IRQLOOPS_PER_IRQ 10 +#define MAX_IRQLOOPS_PER_JIFFY (20000/HZ) /* Based on a guestimate of + * how many events the + * device could + * legitimately generate */ +#define SMALL_KEY_SIZE 5 +#define LARGE_KEY_SIZE 13 +#define TX_NICBUF_SIZE_BUG 1585 /* Bug in Symbol firmware */ + +#define DUMMY_FID 0xFFFF + +#define RUP_EVEN(a) (((a) + 1) & (~1)) + +/*#define MAX_MULTICAST(priv) (priv->firmware_type == FIRMWARE_TYPE_AGERE ? \ + HERMES_MAX_MULTICAST : 0)*/ +#define MAX_MULTICAST(priv) (HERMES_MAX_MULTICAST) + +/********************************************************************/ +/* Data tables */ +/********************************************************************/ + +/* The frequency of each channel in MHz */ +const long channel_frequency[] = { + 2412, 2417, 2422, 2427, 2432, 2437, 2442, + 2447, 2452, 2457, 2462, 2467, 2472, 2484 +}; +#define NUM_CHANNELS ( sizeof(channel_frequency) / sizeof(channel_frequency[0]) ) + +/* This tables gives the actual meanings of the bitrate IDs returned by the firmware. */ +struct { + int bitrate; /* in 100s of kilobits */ + int automatic; + u16 agere_txratectrl; + u16 intersil_txratectrl; +} bitrate_table[] = { + {110, 1, 3, 15}, /* Entry 0 is the default */ + {10, 0, 1, 1}, + {10, 1, 1, 1}, + {20, 0, 2, 2}, + {20, 1, 6, 3}, + {55, 0, 4, 4}, + {55, 1, 7, 7}, + {110, 0, 5, 8}, +}; +#define BITRATE_TABLE_SIZE (sizeof(bitrate_table) / sizeof(bitrate_table[0])) + +/********************************************************************/ +/* Data types */ +/********************************************************************/ + +struct header_struct { + /* 802.3 */ + u8 dest[ETH_ALEN]; + u8 src[ETH_ALEN]; + u16 len; + /* 802.2 */ + u8 dsap; + u8 ssap; + u8 ctrl; + /* SNAP */ + u8 oui[3]; + u16 ethertype; +} __attribute__ ((packed)); + +/* 802.2 LLC/SNAP header used for Ethernet encapsulation over 802.11 */ +u8 encaps_hdr[] = {0xaa, 0xaa, 0x03, 0x00, 0x00, 0x00}; + +#define ENCAPS_OVERHEAD (sizeof(encaps_hdr) + 2) + +/********************************************************************/ +/* Function prototypes */ +/********************************************************************/ + +static void orinoco_stat_gather(struct net_device *dev, + struct sk_buff *skb, + struct hermes_rx_descriptor *desc); + +static struct net_device_stats *orinoco_get_stats(struct net_device *dev); +static struct iw_statistics *orinoco_get_wireless_stats(struct net_device *dev); + +/* Hardware control routines */ + +static int __orinoco_program_rids(struct net_device *dev); + +static int __orinoco_hw_set_bitrate(struct orinoco_private *priv); +static int __orinoco_hw_setup_wep(struct orinoco_private *priv); +static int orinoco_hw_get_bssid(struct orinoco_private *priv, char buf[ETH_ALEN]); +static int orinoco_hw_get_essid(struct orinoco_private *priv, int *active, + char buf[IW_ESSID_MAX_SIZE+1]); +static long orinoco_hw_get_freq(struct orinoco_private *priv); +static int orinoco_hw_get_bitratelist(struct orinoco_private *priv, int *numrates, + s32 *rates, int max); +static void __orinoco_set_multicast_list(struct net_device *dev); + +/* Interrupt handling routines */ +static void __orinoco_ev_tick(struct net_device *dev, hermes_t *hw); +static void __orinoco_ev_wterr(struct net_device *dev, hermes_t *hw); +static void __orinoco_ev_infdrop(struct net_device *dev, hermes_t *hw); +static void __orinoco_ev_info(struct net_device *dev, hermes_t *hw); +static void __orinoco_ev_rx(struct net_device *dev, hermes_t *hw); +static void __orinoco_ev_txexc(struct net_device *dev, hermes_t *hw); +static void __orinoco_ev_tx(struct net_device *dev, hermes_t *hw); +static void __orinoco_ev_alloc(struct net_device *dev, hermes_t *hw); + +/* ioctl() routines */ +static int orinoco_debug_dump_recs(struct net_device *dev); + +/********************************************************************/ +/* Function prototypes */ +/********************************************************************/ + +int __orinoco_up(struct net_device *dev) +{ + struct orinoco_private *priv = dev->priv; + struct hermes *hw = &priv->hw; + int err; + + err = __orinoco_program_rids(dev); + if (err) { + printk(KERN_ERR "%s: Error %d configuring card\n", + dev->name, err); + return err; + } + + /* Fire things up again */ + hermes_set_irqmask(hw, ORINOCO_INTEN); + err = hermes_enable_port(hw, 0); + if (err) { + printk(KERN_ERR "%s: Error %d enabling MAC port\n", + dev->name, err); + return err; + } + + netif_start_queue(dev); + netif_mark_up(dev); + + return 0; +} + +int __orinoco_down(struct net_device *dev) +{ + struct orinoco_private *priv = dev->priv; + struct hermes *hw = &priv->hw; + int err; + + netif_stop_queue(dev); + netif_mark_down(dev); + + if (! priv->hw_unavailable) { + if (! priv->broken_disableport) { + err = hermes_disable_port(hw, 0); + if (err) { + /* Some firmwares (e.g. Intersil 1.3.x) seem + * to have problems disabling the port, oh + * well, too bad. */ + printk(KERN_WARNING "%s: Error %d disabling MAC port\n", + dev->name, err); + priv->broken_disableport = 1; + } + } + hermes_set_irqmask(hw, 0); + hermes_write_regn(hw, EVACK, 0xffff); + } + + /* firmware will have to reassociate */ + priv->last_linkstatus = 0xffff; + priv->connected = 0; + + return 0; +} + +int orinoco_reinit_firmware(struct net_device *dev) +{ + struct orinoco_private *priv = dev->priv; + struct hermes *hw = &priv->hw; + int err; + + err = hermes_init(hw); + if (err) + return err; + + err = hermes_allocate(hw, priv->nicbuf_size, &priv->txfid); + if (err == -EIO) { + /* Try workaround for old Symbol firmware bug */ + printk(KERN_WARNING "%s: firmware ALLOC bug detected " + "(old Symbol firmware?). Trying to work around... ", + dev->name); + + priv->nicbuf_size = TX_NICBUF_SIZE_BUG; + err = hermes_allocate(hw, priv->nicbuf_size, &priv->txfid); + if (err) + printk("failed!\n"); + else + printk("ok.\n"); + } + + return err; +} + +static int orinoco_open(struct net_device *dev) +{ + struct orinoco_private *priv = dev->priv; + unsigned long flags; + int err; + + err = orinoco_lock(priv, &flags); + if (err) + return err; + + err = __orinoco_up(dev); + + if (! err) + priv->open = 1; + + orinoco_unlock(priv, &flags); + + return err; +} + +int orinoco_stop(struct net_device *dev) +{ + struct orinoco_private *priv = dev->priv; + int err = 0; + + /* We mustn't use orinoco_lock() here, because we need to be + able to close the interface even if hw_unavailable is set + (e.g. as we're released after a PC Card removal) */ + spin_lock_irq(&priv->lock); + + priv->open = 0; + + err = __orinoco_down(dev); + + spin_unlock_irq(&priv->lock); + + return err; +} + +static int __orinoco_program_rids(struct net_device *dev) +{ + struct orinoco_private *priv = dev->priv; + hermes_t *hw = &priv->hw; + int err; + struct hermes_idstring idbuf; + + /* Set the MAC address */ + err = hermes_write_ltv(hw, USER_BAP, HERMES_RID_CNFOWNMACADDR, + HERMES_BYTES_TO_RECLEN(ETH_ALEN), dev->dev_addr); + if (err) { + printk(KERN_ERR "%s: Error %d setting MAC address\n", dev->name, err); + return err; + } + + /* Set up the link mode */ + err = hermes_write_wordrec(hw, USER_BAP, HERMES_RID_CNFPORTTYPE, priv->port_type); + if (err) { + printk(KERN_ERR "%s: Error %d setting port type\n", dev->name, err); + return err; + } + /* Set the channel/frequency */ + if (priv->channel == 0) { + printk(KERN_DEBUG "%s: Channel is 0 in __orinoco_program_rids()\n", dev->name); + if (priv->createibss) + priv->channel = 10; + } + err = hermes_write_wordrec(hw, USER_BAP, HERMES_RID_CNFOWNCHANNEL, priv->channel); + if (err) { + printk(KERN_ERR "%s: Error %d setting channel\n", dev->name, err); + return err; + } + + if (priv->has_ibss) { + err = hermes_write_wordrec(hw, USER_BAP, HERMES_RID_CNFCREATEIBSS, + priv->createibss); + if (err) { + printk(KERN_ERR "%s: Error %d setting CREATEIBSS\n", dev->name, err); + return err; + } + + if ((strlen(priv->desired_essid) == 0) && (priv->createibss) + && (!priv->has_ibss_any)) { + printk(KERN_WARNING "%s: This firmware requires an \ +ESSID in IBSS-Ad-Hoc mode.\n", dev->name); + /* With wvlan_cs, in this case, we would crash. + * hopefully, this driver will behave better... + * Jean II */ + } + } + + /* Set the desired ESSID */ + idbuf.len = cpu_to_le16(strlen(priv->desired_essid)); + memcpy(&idbuf.val, priv->desired_essid, sizeof(idbuf.val)); + /* WinXP wants partner to configure OWNSSID even in IBSS mode. (jimc) */ + err = hermes_write_ltv(hw, USER_BAP, HERMES_RID_CNFOWNSSID, + HERMES_BYTES_TO_RECLEN(strlen(priv->desired_essid)+2), + &idbuf); + if (err) { + printk(KERN_ERR "%s: Error %d setting OWNSSID\n", dev->name, err); + return err; + } + err = hermes_write_ltv(hw, USER_BAP, HERMES_RID_CNFDESIREDSSID, + HERMES_BYTES_TO_RECLEN(strlen(priv->desired_essid)+2), + &idbuf); + if (err) { + printk(KERN_ERR "%s: Error %d setting DESIREDSSID\n", dev->name, err); + return err; + } + + /* Set the station name */ + idbuf.len = cpu_to_le16(strlen(priv->nick)); + memcpy(&idbuf.val, priv->nick, sizeof(idbuf.val)); + err = hermes_write_ltv(hw, USER_BAP, HERMES_RID_CNFOWNNAME, + HERMES_BYTES_TO_RECLEN(strlen(priv->nick)+2), + &idbuf); + if (err) { + printk(KERN_ERR "%s: Error %d setting nickname\n", dev->name, err); + return err; + } + + /* Set AP density */ + if (priv->has_sensitivity) { + err = hermes_write_wordrec(hw, USER_BAP, HERMES_RID_CNFSYSTEMSCALE, + priv->ap_density); + if (err) { + printk(KERN_WARNING "%s: Error %d setting SYSTEMSCALE. " + "Disabling sensitivity control\n", dev->name, err); + + priv->has_sensitivity = 0; + } + } + + /* Set RTS threshold */ + err = hermes_write_wordrec(hw, USER_BAP, HERMES_RID_CNFRTSTHRESHOLD, priv->rts_thresh); + if (err) { + printk(KERN_ERR "%s: Error %d setting RTS threshold\n", dev->name, err); + return err; + } + + /* Set fragmentation threshold or MWO robustness */ + if (priv->has_mwo) + err = hermes_write_wordrec(hw, USER_BAP, + HERMES_RID_CNFMWOROBUST_AGERE, + priv->mwo_robust); + else + err = hermes_write_wordrec(hw, USER_BAP, + HERMES_RID_CNFFRAGMENTATIONTHRESHOLD, + priv->frag_thresh); + if (err) { + printk(KERN_ERR "%s: Error %d setting framentation\n", dev->name, err); + return err; + } + + /* Set bitrate */ + err = __orinoco_hw_set_bitrate(priv); + if (err) { + printk(KERN_ERR "%s: Error %d setting bitrate\n", dev->name, err); + return err; + } + + /* Set power management */ + if (priv->has_pm) { + err = hermes_write_wordrec(hw, USER_BAP, HERMES_RID_CNFPMENABLED, + priv->pm_on); + if (err) { + printk(KERN_ERR "%s: Error %d setting up PM\n", + dev->name, err); + return err; + } + + err = hermes_write_wordrec(hw, USER_BAP, + HERMES_RID_CNFMULTICASTRECEIVE, + priv->pm_mcast); + if (err) { + printk(KERN_ERR "%s: Error %d setting up PM\n", + dev->name, err); + return err; + } + err = hermes_write_wordrec(hw, USER_BAP, + HERMES_RID_CNFMAXSLEEPDURATION, + priv->pm_period); + if (err) { + printk(KERN_ERR "%s: Error %d setting up PM\n", + dev->name, err); + return err; + } + err = hermes_write_wordrec(hw, USER_BAP, + HERMES_RID_CNFPMHOLDOVERDURATION, + priv->pm_timeout); + if (err) { + printk(KERN_ERR "%s: Error %d setting up PM\n", + dev->name, err); + return err; + } + } + + /* Set preamble - only for Symbol so far... */ + if (priv->has_preamble) { + err = hermes_write_wordrec(hw, USER_BAP, + HERMES_RID_CNFPREAMBLE_SYMBOL, + priv->preamble); + if (err) { + printk(KERN_ERR "%s: Error %d setting preamble\n", + dev->name, err); + return err; + } + } + + /* Set up encryption */ + if (priv->has_wep) { + err = __orinoco_hw_setup_wep(priv); + if (err) { + printk(KERN_ERR "%s: Error %d activating WEP\n", + dev->name, err); + return err; + } + } + + /* Set promiscuity / multicast*/ + priv->promiscuous = 0; + priv->mc_count = 0; + __orinoco_set_multicast_list(dev); /* FIXME: what about the xmit_lock */ + + return 0; +} + +/* xyzzy */ +static int orinoco_reconfigure(struct net_device *dev) +{ + struct orinoco_private *priv = dev->priv; + struct hermes *hw = &priv->hw; + unsigned long flags; + int err = 0; + + if (priv->broken_disableport) { + schedule_work(&priv->reset_work); + return 0; + } + + err = orinoco_lock(priv, &flags); + if (err) + return err; + + + err = hermes_disable_port(hw, 0); + if (err) { + printk(KERN_WARNING "%s: Unable to disable port while reconfiguring card\n", + dev->name); + priv->broken_disableport = 1; + goto out; + } + + err = __orinoco_program_rids(dev); + if (err) { + printk(KERN_WARNING "%s: Unable to reconfigure card\n", + dev->name); + goto out; + } + + err = hermes_enable_port(hw, 0); + if (err) { + printk(KERN_WARNING "%s: Unable to enable port while reconfiguring card\n", + dev->name); + goto out; + } + + out: + if (err) { + printk(KERN_WARNING "%s: Resetting instead...\n", dev->name); + schedule_work(&priv->reset_work); + err = 0; + } + + orinoco_unlock(priv, &flags); + return err; + +} + +/* This must be called from user context, without locks held - use + * schedule_work() */ +static void orinoco_reset(struct net_device *dev) +{ + struct orinoco_private *priv = dev->priv; + struct hermes *hw = &priv->hw; + int err; + unsigned long flags; + + err = orinoco_lock(priv, &flags); + if (err) + /* When the hardware becomes available again, whatever + * detects that is responsible for re-initializing + * it. So no need for anything further*/ + return; + + netif_stop_queue(dev); + + /* Shut off interrupts. Depending on what state the hardware + * is in, this might not work, but we'll try anyway */ + hermes_set_irqmask(hw, 0); + hermes_write_regn(hw, EVACK, 0xffff); + + priv->hw_unavailable++; + priv->last_linkstatus = 0xffff; /* firmware will have to reassociate */ + priv->connected = 0; + + orinoco_unlock(priv, &flags); + + if (priv->hard_reset) + err = (*priv->hard_reset)(priv); + if (err) { + printk(KERN_ERR "%s: orinoco_reset: Error %d performing hard reset\n", + dev->name, err); + /* FIXME: shutdown of some sort */ + return; + } + + err = orinoco_reinit_firmware(dev); + if (err) { + printk(KERN_ERR "%s: orinoco_reset: Error %d re-initializing firmware\n", + dev->name, err); + return; + } + + spin_lock_irq(&priv->lock); /* This has to be called from user context */ + + priv->hw_unavailable--; + + /* priv->open or priv->hw_unavailable might have changed while + * we dropped the lock */ + if (priv->open && (! priv->hw_unavailable)) { + err = __orinoco_up(dev); + if (err) { + printk(KERN_ERR "%s: orinoco_reset: Error %d reenabling card\n", + dev->name, err); + } else + dev->trans_start = jiffies; + } + + spin_unlock_irq(&priv->lock); + + return; +} + +/********************************************************************/ +/* Internal helper functions */ +/********************************************************************/ + +static inline void +set_port_type(struct orinoco_private *priv) +{ + switch (priv->iw_mode) { + case IW_MODE_INFRA: + priv->port_type = 1; + priv->createibss = 0; + break; + case IW_MODE_ADHOC: + if (priv->prefer_port3) { + priv->port_type = 3; + priv->createibss = 0; + } else { + priv->port_type = priv->ibss_port; + priv->createibss = 1; + } + break; + default: + printk(KERN_ERR "%s: Invalid priv->iw_mode in set_port_type()\n", + priv->ndev->name); + } +} + +/* Does the frame have a SNAP header indicating it should be + * de-encapsulated to Ethernet-II? */ +static inline int +is_ethersnap(struct header_struct *hdr) +{ + /* We de-encapsulate all packets which, a) have SNAP headers + * (i.e. SSAP=DSAP=0xaa and CTRL=0x3 in the 802.2 LLC header + * and where b) the OUI of the SNAP header is 00:00:00 or + * 00:00:f8 - we need both because different APs appear to use + * different OUIs for some reason */ + return (memcmp(&hdr->dsap, &encaps_hdr, 5) == 0) + && ( (hdr->oui[2] == 0x00) || (hdr->oui[2] == 0xf8) ); +} + +static void +orinoco_set_multicast_list(struct net_device *dev) +{ + struct orinoco_private *priv = dev->priv; + unsigned long flags; + + if (orinoco_lock(priv, &flags) != 0) { + printk(KERN_DEBUG "%s: orinoco_set_multicast_list() " + "called when hw_unavailable\n", dev->name); + return; + } + + __orinoco_set_multicast_list(dev); + orinoco_unlock(priv, &flags); +} + +/********************************************************************/ +/* Hardware control functions */ +/********************************************************************/ + + +static int __orinoco_hw_set_bitrate(struct orinoco_private *priv) +{ + hermes_t *hw = &priv->hw; + int err = 0; + + if (priv->bitratemode >= BITRATE_TABLE_SIZE) { + printk(KERN_ERR "%s: BUG: Invalid bitrate mode %d\n", + priv->ndev->name, priv->bitratemode); + return -EINVAL; + } + + switch (priv->firmware_type) { + case FIRMWARE_TYPE_AGERE: + err = hermes_write_wordrec(hw, USER_BAP, + HERMES_RID_CNFTXRATECONTROL, + bitrate_table[priv->bitratemode].agere_txratectrl); + break; + case FIRMWARE_TYPE_INTERSIL: + case FIRMWARE_TYPE_SYMBOL: + err = hermes_write_wordrec(hw, USER_BAP, + HERMES_RID_CNFTXRATECONTROL, + bitrate_table[priv->bitratemode].intersil_txratectrl); + break; + default: + BUG(); + } + + return err; +} + + +static int __orinoco_hw_setup_wep(struct orinoco_private *priv) +{ + hermes_t *hw = &priv->hw; + int err = 0; + int master_wep_flag; + int auth_flag; + + switch (priv->firmware_type) { + case FIRMWARE_TYPE_AGERE: /* Agere style WEP */ + if (priv->wep_on) { + err = hermes_write_wordrec(hw, USER_BAP, + HERMES_RID_CNFTXKEY_AGERE, + priv->tx_key); + if (err) + return err; + + err = HERMES_WRITE_RECORD(hw, USER_BAP, + HERMES_RID_CNFWEPKEYS_AGERE, + &priv->keys); + if (err) + return err; + } + err = hermes_write_wordrec(hw, USER_BAP, + HERMES_RID_CNFWEPENABLED_AGERE, + priv->wep_on); + if (err) + return err; + break; + + case FIRMWARE_TYPE_INTERSIL: /* Intersil style WEP */ + case FIRMWARE_TYPE_SYMBOL: /* Symbol style WEP */ + master_wep_flag = 0; /* Off */ + if (priv->wep_on) { + int keylen; + int i; + + /* Fudge around firmware weirdness */ + keylen = le16_to_cpu(priv->keys[priv->tx_key].len); + + /* Write all 4 keys */ + for(i = 0; i < ORINOCO_MAX_KEYS; i++) { +/* int keylen = le16_to_cpu(priv->keys[i].len); */ + + if (keylen > LARGE_KEY_SIZE) { + printk(KERN_ERR "%s: BUG: Key %d has oversize length %d.\n", + priv->ndev->name, i, keylen); + return -E2BIG; + } + + err = hermes_write_ltv(hw, USER_BAP, + HERMES_RID_CNFDEFAULTKEY0 + i, + HERMES_BYTES_TO_RECLEN(keylen), + priv->keys[i].data); + if (err) + return err; + } + + /* Write the index of the key used in transmission */ + err = hermes_write_wordrec(hw, USER_BAP, HERMES_RID_CNFWEPDEFAULTKEYID, + priv->tx_key); + if (err) + return err; + + if (priv->wep_restrict) { + auth_flag = 2; + master_wep_flag = 3; + } else { + /* Authentication is where Intersil and Symbol + * firmware differ... */ + auth_flag = 1; + if (priv->firmware_type == FIRMWARE_TYPE_SYMBOL) + master_wep_flag = 3; /* Symbol */ + else + master_wep_flag = 1; /* Intersil */ + } + + + err = hermes_write_wordrec(hw, USER_BAP, + HERMES_RID_CNFAUTHENTICATION, auth_flag); + if (err) + return err; + } + + /* Master WEP setting : on/off */ + err = hermes_write_wordrec(hw, USER_BAP, + HERMES_RID_CNFWEPFLAGS_INTERSIL, + master_wep_flag); + if (err) + return err; + + break; + + default: + if (priv->wep_on) { + printk(KERN_ERR "%s: WEP enabled, although not supported!\n", + priv->ndev->name); + return -EINVAL; + } + } + + return 0; +} + +static int orinoco_hw_get_bssid(struct orinoco_private *priv, + char buf[ETH_ALEN]) +{ + hermes_t *hw = &priv->hw; + int err = 0; + unsigned long flags; + + err = orinoco_lock(priv, &flags); + if (err) + return err; + + err = hermes_read_ltv(hw, USER_BAP, HERMES_RID_CURRENTBSSID, + ETH_ALEN, NULL, buf); + + orinoco_unlock(priv, &flags); + + return err; +} + +static int orinoco_hw_get_essid(struct orinoco_private *priv, int *active, + char buf[IW_ESSID_MAX_SIZE+1]) +{ + hermes_t *hw = &priv->hw; + int err = 0; + struct hermes_idstring essidbuf; + char *p = (char *)(&essidbuf.val); + int len; + unsigned long flags; + + err = orinoco_lock(priv, &flags); + if (err) + return err; + + if (strlen(priv->desired_essid) > 0) { + /* We read the desired SSID from the hardware rather + than from priv->desired_essid, just in case the + firmware is allowed to change it on us. I'm not + sure about this */ + /* My guess is that the OWNSSID should always be whatever + * we set to the card, whereas CURRENT_SSID is the one that + * may change... - Jean II */ + u16 rid; + + *active = 1; + + rid = (priv->port_type == 3) ? HERMES_RID_CNFOWNSSID : + HERMES_RID_CNFDESIREDSSID; + + err = hermes_read_ltv(hw, USER_BAP, rid, sizeof(essidbuf), + NULL, &essidbuf); + if (err) + goto fail_unlock; + } else { + *active = 0; + + err = hermes_read_ltv(hw, USER_BAP, HERMES_RID_CURRENTSSID, + sizeof(essidbuf), NULL, &essidbuf); + if (err) + goto fail_unlock; + } + + len = le16_to_cpu(essidbuf.len); + + memset(buf, 0, IW_ESSID_MAX_SIZE+1); + memcpy(buf, p, len); + buf[len] = '\0'; + + fail_unlock: + orinoco_unlock(priv, &flags); + + return err; +} + +static long orinoco_hw_get_freq(struct orinoco_private *priv) +{ + + hermes_t *hw = &priv->hw; + int err = 0; + u16 channel; + long freq = 0; + unsigned long flags; + + err = orinoco_lock(priv, &flags); + if (err) + return err; + + err = hermes_read_wordrec(hw, USER_BAP, HERMES_RID_CURRENTCHANNEL, &channel); + if (err) + goto out; + + /* Intersil firmware 1.3.5 returns 0 when the interface is down */ + if (channel == 0) { + err = -EBUSY; + goto out; + } + + if ( (channel < 1) || (channel > NUM_CHANNELS) ) { + printk(KERN_WARNING "%s: Channel out of range (%d)!\n", + priv->ndev->name, channel); + err = -EBUSY; + goto out; + + } + freq = channel_frequency[channel-1] * 100000; + + out: + orinoco_unlock(priv, &flags); + + if (err > 0) + err = -EBUSY; + return err ? err : freq; +} + +static int orinoco_hw_get_bitratelist(struct orinoco_private *priv, + int *numrates, s32 *rates, int max) +{ + hermes_t *hw = &priv->hw; + struct hermes_idstring list; + unsigned char *p = (unsigned char *)&list.val; + int err = 0; + int num; + int i; + unsigned long flags; + + err = orinoco_lock(priv, &flags); + if (err) + return err; + + err = hermes_read_ltv(hw, USER_BAP, HERMES_RID_SUPPORTEDDATARATES, + sizeof(list), NULL, &list); + orinoco_unlock(priv, &flags); + + if (err) + return err; + + num = le16_to_cpu(list.len); + *numrates = num; + num = min(num, max); + + for (i = 0; i < num; i++) { + rates[i] = (p[i] & 0x7f) * 500000; /* convert to bps */ + } + + return 0; +} + +#if 0 +static void show_rx_frame(struct orinoco_rxframe_hdr *frame) +{ + printk(KERN_DEBUG "RX descriptor:\n"); + printk(KERN_DEBUG " status = 0x%04x\n", frame->desc.status); + printk(KERN_DEBUG " time = 0x%08x\n", frame->desc.time); + printk(KERN_DEBUG " silence = 0x%02x\n", frame->desc.silence); + printk(KERN_DEBUG " signal = 0x%02x\n", frame->desc.signal); + printk(KERN_DEBUG " rate = 0x%02x\n", frame->desc.rate); + printk(KERN_DEBUG " rxflow = 0x%02x\n", frame->desc.rxflow); + printk(KERN_DEBUG " reserved = 0x%08x\n", frame->desc.reserved); + + printk(KERN_DEBUG "IEEE 802.11 header:\n"); + printk(KERN_DEBUG " frame_ctl = 0x%04x\n", + frame->p80211.frame_ctl); + printk(KERN_DEBUG " duration_id = 0x%04x\n", + frame->p80211.duration_id); + printk(KERN_DEBUG " addr1 = %02x:%02x:%02x:%02x:%02x:%02x\n", + frame->p80211.addr1[0], frame->p80211.addr1[1], + frame->p80211.addr1[2], frame->p80211.addr1[3], + frame->p80211.addr1[4], frame->p80211.addr1[5]); + printk(KERN_DEBUG " addr2 = %02x:%02x:%02x:%02x:%02x:%02x\n", + frame->p80211.addr2[0], frame->p80211.addr2[1], + frame->p80211.addr2[2], frame->p80211.addr2[3], + frame->p80211.addr2[4], frame->p80211.addr2[5]); + printk(KERN_DEBUG " addr3 = %02x:%02x:%02x:%02x:%02x:%02x\n", + frame->p80211.addr3[0], frame->p80211.addr3[1], + frame->p80211.addr3[2], frame->p80211.addr3[3], + frame->p80211.addr3[4], frame->p80211.addr3[5]); + printk(KERN_DEBUG " seq_ctl = 0x%04x\n", + frame->p80211.seq_ctl); + printk(KERN_DEBUG " addr4 = %02x:%02x:%02x:%02x:%02x:%02x\n", + frame->p80211.addr4[0], frame->p80211.addr4[1], + frame->p80211.addr4[2], frame->p80211.addr4[3], + frame->p80211.addr4[4], frame->p80211.addr4[5]); + printk(KERN_DEBUG " data_len = 0x%04x\n", + frame->p80211.data_len); + + printk(KERN_DEBUG "IEEE 802.3 header:\n"); + printk(KERN_DEBUG " dest = %02x:%02x:%02x:%02x:%02x:%02x\n", + frame->p8023.h_dest[0], frame->p8023.h_dest[1], + frame->p8023.h_dest[2], frame->p8023.h_dest[3], + frame->p8023.h_dest[4], frame->p8023.h_dest[5]); + printk(KERN_DEBUG " src = %02x:%02x:%02x:%02x:%02x:%02x\n", + frame->p8023.h_source[0], frame->p8023.h_source[1], + frame->p8023.h_source[2], frame->p8023.h_source[3], + frame->p8023.h_source[4], frame->p8023.h_source[5]); + printk(KERN_DEBUG " len = 0x%04x\n", frame->p8023.h_proto); + + printk(KERN_DEBUG "IEEE 802.2 LLC/SNAP header:\n"); + printk(KERN_DEBUG " DSAP = 0x%02x\n", frame->p8022.dsap); + printk(KERN_DEBUG " SSAP = 0x%02x\n", frame->p8022.ssap); + printk(KERN_DEBUG " ctrl = 0x%02x\n", frame->p8022.ctrl); + printk(KERN_DEBUG " OUI = %02x:%02x:%02x\n", + frame->p8022.oui[0], frame->p8022.oui[1], frame->p8022.oui[2]); + printk(KERN_DEBUG " ethertype = 0x%04x\n", frame->ethertype); +} +#endif /* 0 */ + +/* + * Interrupt handler + */ +irqreturn_t orinoco_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct net_device *dev = (struct net_device *)dev_id; + struct orinoco_private *priv = dev->priv; + hermes_t *hw = &priv->hw; + int count = MAX_IRQLOOPS_PER_IRQ; + u16 evstat, events; + /* These are used to detect a runaway interrupt situation */ + /* If we get more than MAX_IRQLOOPS_PER_JIFFY iterations in a jiffy, + * we panic and shut down the hardware */ + static int last_irq_jiffy = 0; /* jiffies value the last time we were called */ + static int loops_this_jiffy = 0; + unsigned long flags; + + if (orinoco_lock(priv, &flags) != 0) { + /* If hw is unavailable - we don't know if the irq was + * for us or not */ + return IRQ_HANDLED; + } + + evstat = hermes_read_regn(hw, EVSTAT); + events = evstat & hw->inten; + if (! events) { + orinoco_unlock(priv, &flags); + return IRQ_NONE; + } + + if (jiffies != last_irq_jiffy) + loops_this_jiffy = 0; + last_irq_jiffy = jiffies; + + while (events && count--) { + if (++loops_this_jiffy > MAX_IRQLOOPS_PER_JIFFY) { + printk(KERN_WARNING "%s: IRQ handler is looping too " + "much! Resetting.\n", dev->name); + /* Disable interrupts for now */ + hermes_set_irqmask(hw, 0); + schedule_work(&priv->reset_work); + break; + } + + /* Check the card hasn't been removed */ + if (! hermes_present(hw)) { + DEBUG(0, "orinoco_interrupt(): card removed\n"); + break; + } + + if (events & HERMES_EV_TICK) + __orinoco_ev_tick(dev, hw); + if (events & HERMES_EV_WTERR) + __orinoco_ev_wterr(dev, hw); + if (events & HERMES_EV_INFDROP) + __orinoco_ev_infdrop(dev, hw); + if (events & HERMES_EV_INFO) + __orinoco_ev_info(dev, hw); + if (events & HERMES_EV_RX) + __orinoco_ev_rx(dev, hw); + if (events & HERMES_EV_TXEXC) + __orinoco_ev_txexc(dev, hw); + if (events & HERMES_EV_TX) + __orinoco_ev_tx(dev, hw); + if (events & HERMES_EV_ALLOC) + __orinoco_ev_alloc(dev, hw); + + hermes_write_regn(hw, EVACK, events); + + evstat = hermes_read_regn(hw, EVSTAT); + events = evstat & hw->inten; + }; + + orinoco_unlock(priv, &flags); + return IRQ_HANDLED; +} + +static void __orinoco_ev_tick(struct net_device *dev, hermes_t *hw) +{ + printk(KERN_DEBUG "%s: TICK\n", dev->name); +} + +static void __orinoco_ev_wterr(struct net_device *dev, hermes_t *hw) +{ + /* This seems to happen a fair bit under load, but ignoring it + seems to work fine...*/ + printk(KERN_DEBUG "%s: MAC controller error (WTERR). Ignoring.\n", + dev->name); +} + +static void __orinoco_ev_infdrop(struct net_device *dev, hermes_t *hw) +{ + printk(KERN_WARNING "%s: Information frame lost.\n", dev->name); +} + +static void print_linkstatus(struct net_device *dev, u16 status) +{ + char * s; + + if (suppress_linkstatus) + return; + + switch (status) { + case HERMES_LINKSTATUS_NOT_CONNECTED: + s = "Not Connected"; + break; + case HERMES_LINKSTATUS_CONNECTED: + s = "Connected"; + break; + case HERMES_LINKSTATUS_DISCONNECTED: + s = "Disconnected"; + break; + case HERMES_LINKSTATUS_AP_CHANGE: + s = "AP Changed"; + break; + case HERMES_LINKSTATUS_AP_OUT_OF_RANGE: + s = "AP Out of Range"; + break; + case HERMES_LINKSTATUS_AP_IN_RANGE: + s = "AP In Range"; + break; + case HERMES_LINKSTATUS_ASSOC_FAILED: + s = "Association Failed"; + break; + default: + s = "UNKNOWN"; + } + + printk(KERN_INFO "%s: New link status: %s (%04x)\n", + dev->name, s, status); +} + +static void __orinoco_ev_info(struct net_device *dev, hermes_t *hw) +{ + struct orinoco_private *priv = dev->priv; + u16 infofid; + struct { + u16 len; + u16 type; + } __attribute__ ((packed)) info; + int len, type; + int err; + + /* This is an answer to an INQUIRE command that we did earlier, + * or an information "event" generated by the card + * The controller return to us a pseudo frame containing + * the information in question - Jean II */ + infofid = hermes_read_regn(hw, INFOFID); + + /* Read the info frame header - don't try too hard */ + err = hermes_bap_pread(hw, IRQ_BAP, &info, sizeof(info), + infofid, 0); + if (err) { + printk(KERN_ERR "%s: error %d reading info frame. " + "Frame dropped.\n", dev->name, err); + return; + } + + len = HERMES_RECLEN_TO_BYTES(le16_to_cpu(info.len)); + type = le16_to_cpu(info.type); + + switch (type) { + case HERMES_INQ_TALLIES: { + struct hermes_tallies_frame tallies; + struct iw_statistics *wstats = &priv->wstats; + + if (len > sizeof(tallies)) { + printk(KERN_WARNING "%s: Tallies frame too long (%d bytes)\n", + dev->name, len); + len = sizeof(tallies); + } + + /* Read directly the data (no seek) */ + hermes_read_words(hw, HERMES_DATA1, (void *) &tallies, + len / 2); /* FIXME: blech! */ + + /* Increment our various counters */ + /* wstats->discard.nwid - no wrong BSSID stuff */ + wstats->discard.code += + le16_to_cpu(tallies.RxWEPUndecryptable); + if (len == sizeof(tallies)) + wstats->discard.code += + le16_to_cpu(tallies.RxDiscards_WEPICVError) + + le16_to_cpu(tallies.RxDiscards_WEPExcluded); + wstats->discard.misc += + le16_to_cpu(tallies.TxDiscardsWrongSA); +#if WIRELESS_EXT > 11 + wstats->discard.fragment += + le16_to_cpu(tallies.RxMsgInBadMsgFragments); + wstats->discard.retries += + le16_to_cpu(tallies.TxRetryLimitExceeded); + /* wstats->miss.beacon - no match */ +#endif /* WIRELESS_EXT > 11 */ + } + break; + case HERMES_INQ_LINKSTATUS: { + struct hermes_linkstatus linkstatus; + u16 newstatus; + + if (len != sizeof(linkstatus)) { + printk(KERN_WARNING "%s: Unexpected size for linkstatus frame (%d bytes)\n", + dev->name, len); + break; + } + + hermes_read_words(hw, HERMES_DATA1, (void *) &linkstatus, + len / 2); + newstatus = le16_to_cpu(linkstatus.linkstatus); + + if ( (newstatus == HERMES_LINKSTATUS_CONNECTED) + || (newstatus == HERMES_LINKSTATUS_AP_CHANGE) + || (newstatus == HERMES_LINKSTATUS_AP_IN_RANGE) ) + priv->connected = 1; + else if ( (newstatus == HERMES_LINKSTATUS_NOT_CONNECTED) + || (newstatus == HERMES_LINKSTATUS_DISCONNECTED) + || (newstatus == HERMES_LINKSTATUS_AP_OUT_OF_RANGE) + || (newstatus == HERMES_LINKSTATUS_ASSOC_FAILED) ) + priv->connected = 0; + + if (newstatus != priv->last_linkstatus) + print_linkstatus(dev, newstatus); + + priv->last_linkstatus = newstatus; + } + break; + default: + printk(KERN_DEBUG "%s: Unknown information frame received (type %04x).\n", + dev->name, type); + /* We don't actually do anything about it */ + break; + } +} + +static void __orinoco_ev_rx(struct net_device *dev, hermes_t *hw) +{ + struct orinoco_private *priv = dev->priv; + struct net_device_stats *stats = &priv->stats; + struct iw_statistics *wstats = &priv->wstats; + struct sk_buff *skb = NULL; + u16 rxfid, status; + int length, data_len, data_off; + char *p; + struct hermes_rx_descriptor desc; + struct header_struct hdr; + struct ethhdr *eh; + int err; + + rxfid = hermes_read_regn(hw, RXFID); + + err = hermes_bap_pread(hw, IRQ_BAP, &desc, sizeof(desc), + rxfid, 0); + if (err) { + printk(KERN_ERR "%s: error %d reading Rx descriptor. " + "Frame dropped.\n", dev->name, err); + stats->rx_errors++; + goto drop; + } + + status = le16_to_cpu(desc.status); + + if (status & HERMES_RXSTAT_ERR) { + if (status & HERMES_RXSTAT_UNDECRYPTABLE) { + wstats->discard.code++; + DEBUG(1, "%s: Undecryptable frame on Rx. Frame dropped.\n", + dev->name); + } else { + stats->rx_crc_errors++; + DEBUG(1, "%s: Bad CRC on Rx. Frame dropped.\n", dev->name); + } + stats->rx_errors++; + goto drop; + } + + /* For now we ignore the 802.11 header completely, assuming + that the card's firmware has handled anything vital */ + + err = hermes_bap_pread(hw, IRQ_BAP, &hdr, sizeof(hdr), + rxfid, HERMES_802_3_OFFSET); + if (err) { + printk(KERN_ERR "%s: error %d reading frame header. " + "Frame dropped.\n", dev->name, err); + stats->rx_errors++; + goto drop; + } + + length = ntohs(hdr.len); + + /* Sanity checks */ + if (length < 3) { /* No for even an 802.2 LLC header */ + /* At least on Symbol firmware with PCF we get quite a + lot of these legitimately - Poll frames with no + data. */ + stats->rx_dropped++; + goto drop; + } + if (length > IEEE802_11_DATA_LEN) { + printk(KERN_WARNING "%s: Oversized frame received (%d bytes)\n", + dev->name, length); + stats->rx_length_errors++; + stats->rx_errors++; + goto drop; + } + + /* We need space for the packet data itself, plus an ethernet + header, plus 2 bytes so we can align the IP header on a + 32bit boundary, plus 1 byte so we can read in odd length + packets from the card, which has an IO granularity of 16 + bits */ + skb = dev_alloc_skb(length+ETH_HLEN+2+1); + if (!skb) { + printk(KERN_WARNING "%s: Can't allocate skb for Rx\n", + dev->name); + goto drop; + } + + skb_reserve(skb, 2); /* This way the IP header is aligned */ + + /* Handle decapsulation + * In most cases, the firmware tell us about SNAP frames. + * For some reason, the SNAP frames sent by LinkSys APs + * are not properly recognised by most firmwares. + * So, check ourselves */ + if(((status & HERMES_RXSTAT_MSGTYPE) == HERMES_RXSTAT_1042) || + ((status & HERMES_RXSTAT_MSGTYPE) == HERMES_RXSTAT_TUNNEL) || + is_ethersnap(&hdr)) { + /* These indicate a SNAP within 802.2 LLC within + 802.11 frame which we'll need to de-encapsulate to + the original EthernetII frame. */ + + if (length < ENCAPS_OVERHEAD) { /* No room for full LLC+SNAP */ + stats->rx_length_errors++; + goto drop; + } + + /* Remove SNAP header, reconstruct EthernetII frame */ + data_len = length - ENCAPS_OVERHEAD; + data_off = HERMES_802_3_OFFSET + sizeof(hdr); + + eh = (struct ethhdr *)skb_put(skb, ETH_HLEN); + + memcpy(eh, &hdr, 2 * ETH_ALEN); + eh->h_proto = hdr.ethertype; + } else { + /* All other cases indicate a genuine 802.3 frame. No + decapsulation needed. We just throw the whole + thing in, and hope the protocol layer can deal with + it as 802.3 */ + data_len = length; + data_off = HERMES_802_3_OFFSET; + /* FIXME: we re-read from the card data we already read here */ + } + + p = skb_put(skb, data_len); + err = hermes_bap_pread(hw, IRQ_BAP, p, RUP_EVEN(data_len), + rxfid, data_off); + if (err) { + printk(KERN_ERR "%s: error %d reading frame. " + "Frame dropped.\n", dev->name, err); + stats->rx_errors++; + goto drop; + } + + dev->last_rx = jiffies; + skb->dev = dev; + skb->protocol = eth_type_trans(skb, dev); + skb->ip_summed = CHECKSUM_NONE; + + /* Process the wireless stats if needed */ + orinoco_stat_gather(dev, skb, &desc); + + /* Pass the packet to the networking stack */ + netif_rx(skb); + stats->rx_packets++; + stats->rx_bytes += length; + + return; + + drop: + stats->rx_dropped++; + + if (skb) + dev_kfree_skb_irq(skb); + return; +} + +static void __orinoco_ev_txexc(struct net_device *dev, hermes_t *hw) +{ + struct orinoco_private *priv = dev->priv; + struct net_device_stats *stats = &priv->stats; + u16 fid = hermes_read_regn(hw, TXCOMPLFID); + struct hermes_tx_descriptor desc; + int err = 0; + + if (fid == DUMMY_FID) + return; /* Nothing's really happened */ + + err = hermes_bap_pread(hw, IRQ_BAP, &desc, sizeof(desc), fid, 0); + if (err) { + printk(KERN_WARNING "%s: Unable to read descriptor on Tx error " + "(FID=%04X error %d)\n", + dev->name, fid, err); + } else { + DEBUG(1, "%s: Tx error, status %d\n", + dev->name, le16_to_cpu(desc.status)); + } + + stats->tx_errors++; + + hermes_write_regn(hw, TXCOMPLFID, DUMMY_FID); +} + +static void __orinoco_ev_tx(struct net_device *dev, hermes_t *hw) +{ + struct orinoco_private *priv = dev->priv; + struct net_device_stats *stats = &priv->stats; + + stats->tx_packets++; + + hermes_write_regn(hw, TXCOMPLFID, DUMMY_FID); +} + +static void __orinoco_ev_alloc(struct net_device *dev, hermes_t *hw) +{ + struct orinoco_private *priv = dev->priv; + + u16 fid = hermes_read_regn(hw, ALLOCFID); + + if (fid != priv->txfid) { + if (fid != DUMMY_FID) + printk(KERN_WARNING "%s: Allocate event on unexpected fid (%04X)\n", + dev->name, fid); + return; + } else { + netif_wake_queue(dev); + } + + hermes_write_regn(hw, ALLOCFID, DUMMY_FID); +} + +struct sta_id { + u16 id, variant, major, minor; +} __attribute__ ((packed)); + +static int determine_firmware_type(struct net_device *dev, struct sta_id *sta_id) +{ + /* FIXME: this is fundamentally broken */ + unsigned int firmver = ((u32)sta_id->major << 16) | sta_id->minor; + + if (sta_id->variant == 1) + return FIRMWARE_TYPE_AGERE; + else if ((sta_id->variant == 2) && + ((firmver == 0x10001) || (firmver == 0x20001))) + return FIRMWARE_TYPE_SYMBOL; + else + return FIRMWARE_TYPE_INTERSIL; +} + +static void determine_firmware(struct net_device *dev) +{ + struct orinoco_private *priv = dev->priv; + hermes_t *hw = &priv->hw; + int err; + struct sta_id sta_id; + unsigned int firmver; + char tmp[SYMBOL_MAX_VER_LEN+1]; + + /* Get the firmware version */ + err = HERMES_READ_RECORD(hw, USER_BAP, HERMES_RID_STAID, &sta_id); + if (err) { + printk(KERN_WARNING "%s: Error %d reading firmware info. Wildly guessing capabilities...\n", + dev->name, err); + memset(&sta_id, 0, sizeof(sta_id)); + } + le16_to_cpus(&sta_id.id); + le16_to_cpus(&sta_id.variant); + le16_to_cpus(&sta_id.major); + le16_to_cpus(&sta_id.minor); + + printk(KERN_DEBUG "%s: Station identity %04x:%04x:%04x:%04x\n", + dev->name, sta_id.id, sta_id.variant, + sta_id.major, sta_id.minor); + + if (! priv->firmware_type) + priv->firmware_type = determine_firmware_type(dev, &sta_id); + + /* Default capabilities */ + priv->has_sensitivity = 1; + priv->has_mwo = 0; + priv->has_preamble = 0; + priv->has_port3 = 1; + priv->has_ibss = 1; + priv->has_ibss_any = 0; + priv->has_wep = 0; + priv->has_big_wep = 0; + + /* Determine capabilities from the firmware version */ + switch (priv->firmware_type) { + case FIRMWARE_TYPE_AGERE: + /* Lucent Wavelan IEEE, Lucent Orinoco, Cabletron RoamAbout, + ELSA, Melco, HP, IBM, Dell 1150, Compaq 110/210 */ + printk(KERN_DEBUG "%s: Looks like a Lucent/Agere firmware " + "version %d.%02d\n", dev->name, + sta_id.major, sta_id.minor); + + firmver = ((unsigned long)sta_id.major << 16) | sta_id.minor; + + priv->has_ibss = (firmver >= 0x60006); + priv->has_ibss_any = (firmver >= 0x60010); + priv->has_wep = (firmver >= 0x40020); + priv->has_big_wep = 1; /* FIXME: this is wrong - how do we tell + Gold cards from the others? */ + priv->has_mwo = (firmver >= 0x60000); + priv->has_pm = (firmver >= 0x40020); /* Don't work in 7.52 ? */ + priv->ibss_port = 1; + + /* Tested with Agere firmware : + * 1.16 ; 4.08 ; 4.52 ; 6.04 ; 6.16 ; 7.28 => Jean II + * Tested CableTron firmware : 4.32 => Anton */ + break; + case FIRMWARE_TYPE_SYMBOL: + /* Symbol , 3Com AirConnect, Intel, Ericsson WLAN */ + /* Intel MAC : 00:02:B3:* */ + /* 3Com MAC : 00:50:DA:* */ + memset(tmp, 0, sizeof(tmp)); + /* Get the Symbol firmware version */ + err = hermes_read_ltv(hw, USER_BAP, + HERMES_RID_SECONDARYVERSION_SYMBOL, + SYMBOL_MAX_VER_LEN, NULL, &tmp); + if (err) { + printk(KERN_WARNING + "%s: Error %d reading Symbol firmware info. Wildly guessing capabilities...\n", + dev->name, err); + firmver = 0; + tmp[0] = '\0'; + } else { + /* The firmware revision is a string, the format is + * something like : "V2.20-01". + * Quick and dirty parsing... - Jean II + */ + firmver = ((tmp[1] - '0') << 16) | ((tmp[3] - '0') << 12) + | ((tmp[4] - '0') << 8) | ((tmp[6] - '0') << 4) + | (tmp[7] - '0'); + + tmp[SYMBOL_MAX_VER_LEN] = '\0'; + } + + printk(KERN_DEBUG "%s: Looks like a Symbol firmware " + "version [%s] (parsing to %X)\n", dev->name, + tmp, firmver); + + priv->has_ibss = (firmver >= 0x20000); + priv->has_wep = (firmver >= 0x15012); + priv->has_big_wep = (firmver >= 0x20000); + priv->has_pm = (firmver >= 0x20000) && (firmver < 0x22000); + priv->has_preamble = (firmver >= 0x20000); + priv->ibss_port = 4; + /* Tested with Intel firmware : 0x20015 => Jean II */ + /* Tested with 3Com firmware : 0x15012 & 0x22001 => Jean II */ + break; + case FIRMWARE_TYPE_INTERSIL: + /* D-Link, Linksys, Adtron, ZoomAir, and many others... + * Samsung, Compaq 100/200 and Proxim are slightly + * different and less well tested */ + /* D-Link MAC : 00:40:05:* */ + /* Addtron MAC : 00:90:D1:* */ + printk(KERN_DEBUG "%s: Looks like an Intersil firmware " + "version %d.%d.%d\n", dev->name, + sta_id.major, sta_id.minor, sta_id.variant); + + firmver = ((unsigned long)sta_id.major << 16) | + ((unsigned long)sta_id.minor << 8) | sta_id.variant; + + priv->has_ibss = (firmver >= 0x000700); /* FIXME */ + priv->has_big_wep = priv->has_wep = (firmver >= 0x000800); + priv->has_pm = (firmver >= 0x000700); + + if (firmver >= 0x000800) + priv->ibss_port = 0; + else { + printk(KERN_NOTICE "%s: Intersil firmware earlier " + "than v0.8.x - several features not supported\n", + dev->name); + priv->ibss_port = 1; + } + break; + default: + break; + } +} + +/* + * struct net_device methods + */ + +static int +orinoco_init(struct net_device *dev) +{ + struct orinoco_private *priv = dev->priv; + hermes_t *hw = &priv->hw; + int err = 0; + struct hermes_idstring nickbuf; + u16 reclen; + int len; + + TRACE_ENTER(dev->name); + + /* No need to lock, the hw_unavailable flag is already set in + * alloc_orinocodev() */ + priv->nicbuf_size = IEEE802_11_FRAME_LEN + ETH_HLEN; + + /* Initialize the firmware */ + err = hermes_init(hw); + if (err != 0) { + printk(KERN_ERR "%s: failed to initialize firmware (err = %d)\n", + dev->name, err); + goto out; + } + + determine_firmware(dev); + + if (priv->has_port3) + printk(KERN_DEBUG "%s: Ad-hoc demo mode supported\n", dev->name); + if (priv->has_ibss) + printk(KERN_DEBUG "%s: IEEE standard IBSS ad-hoc mode supported\n", + dev->name); + if (priv->has_wep) { + printk(KERN_DEBUG "%s: WEP supported, ", dev->name); + if (priv->has_big_wep) + printk("104-bit key\n"); + else + printk("40-bit key\n"); + } + + /* Get the MAC address */ + err = hermes_read_ltv(hw, USER_BAP, HERMES_RID_CNFOWNMACADDR, + ETH_ALEN, NULL, dev->dev_addr); + if (err) { + printk(KERN_WARNING "%s: failed to read MAC address!\n", + dev->name); + goto out; + } + + printk(KERN_DEBUG "%s: MAC address %02X:%02X:%02X:%02X:%02X:%02X\n", + dev->name, dev->dev_addr[0], dev->dev_addr[1], + dev->dev_addr[2], dev->dev_addr[3], dev->dev_addr[4], + dev->dev_addr[5]); + + /* Get the station name */ + err = hermes_read_ltv(hw, USER_BAP, HERMES_RID_CNFOWNNAME, + sizeof(nickbuf), &reclen, &nickbuf); + if (err) { + printk(KERN_ERR "%s: failed to read station name\n", + dev->name); + goto out; + } + if (nickbuf.len) + len = min(IW_ESSID_MAX_SIZE, (int)le16_to_cpu(nickbuf.len)); + else + len = min(IW_ESSID_MAX_SIZE, 2 * reclen); + memcpy(priv->nick, &nickbuf.val, len); + priv->nick[len] = '\0'; + + printk(KERN_DEBUG "%s: Station name \"%s\"\n", dev->name, priv->nick); + + /* Get allowed channels */ + err = hermes_read_wordrec(hw, USER_BAP, HERMES_RID_CHANNELLIST, + &priv->channel_mask); + if (err) { + printk(KERN_ERR "%s: failed to read channel list!\n", + dev->name); + goto out; + } + + /* Get initial AP density */ + err = hermes_read_wordrec(hw, USER_BAP, HERMES_RID_CNFSYSTEMSCALE, + &priv->ap_density); + if (err || priv->ap_density < 1 || priv->ap_density > 3) { + priv->has_sensitivity = 0; + } + + /* Get initial RTS threshold */ + err = hermes_read_wordrec(hw, USER_BAP, HERMES_RID_CNFRTSTHRESHOLD, + &priv->rts_thresh); + if (err) { + printk(KERN_ERR "%s: failed to read RTS threshold!\n", dev->name); + goto out; + } + + /* Get initial fragmentation settings */ + if (priv->has_mwo) + err = hermes_read_wordrec(hw, USER_BAP, + HERMES_RID_CNFMWOROBUST_AGERE, + &priv->mwo_robust); + else + err = hermes_read_wordrec(hw, USER_BAP, HERMES_RID_CNFFRAGMENTATIONTHRESHOLD, + &priv->frag_thresh); + if (err) { + printk(KERN_ERR "%s: failed to read fragmentation settings!\n", dev->name); + goto out; + } + + /* Power management setup */ + if (priv->has_pm) { + priv->pm_on = 0; + priv->pm_mcast = 1; + err = hermes_read_wordrec(hw, USER_BAP, + HERMES_RID_CNFMAXSLEEPDURATION, + &priv->pm_period); + if (err) { + printk(KERN_ERR "%s: failed to read power management period!\n", + dev->name); + goto out; + } + err = hermes_read_wordrec(hw, USER_BAP, + HERMES_RID_CNFPMHOLDOVERDURATION, + &priv->pm_timeout); + if (err) { + printk(KERN_ERR "%s: failed to read power management timeout!\n", + dev->name); + goto out; + } + } + + /* Preamble setup */ + if (priv->has_preamble) { + err = hermes_read_wordrec(hw, USER_BAP, HERMES_RID_CNFPREAMBLE_SYMBOL, + &priv->preamble); + if (err) + goto out; + } + + /* Set up the default configuration */ + priv->iw_mode = IW_MODE_INFRA; + /* By default use IEEE/IBSS ad-hoc mode if we have it */ + priv->prefer_port3 = priv->has_port3 && (! priv->has_ibss); + set_port_type(priv); + priv->channel = 10; /* default channel, more-or-less arbitrary */ + + priv->promiscuous = 0; + priv->wep_on = 0; + priv->tx_key = 0; + + err = hermes_allocate(hw, priv->nicbuf_size, &priv->txfid); + if (err == -EIO) { + /* Try workaround for old Symbol firmware bug */ + printk(KERN_WARNING "%s: firmware ALLOC bug detected " + "(old Symbol firmware?). Trying to work around... ", + dev->name); + + priv->nicbuf_size = TX_NICBUF_SIZE_BUG; + err = hermes_allocate(hw, priv->nicbuf_size, &priv->txfid); + if (err) + printk("failed!\n"); + else + printk("ok.\n"); + } + if (err) { + printk("%s: Error %d allocating Tx buffer\n", dev->name, err); + goto out; + } + + /* Make the hardware available, as long as it hasn't been + * removed elsewhere (e.g. by PCMCIA hot unplug) */ + spin_lock_irq(&priv->lock); + priv->hw_unavailable--; + spin_unlock_irq(&priv->lock); + + printk(KERN_DEBUG "%s: ready\n", dev->name); + + out: + TRACE_EXIT(dev->name); + return err; +} + +struct net_device_stats * +orinoco_get_stats(struct net_device *dev) +{ + struct orinoco_private *priv = dev->priv; + + return &priv->stats; +} + +struct iw_statistics * +orinoco_get_wireless_stats(struct net_device *dev) +{ + struct orinoco_private *priv = dev->priv; + hermes_t *hw = &priv->hw; + struct iw_statistics *wstats = &priv->wstats; + int err = 0; + unsigned long flags; + + if (! netif_device_present(dev)) { + printk(KERN_WARNING "%s: get_wireless_stats() called while device not present\n", + dev->name); + return NULL; /* FIXME: Can we do better than this? */ + } + + err = orinoco_lock(priv, &flags); + if (err) + return NULL; /* FIXME: Erg, we've been signalled, how + * do we propagate this back up? */ + + if (priv->iw_mode == IW_MODE_ADHOC) { + memset(&wstats->qual, 0, sizeof(wstats->qual)); + /* If a spy address is defined, we report stats of the + * first spy address - Jean II */ + if (SPY_NUMBER(priv)) { + wstats->qual.qual = priv->spy_stat[0].qual; + wstats->qual.level = priv->spy_stat[0].level; + wstats->qual.noise = priv->spy_stat[0].noise; + wstats->qual.updated = priv->spy_stat[0].updated; + } + } else { + struct { + u16 qual, signal, noise; + } __attribute__ ((packed)) cq; + + err = HERMES_READ_RECORD(hw, USER_BAP, + HERMES_RID_COMMSQUALITY, &cq); + + wstats->qual.qual = (int)le16_to_cpu(cq.qual); + wstats->qual.level = (int)le16_to_cpu(cq.signal) - 0x95; + wstats->qual.noise = (int)le16_to_cpu(cq.noise) - 0x95; + wstats->qual.updated = 7; + } + + /* We can't really wait for the tallies inquiry command to + * complete, so we just use the previous results and trigger + * a new tallies inquiry command for next time - Jean II */ + /* FIXME: We're in user context (I think?), so we should just + wait for the tallies to come through */ + err = hermes_inquire(hw, HERMES_INQ_TALLIES); + + orinoco_unlock(priv, &flags); + + if (err) + return NULL; + + return wstats; +} + +static inline void orinoco_spy_gather(struct net_device *dev, u_char *mac, + int level, int noise) +{ + struct orinoco_private *priv = (struct orinoco_private *)dev->priv; + int i; + + /* Gather wireless spy statistics: for each packet, compare the + * source address with out list, and if match, get the stats... */ + for (i = 0; i < priv->spy_number; i++) + if (!memcmp(mac, priv->spy_address[i], ETH_ALEN)) { + priv->spy_stat[i].level = level - 0x95; + priv->spy_stat[i].noise = noise - 0x95; + priv->spy_stat[i].qual = (level > noise) ? (level - noise) : 0; + priv->spy_stat[i].updated = 7; + } +} + +void +orinoco_stat_gather(struct net_device *dev, + struct sk_buff *skb, + struct hermes_rx_descriptor *desc) +{ + struct orinoco_private *priv = (struct orinoco_private *)dev->priv; + + /* Using spy support with lots of Rx packets, like in an + * infrastructure (AP), will really slow down everything, because + * the MAC address must be compared to each entry of the spy list. + * If the user really asks for it (set some address in the + * spy list), we do it, but he will pay the price. + * Note that to get here, you need both WIRELESS_SPY + * compiled in AND some addresses in the list !!! + */ + /* Note : gcc will optimise the whole section away if + * WIRELESS_SPY is not defined... - Jean II */ + if (SPY_NUMBER(priv)) { + orinoco_spy_gather(dev, skb->mac.raw + ETH_ALEN, + desc->signal, desc->silence); + } +} + +static int +orinoco_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct orinoco_private *priv = (struct orinoco_private *)dev->priv; + struct net_device_stats *stats = &priv->stats; + hermes_t *hw = &priv->hw; + int err = 0; + u16 txfid = priv->txfid; + char *p; + struct ethhdr *eh; + int len, data_len, data_off; + struct hermes_tx_descriptor desc; + unsigned long flags; + + TRACE_ENTER(dev->name); + + if (! netif_running(dev)) { + printk(KERN_ERR "%s: Tx on stopped device!\n", + dev->name); + TRACE_EXIT(dev->name); + return 1; + } + + if (netif_queue_stopped(dev)) { + printk(KERN_DEBUG "%s: Tx while transmitter busy!\n", + dev->name); + TRACE_EXIT(dev->name); + return 1; + } + + if (orinoco_lock(priv, &flags) != 0) { + printk(KERN_ERR "%s: orinoco_xmit() called while hw_unavailable\n", + dev->name); + TRACE_EXIT(dev->name); +/* BUG(); */ + return 1; + } + + if (! priv->connected) { + /* Oops, the firmware hasn't established a connection, + silently drop the packet (this seems to be the + safest approach). */ + stats->tx_errors++; + orinoco_unlock(priv, &flags); + dev_kfree_skb(skb); + TRACE_EXIT(dev->name); + return 0; + } + + /* Length of the packet body */ + /* FIXME: what if the skb is smaller than this? */ + len = max_t(int,skb->len - ETH_HLEN, ETH_ZLEN - ETH_HLEN); + + eh = (struct ethhdr *)skb->data; + + memset(&desc, 0, sizeof(desc)); + desc.tx_control = cpu_to_le16(HERMES_TXCTRL_TX_OK | HERMES_TXCTRL_TX_EX); + err = hermes_bap_pwrite(hw, USER_BAP, &desc, sizeof(desc), txfid, 0); + if (err) { + printk(KERN_ERR "%s: Error %d writing Tx descriptor to BAP\n", + dev->name, err); + stats->tx_errors++; + goto fail; + } + + /* Clear the 802.11 header and data length fields - some + * firmwares (e.g. Lucent/Agere 8.xx) appear to get confused + * if this isn't done. */ + hermes_clear_words(hw, HERMES_DATA0, + HERMES_802_3_OFFSET - HERMES_802_11_OFFSET); + + /* Encapsulate Ethernet-II frames */ + if (ntohs(eh->h_proto) > 1500) { /* Ethernet-II frame */ + struct header_struct hdr; + data_len = len; + data_off = HERMES_802_3_OFFSET + sizeof(hdr); + p = skb->data + ETH_HLEN; + + /* 802.3 header */ + memcpy(hdr.dest, eh->h_dest, ETH_ALEN); + memcpy(hdr.src, eh->h_source, ETH_ALEN); + hdr.len = htons(data_len + ENCAPS_OVERHEAD); + + /* 802.2 header */ + memcpy(&hdr.dsap, &encaps_hdr, sizeof(encaps_hdr)); + + hdr.ethertype = eh->h_proto; + err = hermes_bap_pwrite(hw, USER_BAP, &hdr, sizeof(hdr), + txfid, HERMES_802_3_OFFSET); + if (err) { + printk(KERN_ERR "%s: Error %d writing packet header to BAP\n", + dev->name, err); + stats->tx_errors++; + goto fail; + } + } else { /* IEEE 802.3 frame */ + data_len = len + ETH_HLEN; + data_off = HERMES_802_3_OFFSET; + p = skb->data; + } + + /* Round up for odd length packets */ + err = hermes_bap_pwrite(hw, USER_BAP, p, RUP_EVEN(data_len), txfid, data_off); + if (err) { + printk(KERN_ERR "%s: Error %d writing packet to BAP\n", + dev->name, err); + stats->tx_errors++; + goto fail; + } + + /* Finally, we actually initiate the send */ + netif_stop_queue(dev); + + err = hermes_docmd_wait(hw, HERMES_CMD_TX | HERMES_CMD_RECL, txfid, NULL); + if (err) { + netif_start_queue(dev); + printk(KERN_ERR "%s: Error %d transmitting packet\n", dev->name, err); + stats->tx_errors++; + goto fail; + } + + dev->trans_start = jiffies; + stats->tx_bytes += data_off + data_len; + + orinoco_unlock(priv, &flags); + + DEV_KFREE_SKB(skb); + + TRACE_EXIT(dev->name); + + return 0; + fail: + TRACE_EXIT(dev->name); + + orinoco_unlock(priv, &flags); + return err; +} + +#ifdef HAVE_TX_TIMEOUT +static void +orinoco_tx_timeout(struct net_device *dev) +{ + struct orinoco_private *priv = (struct orinoco_private *)dev->priv; + struct net_device_stats *stats = &priv->stats; + struct hermes *hw = &priv->hw; + + printk(KERN_WARNING "%s: Tx timeout! " + "ALLOCFID=%04x, TXCOMPLFID=%04x, EVSTAT=%04x\n", + dev->name, hermes_read_regn(hw, ALLOCFID), + hermes_read_regn(hw, TXCOMPLFID), hermes_read_regn(hw, EVSTAT)); + + stats->tx_errors++; + + schedule_work(&priv->reset_work); +} +#endif + +static int +orinoco_change_mtu(struct net_device *dev, int new_mtu) +{ + struct orinoco_private *priv = dev->priv; + + if ( (new_mtu < ORINOCO_MIN_MTU) || (new_mtu > ORINOCO_MAX_MTU) ) + return -EINVAL; + + if ( (new_mtu + ENCAPS_OVERHEAD + IEEE802_11_HLEN) > + (priv->nicbuf_size - ETH_HLEN) ) + return -EINVAL; + + dev->mtu = new_mtu; + + return 0; +} + +/* FIXME: return int? */ +static void +__orinoco_set_multicast_list(struct net_device *dev) +{ + struct orinoco_private *priv = dev->priv; + hermes_t *hw = &priv->hw; + int err = 0; + int promisc, mc_count; + + /* The Hermes doesn't seem to have an allmulti mode, so we go + * into promiscuous mode and let the upper levels deal. */ + if ( (dev->flags & IFF_PROMISC) || (dev->flags & IFF_ALLMULTI) || + (dev->mc_count > MAX_MULTICAST(priv)) ) { + promisc = 1; + mc_count = 0; + } else { + promisc = 0; + mc_count = dev->mc_count; + } + + if (promisc != priv->promiscuous) { + err = hermes_write_wordrec(hw, USER_BAP, + HERMES_RID_CNFPROMISCUOUSMODE, + promisc); + if (err) { + printk(KERN_ERR "%s: Error %d setting PROMISCUOUSMODE to 1.\n", + dev->name, err); + } else + priv->promiscuous = promisc; + } + + if (! promisc && (mc_count || priv->mc_count) ) { + struct dev_mc_list *p = dev->mc_list; + hermes_multicast_t mclist; + int i; + + for (i = 0; i < mc_count; i++) { + /* Paranoia: */ + if (! p) + BUG(); /* Multicast list shorter than mc_count */ + if (p->dmi_addrlen != ETH_ALEN) + BUG(); /* Bad address size in multicast list */ + + memcpy(mclist.addr[i], p->dmi_addr, ETH_ALEN); + p = p->next; + } + + if (p) + printk(KERN_WARNING "Multicast list is longer than mc_count\n"); + + err = hermes_write_ltv(hw, USER_BAP, HERMES_RID_CNFGROUPADDRESSES, + HERMES_BYTES_TO_RECLEN(priv->mc_count * ETH_ALEN), + &mclist); + if (err) + printk(KERN_ERR "%s: Error %d setting multicast list.\n", + dev->name, err); + else + priv->mc_count = mc_count; + } + + /* Since we can set the promiscuous flag when it wasn't asked + for, make sure the net_device knows about it. */ + if (priv->promiscuous) + dev->flags |= IFF_PROMISC; + else + dev->flags &= ~IFF_PROMISC; +} + +/********************************************************************/ +/* Wireless extensions support */ +/********************************************************************/ + +static int orinoco_ioctl_getiwrange(struct net_device *dev, struct iw_point *rrq) +{ + struct orinoco_private *priv = dev->priv; + int err = 0; + int mode; + struct iw_range range; + int numrates; + int i, k; + unsigned long flags; + + TRACE_ENTER(dev->name); + + err = verify_area(VERIFY_WRITE, rrq->pointer, sizeof(range)); + if (err) + return err; + + rrq->length = sizeof(range); + + err = orinoco_lock(priv, &flags); + if (err) + return err; + + mode = priv->iw_mode; + orinoco_unlock(priv, &flags); + + memset(&range, 0, sizeof(range)); + + /* Much of this shamelessly taken from wvlan_cs.c. No idea + * what it all means -dgibson */ +#if WIRELESS_EXT > 10 + range.we_version_compiled = WIRELESS_EXT; + range.we_version_source = 11; +#endif /* WIRELESS_EXT > 10 */ + + range.min_nwid = range.max_nwid = 0; /* We don't use nwids */ + + /* Set available channels/frequencies */ + range.num_channels = NUM_CHANNELS; + k = 0; + for (i = 0; i < NUM_CHANNELS; i++) { + if (priv->channel_mask & (1 << i)) { + range.freq[k].i = i + 1; + range.freq[k].m = channel_frequency[i] * 100000; + range.freq[k].e = 1; + k++; + } + + if (k >= IW_MAX_FREQUENCIES) + break; + } + range.num_frequency = k; + + range.sensitivity = 3; + + if ((mode == IW_MODE_ADHOC) && (priv->spy_number == 0)){ + /* Quality stats meaningless in ad-hoc mode */ + range.max_qual.qual = 0; + range.max_qual.level = 0; + range.max_qual.noise = 0; +#if WIRELESS_EXT > 11 + range.avg_qual.qual = 0; + range.avg_qual.level = 0; + range.avg_qual.noise = 0; +#endif /* WIRELESS_EXT > 11 */ + + } else { + range.max_qual.qual = 0x8b - 0x2f; + range.max_qual.level = 0x2f - 0x95 - 1; + range.max_qual.noise = 0x2f - 0x95 - 1; +#if WIRELESS_EXT > 11 + /* Need to get better values */ + range.avg_qual.qual = 0x24; + range.avg_qual.level = 0xC2; + range.avg_qual.noise = 0x9E; +#endif /* WIRELESS_EXT > 11 */ + } + + err = orinoco_hw_get_bitratelist(priv, &numrates, + range.bitrate, IW_MAX_BITRATES); + if (err) + return err; + range.num_bitrates = numrates; + + /* Set an indication of the max TCP throughput in bit/s that we can + * expect using this interface. May be use for QoS stuff... + * Jean II */ + if(numrates > 2) + range.throughput = 5 * 1000 * 1000; /* ~5 Mb/s */ + else + range.throughput = 1.5 * 1000 * 1000; /* ~1.5 Mb/s */ + + range.min_rts = 0; + range.max_rts = 2347; + range.min_frag = 256; + range.max_frag = 2346; + + err = orinoco_lock(priv, &flags); + if (err) + return err; + if (priv->has_wep) { + range.max_encoding_tokens = ORINOCO_MAX_KEYS; + + range.encoding_size[0] = SMALL_KEY_SIZE; + range.num_encoding_sizes = 1; + + if (priv->has_big_wep) { + range.encoding_size[1] = LARGE_KEY_SIZE; + range.num_encoding_sizes = 2; + } + } else { + range.num_encoding_sizes = 0; + range.max_encoding_tokens = 0; + } + orinoco_unlock(priv, &flags); + + range.min_pmp = 0; + range.max_pmp = 65535000; + range.min_pmt = 0; + range.max_pmt = 65535 * 1000; /* ??? */ + range.pmp_flags = IW_POWER_PERIOD; + range.pmt_flags = IW_POWER_TIMEOUT; + range.pm_capa = IW_POWER_PERIOD | IW_POWER_TIMEOUT | IW_POWER_UNICAST_R; + + range.num_txpower = 1; + range.txpower[0] = 15; /* 15dBm */ + range.txpower_capa = IW_TXPOW_DBM; + +#if WIRELESS_EXT > 10 + range.retry_capa = IW_RETRY_LIMIT | IW_RETRY_LIFETIME; + range.retry_flags = IW_RETRY_LIMIT; + range.r_time_flags = IW_RETRY_LIFETIME; + range.min_retry = 0; + range.max_retry = 65535; /* ??? */ + range.min_r_time = 0; + range.max_r_time = 65535 * 1000; /* ??? */ +#endif /* WIRELESS_EXT > 10 */ + + if (copy_to_user(rrq->pointer, &range, sizeof(range))) + return -EFAULT; + + TRACE_EXIT(dev->name); + + return 0; +} + +static int orinoco_ioctl_setiwencode(struct net_device *dev, struct iw_point *erq) +{ + struct orinoco_private *priv = dev->priv; + int index = (erq->flags & IW_ENCODE_INDEX) - 1; + int setindex = priv->tx_key; + int enable = priv->wep_on; + int restricted = priv->wep_restrict; + u16 xlen = 0; + int err = 0; + char keybuf[ORINOCO_MAX_KEY_SIZE]; + unsigned long flags; + + if (erq->pointer) { + /* We actually have a key to set */ + if ( (erq->length < SMALL_KEY_SIZE) || (erq->length > ORINOCO_MAX_KEY_SIZE) ) + return -EINVAL; + + if (copy_from_user(keybuf, erq->pointer, erq->length)) + return -EFAULT; + } + + err = orinoco_lock(priv, &flags); + if (err) + return err; + + if (erq->pointer) { + if (erq->length > ORINOCO_MAX_KEY_SIZE) { + err = -E2BIG; + goto out; + } + + if ( (erq->length > LARGE_KEY_SIZE) + || ( ! priv->has_big_wep && (erq->length > SMALL_KEY_SIZE)) ) { + err = -EINVAL; + goto out; + } + + if ((index < 0) || (index >= ORINOCO_MAX_KEYS)) + index = priv->tx_key; + + if (erq->length > SMALL_KEY_SIZE) { + xlen = LARGE_KEY_SIZE; + } else if (erq->length > 0) { + xlen = SMALL_KEY_SIZE; + } else + xlen = 0; + + /* Switch on WEP if off */ + if ((!enable) && (xlen > 0)) { + setindex = index; + enable = 1; + } + } else { + /* Important note : if the user do "iwconfig eth0 enc off", + * we will arrive there with an index of -1. This is valid + * but need to be taken care off... Jean II */ + if ((index < 0) || (index >= ORINOCO_MAX_KEYS)) { + if((index != -1) || (erq->flags == 0)) { + err = -EINVAL; + goto out; + } + } else { + /* Set the index : Check that the key is valid */ + if(priv->keys[index].len == 0) { + err = -EINVAL; + goto out; + } + setindex = index; + } + } + + if (erq->flags & IW_ENCODE_DISABLED) + enable = 0; + /* Only for Prism2 & Symbol cards (so far) - Jean II */ + if (erq->flags & IW_ENCODE_OPEN) + restricted = 0; + if (erq->flags & IW_ENCODE_RESTRICTED) + restricted = 1; + + if (erq->pointer) { + priv->keys[index].len = cpu_to_le16(xlen); + memset(priv->keys[index].data, 0, sizeof(priv->keys[index].data)); + memcpy(priv->keys[index].data, keybuf, erq->length); + } + priv->tx_key = setindex; + priv->wep_on = enable; + priv->wep_restrict = restricted; + + + out: + orinoco_unlock(priv, &flags); + + return err; +} + +static int orinoco_ioctl_getiwencode(struct net_device *dev, struct iw_point *erq) +{ + struct orinoco_private *priv = dev->priv; + int index = (erq->flags & IW_ENCODE_INDEX) - 1; + u16 xlen = 0; + char keybuf[ORINOCO_MAX_KEY_SIZE]; + int err; + unsigned long flags; + + err = orinoco_lock(priv, &flags); + if (err) + return err; + + if ((index < 0) || (index >= ORINOCO_MAX_KEYS)) + index = priv->tx_key; + + erq->flags = 0; + if (! priv->wep_on) + erq->flags |= IW_ENCODE_DISABLED; + erq->flags |= index + 1; + + /* Only for symbol cards - Jean II */ + if (priv->firmware_type != FIRMWARE_TYPE_AGERE) { + if(priv->wep_restrict) + erq->flags |= IW_ENCODE_RESTRICTED; + else + erq->flags |= IW_ENCODE_OPEN; + } + + xlen = le16_to_cpu(priv->keys[index].len); + + erq->length = xlen; + + if (erq->pointer) { + memcpy(keybuf, priv->keys[index].data, ORINOCO_MAX_KEY_SIZE); + } + + orinoco_unlock(priv, &flags); + + if (erq->pointer) { + if (copy_to_user(erq->pointer, keybuf, xlen)) + return -EFAULT; + } + + return 0; +} + +static int orinoco_ioctl_setessid(struct net_device *dev, struct iw_point *erq) +{ + struct orinoco_private *priv = dev->priv; + char essidbuf[IW_ESSID_MAX_SIZE+1]; + int err; + unsigned long flags; + + /* Note : ESSID is ignored in Ad-Hoc demo mode, but we can set it + * anyway... - Jean II */ + + memset(&essidbuf, 0, sizeof(essidbuf)); + + if (erq->flags) { + if (erq->length > IW_ESSID_MAX_SIZE) + return -E2BIG; + + if (copy_from_user(&essidbuf, erq->pointer, erq->length)) + return -EFAULT; + + essidbuf[erq->length] = '\0'; + } + + err = orinoco_lock(priv, &flags); + if (err) + return err; + + memcpy(priv->desired_essid, essidbuf, sizeof(priv->desired_essid)); + + orinoco_unlock(priv, &flags); + + return 0; +} + +static int orinoco_ioctl_getessid(struct net_device *dev, struct iw_point *erq) +{ + struct orinoco_private *priv = dev->priv; + char essidbuf[IW_ESSID_MAX_SIZE+1]; + int active; + int err = 0; + unsigned long flags; + + TRACE_ENTER(dev->name); + + if (netif_running(dev)) { + err = orinoco_hw_get_essid(priv, &active, essidbuf); + if (err) + return err; + } else { + err = orinoco_lock(priv, &flags); + if (err) + return err; + memcpy(essidbuf, priv->desired_essid, sizeof(essidbuf)); + orinoco_unlock(priv, &flags); + } + + erq->flags = 1; + erq->length = strlen(essidbuf) + 1; + if (erq->pointer) + if (copy_to_user(erq->pointer, essidbuf, erq->length)) + return -EFAULT; + + TRACE_EXIT(dev->name); + + return 0; +} + +static int orinoco_ioctl_setnick(struct net_device *dev, struct iw_point *nrq) +{ + struct orinoco_private *priv = dev->priv; + char nickbuf[IW_ESSID_MAX_SIZE+1]; + int err; + unsigned long flags; + + if (nrq->length > IW_ESSID_MAX_SIZE) + return -E2BIG; + + memset(nickbuf, 0, sizeof(nickbuf)); + + if (copy_from_user(nickbuf, nrq->pointer, nrq->length)) + return -EFAULT; + + nickbuf[nrq->length] = '\0'; + + err = orinoco_lock(priv, &flags); + if (err) + return err; + + memcpy(priv->nick, nickbuf, sizeof(priv->nick)); + + orinoco_unlock(priv, &flags); + + return 0; +} + +static int orinoco_ioctl_getnick(struct net_device *dev, struct iw_point *nrq) +{ + struct orinoco_private *priv = dev->priv; + char nickbuf[IW_ESSID_MAX_SIZE+1]; + int err; + unsigned long flags; + + err = orinoco_lock(priv, &flags); + if (err) + return err; + + memcpy(nickbuf, priv->nick, IW_ESSID_MAX_SIZE+1); + orinoco_unlock(priv, &flags); + + nrq->length = strlen(nickbuf)+1; + + if (copy_to_user(nrq->pointer, nickbuf, sizeof(nickbuf))) + return -EFAULT; + + return 0; +} + +static int orinoco_ioctl_setfreq(struct net_device *dev, struct iw_freq *frq) +{ + struct orinoco_private *priv = dev->priv; + int chan = -1; + int err; + unsigned long flags; + + /* We can only use this in Ad-Hoc demo mode to set the operating + * frequency, or in IBSS mode to set the frequency where the IBSS + * will be created - Jean II */ + if (priv->iw_mode != IW_MODE_ADHOC) + return -EOPNOTSUPP; + + if ( (frq->e == 0) && (frq->m <= 1000) ) { + /* Setting by channel number */ + chan = frq->m; + } else { + /* Setting by frequency - search the table */ + int mult = 1; + int i; + + for (i = 0; i < (6 - frq->e); i++) + mult *= 10; + + for (i = 0; i < NUM_CHANNELS; i++) + if (frq->m == (channel_frequency[i] * mult)) + chan = i+1; + } + + if ( (chan < 1) || (chan > NUM_CHANNELS) || + ! (priv->channel_mask & (1 << (chan-1)) ) ) + return -EINVAL; + + err = orinoco_lock(priv, &flags); + if (err) + return err; + priv->channel = chan; + orinoco_unlock(priv, &flags); + + return 0; +} + +static int orinoco_ioctl_getsens(struct net_device *dev, struct iw_param *srq) +{ + struct orinoco_private *priv = dev->priv; + hermes_t *hw = &priv->hw; + u16 val; + int err; + unsigned long flags; + + if (!priv->has_sensitivity) + return -EOPNOTSUPP; + + err = orinoco_lock(priv, &flags); + if (err) + return err; + err = hermes_read_wordrec(hw, USER_BAP, HERMES_RID_CNFSYSTEMSCALE, &val); + orinoco_unlock(priv, &flags); + + if (err) + return err; + + srq->value = val; + srq->fixed = 0; /* auto */ + + return 0; +} + +static int orinoco_ioctl_setsens(struct net_device *dev, struct iw_param *srq) +{ + struct orinoco_private *priv = dev->priv; + int val = srq->value; + int err; + unsigned long flags; + + if (!priv->has_sensitivity) + return -EOPNOTSUPP; + + if ((val < 1) || (val > 3)) + return -EINVAL; + + err = orinoco_lock(priv, &flags); + if (err) + return err; + priv->ap_density = val; + orinoco_unlock(priv, &flags); + + return 0; +} + +static int orinoco_ioctl_setrts(struct net_device *dev, struct iw_param *rrq) +{ + struct orinoco_private *priv = dev->priv; + int val = rrq->value; + int err; + unsigned long flags; + + if (rrq->disabled) + val = 2347; + + if ( (val < 0) || (val > 2347) ) + return -EINVAL; + + err = orinoco_lock(priv, &flags); + if (err) + return err; + + priv->rts_thresh = val; + orinoco_unlock(priv, &flags); + + return 0; +} + +static int orinoco_ioctl_setfrag(struct net_device *dev, struct iw_param *frq) +{ + struct orinoco_private *priv = dev->priv; + int err = 0; + unsigned long flags; + + err = orinoco_lock(priv, &flags); + if (err) + return err; + + if (priv->has_mwo) { + if (frq->disabled) + priv->mwo_robust = 0; + else { + if (frq->fixed) + printk(KERN_WARNING "%s: Fixed fragmentation not \ +supported on this firmware. Using MWO robust instead.\n", dev->name); + priv->mwo_robust = 1; + } + } else { + if (frq->disabled) + priv->frag_thresh = 2346; + else { + if ( (frq->value < 256) || (frq->value > 2346) ) + err = -EINVAL; + else + priv->frag_thresh = frq->value & ~0x1; /* must be even */ + } + } + + orinoco_unlock(priv, &flags); + + return err; +} + +static int orinoco_ioctl_getfrag(struct net_device *dev, struct iw_param *frq) +{ + struct orinoco_private *priv = dev->priv; + hermes_t *hw = &priv->hw; + int err = 0; + u16 val; + unsigned long flags; + + err = orinoco_lock(priv, &flags); + if (err) + return err; + + if (priv->has_mwo) { + err = hermes_read_wordrec(hw, USER_BAP, + HERMES_RID_CNFMWOROBUST_AGERE, + &val); + if (err) + val = 0; + + frq->value = val ? 2347 : 0; + frq->disabled = ! val; + frq->fixed = 0; + } else { + err = hermes_read_wordrec(hw, USER_BAP, HERMES_RID_CNFFRAGMENTATIONTHRESHOLD, + &val); + if (err) + val = 0; + + frq->value = val; + frq->disabled = (val >= 2346); + frq->fixed = 1; + } + + orinoco_unlock(priv, &flags); + + return err; +} + +static int orinoco_ioctl_setrate(struct net_device *dev, struct iw_param *rrq) +{ + struct orinoco_private *priv = dev->priv; + int err = 0; + int ratemode = -1; + int bitrate; /* 100s of kilobits */ + int i; + unsigned long flags; + + /* As the user space doesn't know our highest rate, it uses -1 + * to ask us to set the highest rate. Test it using "iwconfig + * ethX rate auto" - Jean II */ + if (rrq->value == -1) + bitrate = 110; + else { + if (rrq->value % 100000) + return -EINVAL; + bitrate = rrq->value / 100000; + } + + if ( (bitrate != 10) && (bitrate != 20) && + (bitrate != 55) && (bitrate != 110) ) + return -EINVAL; + + for (i = 0; i < BITRATE_TABLE_SIZE; i++) + if ( (bitrate_table[i].bitrate == bitrate) && + (bitrate_table[i].automatic == ! rrq->fixed) ) { + ratemode = i; + break; + } + + if (ratemode == -1) + return -EINVAL; + + err = orinoco_lock(priv, &flags); + if (err) + return err; + priv->bitratemode = ratemode; + orinoco_unlock(priv, &flags); + + return err; +} + +static int orinoco_ioctl_getrate(struct net_device *dev, struct iw_param *rrq) +{ + struct orinoco_private *priv = dev->priv; + hermes_t *hw = &priv->hw; + int err = 0; + int ratemode; + int i; + u16 val; + unsigned long flags; + + err = orinoco_lock(priv, &flags); + if (err) + return err; + + ratemode = priv->bitratemode; + + if ( (ratemode < 0) || (ratemode >= BITRATE_TABLE_SIZE) ) + BUG(); + + rrq->value = bitrate_table[ratemode].bitrate * 100000; + rrq->fixed = ! bitrate_table[ratemode].automatic; + rrq->disabled = 0; + + /* If the interface is running we try to find more about the + current mode */ + if (netif_running(dev)) { + err = hermes_read_wordrec(hw, USER_BAP, + HERMES_RID_CURRENTTXRATE, &val); + if (err) + goto out; + + switch (priv->firmware_type) { + case FIRMWARE_TYPE_AGERE: /* Lucent style rate */ + /* Note : in Lucent firmware, the return value of + * HERMES_RID_CURRENTTXRATE is the bitrate in Mb/s, + * and therefore is totally different from the + * encoding of HERMES_RID_CNFTXRATECONTROL. + * Don't forget that 6Mb/s is really 5.5Mb/s */ + if (val == 6) + rrq->value = 5500000; + else + rrq->value = val * 1000000; + break; + case FIRMWARE_TYPE_INTERSIL: /* Intersil style rate */ + case FIRMWARE_TYPE_SYMBOL: /* Symbol style rate */ + for (i = 0; i < BITRATE_TABLE_SIZE; i++) + if (bitrate_table[i].intersil_txratectrl == val) { + ratemode = i; + break; + } + if (i >= BITRATE_TABLE_SIZE) + printk(KERN_INFO "%s: Unable to determine current bitrate (0x%04hx)\n", + dev->name, val); + + rrq->value = bitrate_table[ratemode].bitrate * 100000; + break; + default: + BUG(); + } + } + + out: + orinoco_unlock(priv, &flags); + + return err; +} + +static int orinoco_ioctl_setpower(struct net_device *dev, struct iw_param *prq) +{ + struct orinoco_private *priv = dev->priv; + int err = 0; + unsigned long flags; + + err = orinoco_lock(priv, &flags); + if (err) + return err; + + if (prq->disabled) { + priv->pm_on = 0; + } else { + switch (prq->flags & IW_POWER_MODE) { + case IW_POWER_UNICAST_R: + priv->pm_mcast = 0; + priv->pm_on = 1; + break; + case IW_POWER_ALL_R: + priv->pm_mcast = 1; + priv->pm_on = 1; + break; + case IW_POWER_ON: + /* No flags : but we may have a value - Jean II */ + break; + default: + err = -EINVAL; + } + if (err) + goto out; + + if (prq->flags & IW_POWER_TIMEOUT) { + priv->pm_on = 1; + priv->pm_timeout = prq->value / 1000; + } + if (prq->flags & IW_POWER_PERIOD) { + priv->pm_on = 1; + priv->pm_period = prq->value / 1000; + } + /* It's valid to not have a value if we are just toggling + * the flags... Jean II */ + if(!priv->pm_on) { + err = -EINVAL; + goto out; + } + } + + out: + orinoco_unlock(priv, &flags); + + return err; +} + +static int orinoco_ioctl_getpower(struct net_device *dev, struct iw_param *prq) +{ + struct orinoco_private *priv = dev->priv; + hermes_t *hw = &priv->hw; + int err = 0; + u16 enable, period, timeout, mcast; + unsigned long flags; + + err = orinoco_lock(priv, &flags); + if (err) + return err; + + err = hermes_read_wordrec(hw, USER_BAP, HERMES_RID_CNFPMENABLED, &enable); + if (err) + goto out; + + err = hermes_read_wordrec(hw, USER_BAP, + HERMES_RID_CNFMAXSLEEPDURATION, &period); + if (err) + goto out; + + err = hermes_read_wordrec(hw, USER_BAP, HERMES_RID_CNFPMHOLDOVERDURATION, &timeout); + if (err) + goto out; + + err = hermes_read_wordrec(hw, USER_BAP, HERMES_RID_CNFMULTICASTRECEIVE, &mcast); + if (err) + goto out; + + prq->disabled = !enable; + /* Note : by default, display the period */ + if ((prq->flags & IW_POWER_TYPE) == IW_POWER_TIMEOUT) { + prq->flags = IW_POWER_TIMEOUT; + prq->value = timeout * 1000; + } else { + prq->flags = IW_POWER_PERIOD; + prq->value = period * 1000; + } + if (mcast) + prq->flags |= IW_POWER_ALL_R; + else + prq->flags |= IW_POWER_UNICAST_R; + + out: + orinoco_unlock(priv, &flags); + + return err; +} + +#if WIRELESS_EXT > 10 +static int orinoco_ioctl_getretry(struct net_device *dev, struct iw_param *rrq) +{ + struct orinoco_private *priv = dev->priv; + hermes_t *hw = &priv->hw; + int err = 0; + u16 short_limit, long_limit, lifetime; + unsigned long flags; + + err = orinoco_lock(priv, &flags); + if (err) + return err; + + err = hermes_read_wordrec(hw, USER_BAP, HERMES_RID_SHORTRETRYLIMIT, + &short_limit); + if (err) + goto out; + + err = hermes_read_wordrec(hw, USER_BAP, HERMES_RID_LONGRETRYLIMIT, + &long_limit); + if (err) + goto out; + + err = hermes_read_wordrec(hw, USER_BAP, HERMES_RID_MAXTRANSMITLIFETIME, + &lifetime); + if (err) + goto out; + + rrq->disabled = 0; /* Can't be disabled */ + + /* Note : by default, display the retry number */ + if ((rrq->flags & IW_RETRY_TYPE) == IW_RETRY_LIFETIME) { + rrq->flags = IW_RETRY_LIFETIME; + rrq->value = lifetime * 1000; /* ??? */ + } else { + /* By default, display the min number */ + if ((rrq->flags & IW_RETRY_MAX)) { + rrq->flags = IW_RETRY_LIMIT | IW_RETRY_MAX; + rrq->value = long_limit; + } else { + rrq->flags = IW_RETRY_LIMIT; + rrq->value = short_limit; + if(short_limit != long_limit) + rrq->flags |= IW_RETRY_MIN; + } + } + + out: + orinoco_unlock(priv, &flags); + + return err; +} +#endif /* WIRELESS_EXT > 10 */ + +static int orinoco_ioctl_setibssport(struct net_device *dev, struct iwreq *wrq) +{ + struct orinoco_private *priv = dev->priv; + int val = *( (int *) wrq->u.name ); + int err; + unsigned long flags; + + err = orinoco_lock(priv, &flags); + if (err) + return err; + + priv->ibss_port = val ; + + /* Actually update the mode we are using */ + set_port_type(priv); + + orinoco_unlock(priv, &flags); + return 0; +} + +static int orinoco_ioctl_getibssport(struct net_device *dev, struct iwreq *wrq) +{ + struct orinoco_private *priv = dev->priv; + int *val = (int *)wrq->u.name; + int err; + unsigned long flags; + + err = orinoco_lock(priv, &flags); + if (err) + return err; + + *val = priv->ibss_port; + orinoco_unlock(priv, &flags); + + return 0; +} + +static int orinoco_ioctl_setport3(struct net_device *dev, struct iwreq *wrq) +{ + struct orinoco_private *priv = dev->priv; + int val = *( (int *) wrq->u.name ); + int err = 0; + unsigned long flags; + + err = orinoco_lock(priv, &flags); + if (err) + return err; + + switch (val) { + case 0: /* Try to do IEEE ad-hoc mode */ + if (! priv->has_ibss) { + err = -EINVAL; + break; + } + priv->prefer_port3 = 0; + + break; + + case 1: /* Try to do Lucent proprietary ad-hoc mode */ + if (! priv->has_port3) { + err = -EINVAL; + break; + } + priv->prefer_port3 = 1; + break; + + default: + err = -EINVAL; + } + + if (! err) + /* Actually update the mode we are using */ + set_port_type(priv); + + orinoco_unlock(priv, &flags); + + return err; +} + +static int orinoco_ioctl_getport3(struct net_device *dev, struct iwreq *wrq) +{ + struct orinoco_private *priv = dev->priv; + int *val = (int *)wrq->u.name; + int err; + unsigned long flags; + + err = orinoco_lock(priv, &flags); + if (err) + return err; + + *val = priv->prefer_port3; + orinoco_unlock(priv, &flags); + + return 0; +} + +/* Spy is used for link quality/strength measurements in Ad-Hoc mode + * Jean II */ +static int orinoco_ioctl_setspy(struct net_device *dev, struct iw_point *srq) +{ + struct orinoco_private *priv = dev->priv; + struct sockaddr address[IW_MAX_SPY]; + int number = srq->length; + int i; + int err = 0; + unsigned long flags; + + /* Check the number of addresses */ + if (number > IW_MAX_SPY) + return -E2BIG; + + /* Get the data in the driver */ + if (srq->pointer) { + if (copy_from_user(address, srq->pointer, + sizeof(struct sockaddr) * number)) + return -EFAULT; + } + + /* Make sure nobody mess with the structure while we do */ + err = orinoco_lock(priv, &flags); + if (err) + return err; + + /* orinoco_lock() doesn't disable interrupts, so make sure the + * interrupt rx path don't get confused while we copy */ + priv->spy_number = 0; + + if (number > 0) { + /* Extract the addresses */ + for (i = 0; i < number; i++) + memcpy(priv->spy_address[i], address[i].sa_data, + ETH_ALEN); + /* Reset stats */ + memset(priv->spy_stat, 0, + sizeof(struct iw_quality) * IW_MAX_SPY); + /* Set number of addresses */ + priv->spy_number = number; + } + + /* Now, let the others play */ + orinoco_unlock(priv, &flags); + + return err; +} + +static int orinoco_ioctl_getspy(struct net_device *dev, struct iw_point *srq) +{ + struct orinoco_private *priv = dev->priv; + struct sockaddr address[IW_MAX_SPY]; + struct iw_quality spy_stat[IW_MAX_SPY]; + int number; + int i; + int err; + unsigned long flags; + + err = orinoco_lock(priv, &flags); + if (err) + return err; + + number = priv->spy_number; + if ((number > 0) && (srq->pointer)) { + /* Create address struct */ + for (i = 0; i < number; i++) { + memcpy(address[i].sa_data, priv->spy_address[i], + ETH_ALEN); + address[i].sa_family = AF_UNIX; + } + /* Copy stats */ + /* In theory, we should disable irqs while copying the stats + * because the rx path migh update it in the middle... + * Bah, who care ? - Jean II */ + memcpy(&spy_stat, priv->spy_stat, + sizeof(struct iw_quality) * IW_MAX_SPY); + for (i=0; i < number; i++) + priv->spy_stat[i].updated = 0; + } + + orinoco_unlock(priv, &flags); + + /* Push stuff to user space */ + srq->length = number; + if(copy_to_user(srq->pointer, address, + sizeof(struct sockaddr) * number)) + return -EFAULT; + if(copy_to_user(srq->pointer + (sizeof(struct sockaddr)*number), + &spy_stat, sizeof(struct iw_quality) * number)) + return -EFAULT; + + return 0; +} + +static int +orinoco_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) +{ + struct orinoco_private *priv = dev->priv; + struct iwreq *wrq = (struct iwreq *)rq; + int err = 0; + int tmp; + int changed = 0; + unsigned long flags; + + TRACE_ENTER(dev->name); + + /* In theory, we could allow most of the the SET stuff to be + * done. In practice, the lapse of time at startup when the + * card is not ready is very short, so why bother... Note + * that netif_device_present is different from up/down + * (ifconfig), when the device is not yet up, it is usually + * already ready... Jean II */ + if (! netif_device_present(dev)) + return -ENODEV; + + switch (cmd) { + case SIOCGIWNAME: + strcpy(wrq->u.name, "IEEE 802.11-DS"); + break; + + case SIOCGIWAP: + wrq->u.ap_addr.sa_family = ARPHRD_ETHER; + err = orinoco_hw_get_bssid(priv, wrq->u.ap_addr.sa_data); + break; + + case SIOCGIWRANGE: + err = orinoco_ioctl_getiwrange(dev, &wrq->u.data); + break; + + case SIOCSIWMODE: + err = orinoco_lock(priv, &flags); + if (err) + return err; + switch (wrq->u.mode) { + case IW_MODE_ADHOC: + if (! (priv->has_ibss || priv->has_port3) ) + err = -EINVAL; + else { + priv->iw_mode = IW_MODE_ADHOC; + changed = 1; + } + break; + + case IW_MODE_INFRA: + priv->iw_mode = IW_MODE_INFRA; + changed = 1; + break; + + default: + err = -EINVAL; + break; + } + set_port_type(priv); + orinoco_unlock(priv, &flags); + break; + + case SIOCGIWMODE: + err = orinoco_lock(priv, &flags); + if (err) + return err; + wrq->u.mode = priv->iw_mode; + orinoco_unlock(priv, &flags); + break; + + case SIOCSIWENCODE: + if (! priv->has_wep) { + err = -EOPNOTSUPP; + break; + } + + err = orinoco_ioctl_setiwencode(dev, &wrq->u.encoding); + if (! err) + changed = 1; + break; + + case SIOCGIWENCODE: + if (! priv->has_wep) { + err = -EOPNOTSUPP; + break; + } + + if (! capable(CAP_NET_ADMIN)) { + err = -EPERM; + break; + } + + err = orinoco_ioctl_getiwencode(dev, &wrq->u.encoding); + break; + + case SIOCSIWESSID: + err = orinoco_ioctl_setessid(dev, &wrq->u.essid); + if (! err) + changed = 1; + break; + + case SIOCGIWESSID: + err = orinoco_ioctl_getessid(dev, &wrq->u.essid); + break; + + case SIOCSIWNICKN: + err = orinoco_ioctl_setnick(dev, &wrq->u.data); + if (! err) + changed = 1; + break; + + case SIOCGIWNICKN: + err = orinoco_ioctl_getnick(dev, &wrq->u.data); + break; + + case SIOCGIWFREQ: + tmp = orinoco_hw_get_freq(priv); + if (tmp < 0) { + err = tmp; + } else { + wrq->u.freq.m = tmp; + wrq->u.freq.e = 1; + } + break; + + case SIOCSIWFREQ: + err = orinoco_ioctl_setfreq(dev, &wrq->u.freq); + if (! err) + changed = 1; + break; + + case SIOCGIWSENS: + err = orinoco_ioctl_getsens(dev, &wrq->u.sens); + break; + + case SIOCSIWSENS: + err = orinoco_ioctl_setsens(dev, &wrq->u.sens); + if (! err) + changed = 1; + break; + + case SIOCGIWRTS: + wrq->u.rts.value = priv->rts_thresh; + wrq->u.rts.disabled = (wrq->u.rts.value == 2347); + wrq->u.rts.fixed = 1; + break; + + case SIOCSIWRTS: + err = orinoco_ioctl_setrts(dev, &wrq->u.rts); + if (! err) + changed = 1; + break; + + case SIOCSIWFRAG: + err = orinoco_ioctl_setfrag(dev, &wrq->u.frag); + if (! err) + changed = 1; + break; + + case SIOCGIWFRAG: + err = orinoco_ioctl_getfrag(dev, &wrq->u.frag); + break; + + case SIOCSIWRATE: + err = orinoco_ioctl_setrate(dev, &wrq->u.bitrate); + if (! err) + changed = 1; + break; + + case SIOCGIWRATE: + err = orinoco_ioctl_getrate(dev, &wrq->u.bitrate); + break; + + case SIOCSIWPOWER: + err = orinoco_ioctl_setpower(dev, &wrq->u.power); + if (! err) + changed = 1; + break; + + case SIOCGIWPOWER: + err = orinoco_ioctl_getpower(dev, &wrq->u.power); + break; + + case SIOCGIWTXPOW: + /* The card only supports one tx power, so this is easy */ + wrq->u.txpower.value = 15; /* dBm */ + wrq->u.txpower.fixed = 1; + wrq->u.txpower.disabled = 0; + wrq->u.txpower.flags = IW_TXPOW_DBM; + break; + +#if WIRELESS_EXT > 10 + case SIOCSIWRETRY: + err = -EOPNOTSUPP; + break; + + case SIOCGIWRETRY: + err = orinoco_ioctl_getretry(dev, &wrq->u.retry); + break; +#endif /* WIRELESS_EXT > 10 */ + + case SIOCSIWSPY: + err = orinoco_ioctl_setspy(dev, &wrq->u.data); + break; + + case SIOCGIWSPY: + err = orinoco_ioctl_getspy(dev, &wrq->u.data); + break; + + case SIOCGIWPRIV: + if (wrq->u.data.pointer) { + struct iw_priv_args privtab[] = { + { SIOCIWFIRSTPRIV + 0x0, 0, 0, "force_reset" }, + { SIOCIWFIRSTPRIV + 0x1, 0, 0, "card_reset" }, + { SIOCIWFIRSTPRIV + 0x2, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + 0, "set_port3" }, + { SIOCIWFIRSTPRIV + 0x3, 0, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + "get_port3" }, + { SIOCIWFIRSTPRIV + 0x4, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + 0, "set_preamble" }, + { SIOCIWFIRSTPRIV + 0x5, 0, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + "get_preamble" }, + { SIOCIWFIRSTPRIV + 0x6, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + 0, "set_ibssport" }, + { SIOCIWFIRSTPRIV + 0x7, 0, + IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + "get_ibssport" }, + { SIOCIWLASTPRIV, 0, 0, "dump_recs" }, + }; + + err = verify_area(VERIFY_WRITE, wrq->u.data.pointer, sizeof(privtab)); + if (err) + break; + + wrq->u.data.length = sizeof(privtab) / sizeof(privtab[0]); + if (copy_to_user(wrq->u.data.pointer, privtab, sizeof(privtab))) + err = -EFAULT; + } + break; + + case SIOCIWFIRSTPRIV + 0x0: /* force_reset */ + case SIOCIWFIRSTPRIV + 0x1: /* card_reset */ + if (! capable(CAP_NET_ADMIN)) { + err = -EPERM; + break; + } + + printk(KERN_DEBUG "%s: Force scheduling reset!\n", dev->name); + + schedule_work(&priv->reset_work); + break; + + case SIOCIWFIRSTPRIV + 0x2: /* set_port3 */ + if (! capable(CAP_NET_ADMIN)) { + err = -EPERM; + break; + } + + err = orinoco_ioctl_setport3(dev, wrq); + if (! err) + changed = 1; + break; + + case SIOCIWFIRSTPRIV + 0x3: /* get_port3 */ + err = orinoco_ioctl_getport3(dev, wrq); + break; + + case SIOCIWFIRSTPRIV + 0x4: /* set_preamble */ + if (! capable(CAP_NET_ADMIN)) { + err = -EPERM; + break; + } + + /* 802.11b has recently defined some short preamble. + * Basically, the Phy header has been reduced in size. + * This increase performance, especially at high rates + * (the preamble is transmitted at 1Mb/s), unfortunately + * this give compatibility troubles... - Jean II */ + if(priv->has_preamble) { + int val = *( (int *) wrq->u.name ); + + err = orinoco_lock(priv, &flags); + if (err) + return err; + if (val) + priv->preamble = 1; + else + priv->preamble = 0; + orinoco_unlock(priv, &flags); + changed = 1; + } else + err = -EOPNOTSUPP; + break; + + case SIOCIWFIRSTPRIV + 0x5: /* get_preamble */ + if(priv->has_preamble) { + int *val = (int *)wrq->u.name; + + err = orinoco_lock(priv, &flags); + if (err) + return err; + *val = priv->preamble; + orinoco_unlock(priv, &flags); + } else + err = -EOPNOTSUPP; + break; + case SIOCIWFIRSTPRIV + 0x6: /* set_ibssport */ + if (! capable(CAP_NET_ADMIN)) { + err = -EPERM; + break; + } + + err = orinoco_ioctl_setibssport(dev, wrq); + if (! err) + changed = 1; + break; + + case SIOCIWFIRSTPRIV + 0x7: /* get_ibssport */ + err = orinoco_ioctl_getibssport(dev, wrq); + break; + + case SIOCIWLASTPRIV: + err = orinoco_debug_dump_recs(dev); + if (err) + printk(KERN_ERR "%s: Unable to dump records (%d)\n", + dev->name, err); + break; + + + default: + err = -EOPNOTSUPP; + } + + if (! err && changed && netif_running(dev)) { + err = orinoco_reconfigure(dev); + } + + TRACE_EXIT(dev->name); + + return err; +} + +struct { + u16 rid; + char *name; + int displaytype; +#define DISPLAY_WORDS 0 +#define DISPLAY_BYTES 1 +#define DISPLAY_STRING 2 +#define DISPLAY_XSTRING 3 +} record_table[] = { +#define DEBUG_REC(name,type) { HERMES_RID_##name, #name, DISPLAY_##type } + DEBUG_REC(CNFPORTTYPE,WORDS), + DEBUG_REC(CNFOWNMACADDR,BYTES), + DEBUG_REC(CNFDESIREDSSID,STRING), + DEBUG_REC(CNFOWNCHANNEL,WORDS), + DEBUG_REC(CNFOWNSSID,STRING), + DEBUG_REC(CNFOWNATIMWINDOW,WORDS), + DEBUG_REC(CNFSYSTEMSCALE,WORDS), + DEBUG_REC(CNFMAXDATALEN,WORDS), + DEBUG_REC(CNFPMENABLED,WORDS), + DEBUG_REC(CNFPMEPS,WORDS), + DEBUG_REC(CNFMULTICASTRECEIVE,WORDS), + DEBUG_REC(CNFMAXSLEEPDURATION,WORDS), + DEBUG_REC(CNFPMHOLDOVERDURATION,WORDS), + DEBUG_REC(CNFOWNNAME,STRING), + DEBUG_REC(CNFOWNDTIMPERIOD,WORDS), + DEBUG_REC(CNFMULTICASTPMBUFFERING,WORDS), + DEBUG_REC(CNFWEPENABLED_AGERE,WORDS), + DEBUG_REC(CNFMANDATORYBSSID_SYMBOL,WORDS), + DEBUG_REC(CNFWEPDEFAULTKEYID,WORDS), + DEBUG_REC(CNFDEFAULTKEY0,BYTES), + DEBUG_REC(CNFDEFAULTKEY1,BYTES), + DEBUG_REC(CNFMWOROBUST_AGERE,WORDS), + DEBUG_REC(CNFDEFAULTKEY2,BYTES), + DEBUG_REC(CNFDEFAULTKEY3,BYTES), + DEBUG_REC(CNFWEPFLAGS_INTERSIL,WORDS), + DEBUG_REC(CNFWEPKEYMAPPINGTABLE,WORDS), + DEBUG_REC(CNFAUTHENTICATION,WORDS), + DEBUG_REC(CNFMAXASSOCSTA,WORDS), + DEBUG_REC(CNFKEYLENGTH_SYMBOL,WORDS), + DEBUG_REC(CNFTXCONTROL,WORDS), + DEBUG_REC(CNFROAMINGMODE,WORDS), + DEBUG_REC(CNFHOSTAUTHENTICATION,WORDS), + DEBUG_REC(CNFRCVCRCERROR,WORDS), + DEBUG_REC(CNFMMLIFE,WORDS), + DEBUG_REC(CNFALTRETRYCOUNT,WORDS), + DEBUG_REC(CNFBEACONINT,WORDS), + DEBUG_REC(CNFAPPCFINFO,WORDS), + DEBUG_REC(CNFSTAPCFINFO,WORDS), + DEBUG_REC(CNFPRIORITYQUSAGE,WORDS), + DEBUG_REC(CNFTIMCTRL,WORDS), + DEBUG_REC(CNFTHIRTY2TALLY,WORDS), + DEBUG_REC(CNFENHSECURITY,WORDS), + DEBUG_REC(CNFGROUPADDRESSES,BYTES), + DEBUG_REC(CNFCREATEIBSS,WORDS), + DEBUG_REC(CNFFRAGMENTATIONTHRESHOLD,WORDS), + DEBUG_REC(CNFRTSTHRESHOLD,WORDS), + DEBUG_REC(CNFTXRATECONTROL,WORDS), + DEBUG_REC(CNFPROMISCUOUSMODE,WORDS), + DEBUG_REC(CNFBASICRATES_SYMBOL,WORDS), + DEBUG_REC(CNFPREAMBLE_SYMBOL,WORDS), + DEBUG_REC(CNFSHORTPREAMBLE,WORDS), + DEBUG_REC(CNFWEPKEYS_AGERE,BYTES), + DEBUG_REC(CNFEXCLUDELONGPREAMBLE,WORDS), + DEBUG_REC(CNFTXKEY_AGERE,WORDS), + DEBUG_REC(CNFAUTHENTICATIONRSPTO,WORDS), + DEBUG_REC(CNFBASICRATES,WORDS), + DEBUG_REC(CNFSUPPORTEDRATES,WORDS), + DEBUG_REC(CNFTICKTIME,WORDS), + DEBUG_REC(CNFSCANREQUEST,WORDS), + DEBUG_REC(CNFJOINREQUEST,WORDS), + DEBUG_REC(CNFAUTHENTICATESTATION,WORDS), + DEBUG_REC(CNFCHANNELINFOREQUEST,WORDS), + DEBUG_REC(MAXLOADTIME,WORDS), + DEBUG_REC(DOWNLOADBUFFER,WORDS), + DEBUG_REC(PRIID,WORDS), + DEBUG_REC(PRISUPRANGE,WORDS), + DEBUG_REC(CFIACTRANGES,WORDS), + DEBUG_REC(NICSERNUM,XSTRING), + DEBUG_REC(NICID,WORDS), + DEBUG_REC(MFISUPRANGE,WORDS), + DEBUG_REC(CFISUPRANGE,WORDS), + DEBUG_REC(CHANNELLIST,WORDS), + DEBUG_REC(REGULATORYDOMAINS,WORDS), + DEBUG_REC(TEMPTYPE,WORDS), +/* DEBUG_REC(CIS,BYTES), */ + DEBUG_REC(STAID,WORDS), + DEBUG_REC(CURRENTSSID,STRING), + DEBUG_REC(CURRENTBSSID,BYTES), + DEBUG_REC(COMMSQUALITY,WORDS), + DEBUG_REC(CURRENTTXRATE,WORDS), + DEBUG_REC(CURRENTBEACONINTERVAL,WORDS), + DEBUG_REC(CURRENTSCALETHRESHOLDS,WORDS), + DEBUG_REC(PROTOCOLRSPTIME,WORDS), + DEBUG_REC(SHORTRETRYLIMIT,WORDS), + DEBUG_REC(LONGRETRYLIMIT,WORDS), + DEBUG_REC(MAXTRANSMITLIFETIME,WORDS), + DEBUG_REC(MAXRECEIVELIFETIME,WORDS), + DEBUG_REC(CFPOLLABLE,WORDS), + DEBUG_REC(AUTHENTICATIONALGORITHMS,WORDS), + DEBUG_REC(PRIVACYOPTIONIMPLEMENTED,WORDS), + DEBUG_REC(OWNMACADDR,BYTES), + DEBUG_REC(SCANRESULTSTABLE,WORDS), + DEBUG_REC(PHYTYPE,WORDS), + DEBUG_REC(CURRENTCHANNEL,WORDS), + DEBUG_REC(CURRENTPOWERSTATE,WORDS), + DEBUG_REC(CCAMODE,WORDS), + DEBUG_REC(SUPPORTEDDATARATES,WORDS), + DEBUG_REC(BUILDSEQ,BYTES), + DEBUG_REC(FWID,XSTRING) +#undef DEBUG_REC +}; + +#define DEBUG_LTV_SIZE 128 + +static int orinoco_debug_dump_recs(struct net_device *dev) +{ + struct orinoco_private *priv = dev->priv; + hermes_t *hw = &priv->hw; + u8 *val8; + u16 *val16; + int i,j; + u16 length; + int err; + + /* I'm not sure: we might have a lock here, so we'd better go + atomic, just in case. */ + val8 = kmalloc(DEBUG_LTV_SIZE + 2, GFP_ATOMIC); + if (! val8) + return -ENOMEM; + val16 = (u16 *)val8; + + for (i = 0; i < ARRAY_SIZE(record_table); i++) { + u16 rid = record_table[i].rid; + int len; + + memset(val8, 0, DEBUG_LTV_SIZE + 2); + + err = hermes_read_ltv(hw, USER_BAP, rid, DEBUG_LTV_SIZE, + &length, val8); + if (err) { + DEBUG(0, "Error %d reading RID 0x%04x\n", err, rid); + continue; + } + val16 = (u16 *)val8; + if (length == 0) + continue; + + printk(KERN_DEBUG "%-15s (0x%04x): length=%d (%d bytes)\tvalue=", + record_table[i].name, + rid, length, (length-1)*2); + len = min(((int)length-1)*2, DEBUG_LTV_SIZE); + + switch (record_table[i].displaytype) { + case DISPLAY_WORDS: + for (j = 0; j < len / 2; j++) + printk("%04X-", le16_to_cpu(val16[j])); + break; + + case DISPLAY_BYTES: + default: + for (j = 0; j < len; j++) + printk("%02X:", val8[j]); + break; + + case DISPLAY_STRING: + len = min(len, le16_to_cpu(val16[0])+2); + val8[len] = '\0'; + printk("\"%s\"", (char *)&val16[1]); + break; + + case DISPLAY_XSTRING: + printk("'%s'", (char *)val8); + } + + printk("\n"); + } + + kfree(val8); + + return 0; +} + +struct net_device *alloc_orinocodev(int sizeof_card, int (*hard_reset)(struct orinoco_private *)) +{ + struct net_device *dev; + struct orinoco_private *priv; + + dev = alloc_etherdev(sizeof(struct orinoco_private) + sizeof_card); + priv = (struct orinoco_private *)dev->priv; + priv->ndev = dev; + if (sizeof_card) + priv->card = (void *)((unsigned long)dev->priv + sizeof(struct orinoco_private)); + else + priv->card = NULL; + + /* Setup / override net_device fields */ + dev->init = orinoco_init; + dev->hard_start_xmit = orinoco_xmit; +#ifdef HAVE_TX_TIMEOUT + dev->tx_timeout = orinoco_tx_timeout; + dev->watchdog_timeo = HZ; /* 1 second timeout */ +#endif + dev->get_stats = orinoco_get_stats; + dev->get_wireless_stats = orinoco_get_wireless_stats; + dev->do_ioctl = orinoco_ioctl; + dev->change_mtu = orinoco_change_mtu; + dev->set_multicast_list = orinoco_set_multicast_list; + /* we use the default eth_mac_addr for setting the MAC addr */ + + /* Set up default callbacks */ + dev->open = orinoco_open; + dev->stop = orinoco_stop; + priv->hard_reset = hard_reset; + + spin_lock_init(&priv->lock); + priv->open = 0; + priv->hw_unavailable = 1; /* orinoco_init() must clear this + * before anything else touches the + * hardware */ + INIT_WORK(&priv->reset_work, (void (*)(void *))orinoco_reset, dev); + + priv->last_linkstatus = 0xffff; + priv->connected = 0; + + return dev; + +} + +/********************************************************************/ +/* Module initialization */ +/********************************************************************/ + +EXPORT_SYMBOL(alloc_orinocodev); + +EXPORT_SYMBOL(__orinoco_up); +EXPORT_SYMBOL(__orinoco_down); +EXPORT_SYMBOL(orinoco_stop); +EXPORT_SYMBOL(orinoco_reinit_firmware); + +EXPORT_SYMBOL(orinoco_interrupt); + +/* Can't be declared "const" or the whole __initdata section will + * become const */ +static char version[] __initdata = "orinoco.c 0.13e (David Gibson <hermes@gibson.dropbear.id.au> and others)"; + +static int __init init_orinoco(void) +{ + printk(KERN_DEBUG "%s\n", version); + return 0; +} + +static void __exit exit_orinoco(void) +{ +} + +module_init(init_orinoco); +module_exit(exit_orinoco); diff --git a/linux/pcmcia-cs/wireless/orinoco.h b/linux/pcmcia-cs/wireless/orinoco.h new file mode 100644 index 0000000..6eb9e85 --- /dev/null +++ b/linux/pcmcia-cs/wireless/orinoco.h @@ -0,0 +1,166 @@ +/* orinoco.h + * + * Common definitions to all pieces of the various orinoco + * drivers + */ + +#ifndef _ORINOCO_H +#define _ORINOCO_H + +#include <linux/types.h> +#include <linux/spinlock.h> +#include <linux/netdevice.h> +#include <linux/wireless.h> +#include <linux/version.h> +#include "hermes.h" + +/* Workqueue / task queue backwards compatibility stuff */ + +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,41) +#include <linux/workqueue.h> +#else +#include <linux/tqueue.h> +#define work_struct tq_struct +#define INIT_WORK INIT_TQUEUE +#define schedule_work schedule_task +#endif + +/* Interrupt handler backwards compatibility stuff */ +#ifndef IRQ_NONE + +#define IRQ_NONE +#define IRQ_HANDLED +typedef void irqreturn_t; + +#endif + +/* To enable debug messages */ +//#define ORINOCO_DEBUG 3 + +#if (! defined (WIRELESS_EXT)) || (WIRELESS_EXT < 10) +#error "orinoco driver requires Wireless extensions v10 or later." +#endif /* (! defined (WIRELESS_EXT)) || (WIRELESS_EXT < 10) */ +#define WIRELESS_SPY // enable iwspy support + +#define ORINOCO_MAX_KEY_SIZE 14 +#define ORINOCO_MAX_KEYS 4 + +struct orinoco_key { + u16 len; /* always stored as little-endian */ + char data[ORINOCO_MAX_KEY_SIZE]; +} __attribute__ ((packed)); + +#define ORINOCO_INTEN ( HERMES_EV_RX | HERMES_EV_ALLOC | HERMES_EV_TX | \ + HERMES_EV_TXEXC | HERMES_EV_WTERR | HERMES_EV_INFO | \ + HERMES_EV_INFDROP ) + + +struct orinoco_private { + void *card; /* Pointer to card dependent structure */ + int (*hard_reset)(struct orinoco_private *); + + /* Synchronisation stuff */ + spinlock_t lock; + int hw_unavailable; + struct work_struct reset_work; + + /* driver state */ + int open; + u16 last_linkstatus; + int connected; + + /* Net device stuff */ + struct net_device *ndev; + struct net_device_stats stats; + struct iw_statistics wstats; + + /* Hardware control variables */ + hermes_t hw; + u16 txfid; + + + /* Capabilities of the hardware/firmware */ + int firmware_type; +#define FIRMWARE_TYPE_AGERE 1 +#define FIRMWARE_TYPE_INTERSIL 2 +#define FIRMWARE_TYPE_SYMBOL 3 + int has_ibss, has_port3, has_ibss_any, ibss_port; + int has_wep, has_big_wep; + int has_mwo; + int has_pm; + int has_preamble; + int has_sensitivity; + int nicbuf_size; + u16 channel_mask; + int broken_disableport; + + /* Configuration paramaters */ + u32 iw_mode; + int prefer_port3; + u16 wep_on, wep_restrict, tx_key; + struct orinoco_key keys[ORINOCO_MAX_KEYS]; + int bitratemode; + char nick[IW_ESSID_MAX_SIZE+1]; + char desired_essid[IW_ESSID_MAX_SIZE+1]; + u16 frag_thresh, mwo_robust; + u16 channel; + u16 ap_density, rts_thresh; + u16 pm_on, pm_mcast, pm_period, pm_timeout; + u16 preamble; +#ifdef WIRELESS_SPY + int spy_number; + u_char spy_address[IW_MAX_SPY][ETH_ALEN]; + struct iw_quality spy_stat[IW_MAX_SPY]; +#endif + + /* Configuration dependent variables */ + int port_type, createibss; + int promiscuous, mc_count; +}; + +#ifdef ORINOCO_DEBUG +extern int orinoco_debug; +#define DEBUG(n, args...) do { if (orinoco_debug>(n)) printk(KERN_DEBUG args); } while(0) +#else +#define DEBUG(n, args...) do { } while (0) +#endif /* ORINOCO_DEBUG */ + +#define TRACE_ENTER(devname) DEBUG(2, "%s: -> " __FUNCTION__ "()\n", devname); +#define TRACE_EXIT(devname) DEBUG(2, "%s: <- " __FUNCTION__ "()\n", devname); + +extern struct net_device *alloc_orinocodev(int sizeof_card, + int (*hard_reset)(struct orinoco_private *)); +extern int __orinoco_up(struct net_device *dev); +extern int __orinoco_down(struct net_device *dev); +extern int orinoco_stop(struct net_device *dev); +extern int orinoco_reinit_firmware(struct net_device *dev); +extern irqreturn_t orinoco_interrupt(int irq, void * dev_id, struct pt_regs *regs); + +/********************************************************************/ +/* Locking and synchronization functions */ +/********************************************************************/ + +/* These functions *must* be inline or they will break horribly on + * SPARC, due to its weird semantics for save/restore flags. extern + * inline should prevent the kernel from linking or module from + * loading if they are not inlined. */ +extern inline int orinoco_lock(struct orinoco_private *priv, + unsigned long *flags) +{ + spin_lock_irqsave(&priv->lock, *flags); + if (priv->hw_unavailable) { + printk(KERN_DEBUG "orinoco_lock() called with hw_unavailable (dev=%p)\n", + priv->ndev); + spin_unlock_irqrestore(&priv->lock, *flags); + return -EBUSY; + } + return 0; +} + +extern inline void orinoco_unlock(struct orinoco_private *priv, + unsigned long *flags) +{ + spin_unlock_irqrestore(&priv->lock, *flags); +} + +#endif /* _ORINOCO_H */ diff --git a/linux/pcmcia-cs/wireless/orinoco_cs.c b/linux/pcmcia-cs/wireless/orinoco_cs.c new file mode 100644 index 0000000..a3f6357 --- /dev/null +++ b/linux/pcmcia-cs/wireless/orinoco_cs.c @@ -0,0 +1,705 @@ +/* orinoco_cs.c 0.13e - (formerly known as dldwd_cs.c) + * + * A driver for "Hermes" chipset based PCMCIA wireless adaptors, such + * as the Lucent WavelanIEEE/Orinoco cards and their OEM (Cabletron/ + * EnteraSys RoamAbout 802.11, ELSA Airlancer, Melco Buffalo and others). + * It should also be usable on various Prism II based cards such as the + * Linksys, D-Link and Farallon Skyline. It should also work on Symbol + * cards such as the 3Com AirConnect and Ericsson WLAN. + * + * Copyright notice & release notes in file orinoco.c + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/ptrace.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/ioport.h> +#include <linux/netdevice.h> +#include <linux/if_arp.h> +#include <linux/etherdevice.h> +#include <linux/wireless.h> + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/cisreg.h> +#include <pcmcia/ds.h> + +#include <asm/uaccess.h> +#include <asm/io.h> +#include <asm/system.h> + +#include "orinoco.h" + +/********************************************************************/ +/* Module stuff */ +/********************************************************************/ + +MODULE_AUTHOR("David Gibson <hermes@gibson.dropbear.id.au>"); +MODULE_DESCRIPTION("Driver for PCMCIA Lucent Orinoco, Prism II based and similar wireless cards"); +#ifdef MODULE_LICENSE +MODULE_LICENSE("Dual MPL/GPL"); +#endif + +/* Module parameters */ + +/* The old way: bit map of interrupts to choose from */ +/* This means pick from 15, 14, 12, 11, 10, 9, 7, 5, 4, and 3 */ +static uint irq_mask = 0xdeb8; +/* Newer, simpler way of listing specific interrupts */ +static int irq_list[4] = { -1 }; + +/* Some D-Link cards have buggy CIS. They do work at 5v properly, but + * don't have any CIS entry for it. This workaround it... */ +static int ignore_cis_vcc; /* = 0 */ + +MODULE_PARM(irq_mask, "i"); +MODULE_PARM(irq_list, "1-4i"); +MODULE_PARM(ignore_cis_vcc, "i"); + +/********************************************************************/ +/* Magic constants */ +/********************************************************************/ + +/* + * 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 = "orinoco_cs"; + +/********************************************************************/ +/* Data structures */ +/********************************************************************/ + +/* PCMCIA specific device information (goes in the card field of + * struct orinoco_private */ +struct orinoco_pccard { + dev_link_t link; + dev_node_t node; + + /* Used to handle hard reset */ + /* yuck, we need this hack to work around the insanity of the + * PCMCIA layer */ + unsigned long hard_reset_in_progress; +}; + +/* + * A linked list of "instances" of the device. Each actual PCMCIA + * card corresponds to one device instance, and is described by one + * dev_link_t structure (defined in ds.h). + */ +static dev_link_t *dev_list; /* = NULL */ + +/********************************************************************/ +/* Function prototypes */ +/********************************************************************/ + +/* device methods */ +static int orinoco_cs_hard_reset(struct orinoco_private *priv); + +/* PCMCIA gumpf */ +static void orinoco_cs_config(dev_link_t * link); +static void orinoco_cs_release(u_long arg); +static int orinoco_cs_event(event_t event, int priority, + event_callback_args_t * args); + +static dev_link_t *orinoco_cs_attach(void); +static void orinoco_cs_detach(dev_link_t *); + +/********************************************************************/ +/* Device methods */ +/********************************************************************/ + +static int +orinoco_cs_hard_reset(struct orinoco_private *priv) +{ + struct orinoco_pccard *card = priv->card; + dev_link_t *link = &card->link; + int err; + + /* We need atomic ops here, because we're not holding the lock */ + set_bit(0, &card->hard_reset_in_progress); + + err = CardServices(ResetCard, link->handle, NULL); + if (err) + return err; + + clear_bit(0, &card->hard_reset_in_progress); + + return 0; +} + +/********************************************************************/ +/* PCMCIA stuff */ +/********************************************************************/ + +/* In 2.5 (as of 2.5.69 at least) there is a cs_error exported which + * does this, but it's not in 2.4 so we do our own for now. */ +static void +orinoco_cs_error(client_handle_t handle, int func, int ret) +{ + error_info_t err = { func, ret }; + CardServices(ReportError, handle, &err); +} + + +/* Remove zombie instances (card removed, detach pending) */ +static void +flush_stale_links(void) +{ + dev_link_t *link, *next; + + TRACE_ENTER(""); + + for (link = dev_list; link; link = next) { + next = link->next; + if (link->state & DEV_STALE_LINK) { + orinoco_cs_detach(link); + } + } + TRACE_EXIT(""); +} + +/* + * This 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 * +orinoco_cs_attach(void) +{ + struct net_device *dev; + struct orinoco_private *priv; + struct orinoco_pccard *card; + dev_link_t *link; + client_reg_t client_reg; + int ret, i; + + /* A bit of cleanup */ + flush_stale_links(); + + dev = alloc_orinocodev(sizeof(*card), orinoco_cs_hard_reset); + if (! dev) + return NULL; + priv = dev->priv; + card = priv->card; + + /* Link both structures together */ + link = &card->link; + link->priv = dev; + + /* Initialize the dev_link_t structure */ + init_timer(&link->release); + link->release.function = &orinoco_cs_release; + link->release.data = (u_long) link; + + /* Interrupt setup */ + link->irq.Attributes = IRQ_TYPE_EXCLUSIVE; + link->irq.IRQInfo1 = IRQ_INFO2_VALID | IRQ_LEVEL_ID; + if (irq_list[0] == -1) + link->irq.IRQInfo2 = irq_mask; + else + for (i = 0; i < 4; i++) + link->irq.IRQInfo2 |= 1 << irq_list[i]; + link->irq.Handler = NULL; + + /* General socket configuration defaults can go here. In this + * client, we assume very little, and rely on the CIS for + * almost everything. In most clients, many details (i.e., + * number, sizes, and attributes of IO windows) are fixed by + * the nature of the device, and can be hard-wired here. */ + link->conf.Attributes = 0; + link->conf.IntType = INT_MEMORY_AND_IO; + + /* Register with Card Services */ + /* FIXME: need a lock? */ + 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 = &orinoco_cs_event; + client_reg.Version = 0x0210; /* FIXME: what does this mean? */ + client_reg.event_callback_args.client_data = link; + + ret = CardServices(RegisterClient, &link->handle, &client_reg); + if (ret != CS_SUCCESS) { + orinoco_cs_error(link->handle, RegisterClient, ret); + orinoco_cs_detach(link); + return NULL; + } + + return link; +} /* orinoco_cs_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 +orinoco_cs_detach(dev_link_t * link) +{ + dev_link_t **linkp; + struct net_device *dev = link->priv; + + /* Locate device structure */ + for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next) + if (*linkp == link) + break; + if (*linkp == NULL) { + BUG(); + return; + } + + if (link->state & DEV_CONFIG) { + orinoco_cs_release((u_long)link); + if (link->state & DEV_CONFIG) { + link->state |= DEV_STALE_LINK; + return; + } + } + + /* Break the link with Card Services */ + if (link->handle) + CardServices(DeregisterClient, link->handle); + + /* Unlink device structure, and free it */ + *linkp = link->next; + DEBUG(0, "orinoco_cs: detach: link=%p link->dev=%p\n", link, link->dev); + if (link->dev) { + DEBUG(0, "orinoco_cs: About to unregister net device %p\n", + dev); + unregister_netdev(dev); + } + kfree(dev); +} /* orinoco_cs_detach */ + +/* + * orinoco_cs_config() is scheduled to run after a CARD_INSERTION + * event is received, to configure the PCMCIA socket, and to make the + * device available to the system. + */ + +#define CS_CHECK(fn, args...) \ + while ((last_ret=CardServices(last_fn=(fn),args))!=0) goto cs_failed + +#define CFG_CHECK(fn, args...) \ + if (CardServices(fn, args) != 0) goto next_entry + +static void +orinoco_cs_config(dev_link_t *link) +{ + struct net_device *dev = link->priv; + client_handle_t handle = link->handle; + struct orinoco_private *priv = dev->priv; + struct orinoco_pccard *card = priv->card; + hermes_t *hw = &priv->hw; + int last_fn, last_ret; + u_char buf[64]; + config_info_t conf; + cisinfo_t info; + tuple_t tuple; + cisparse_t parse; + + CS_CHECK(ValidateCIS, handle, &info); + + /* + * This reads the card's CONFIG tuple to find its + * configuration registers. + */ + tuple.DesiredTuple = CISTPL_CONFIG; + tuple.Attributes = 0; + tuple.TupleData = buf; + tuple.TupleDataMax = sizeof(buf); + tuple.TupleOffset = 0; + CS_CHECK(GetFirstTuple, handle, &tuple); + CS_CHECK(GetTupleData, handle, &tuple); + CS_CHECK(ParseTuple, handle, &tuple, &parse); + link->conf.ConfigBase = parse.config.base; + link->conf.Present = parse.config.rmask[0]; + + /* Configure card */ + link->state |= DEV_CONFIG; + + /* Look up the current Vcc */ + CS_CHECK(GetConfigurationInfo, handle, &conf); + link->conf.Vcc = conf.Vcc; + + /* + * In this loop, we scan the CIS for configuration table + * entries, each of which describes a valid card + * configuration, including voltage, IO window, memory window, + * and interrupt settings. + * + * We make no assumptions about the card to be configured: we + * use just the information available in the CIS. In an ideal + * world, this would work for any PCMCIA card, but it requires + * a complete and accurate CIS. In practice, a driver usually + * "knows" most of these things without consulting the CIS, + * and most client drivers will only use the CIS to fill in + * implementation-defined details. + */ + tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY; + CS_CHECK(GetFirstTuple, handle, &tuple); + while (1) { + cistpl_cftable_entry_t *cfg = &(parse.cftable_entry); + cistpl_cftable_entry_t dflt = { .index = 0 }; + + CFG_CHECK(GetTupleData, handle, &tuple); + CFG_CHECK(ParseTuple, handle, &tuple, &parse); + + if (cfg->flags & CISTPL_CFTABLE_DEFAULT) + dflt = *cfg; + if (cfg->index == 0) + goto next_entry; + link->conf.ConfigIndex = cfg->index; + + /* Does this card need audio output? */ + if (cfg->flags & CISTPL_CFTABLE_AUDIO) { + link->conf.Attributes |= CONF_ENABLE_SPKR; + link->conf.Status = CCSR_AUDIO_ENA; + } + + /* Use power settings for Vcc and Vpp if present */ + /* Note that the CIS values need to be rescaled */ + if (cfg->vcc.present & (1 << CISTPL_POWER_VNOM)) { + if (conf.Vcc != cfg->vcc.param[CISTPL_POWER_VNOM] / 10000) { + DEBUG(2, "orinoco_cs_config: Vcc mismatch (conf.Vcc = %d, CIS = %d)\n", conf.Vcc, cfg->vcc.param[CISTPL_POWER_VNOM] / 10000); + if (!ignore_cis_vcc) + goto next_entry; + } + } else if (dflt.vcc.present & (1 << CISTPL_POWER_VNOM)) { + if (conf.Vcc != dflt.vcc.param[CISTPL_POWER_VNOM] / 10000) { + DEBUG(2, "orinoco_cs_config: Vcc mismatch (conf.Vcc = %d, CIS = %d)\n", conf.Vcc, dflt.vcc.param[CISTPL_POWER_VNOM] / 10000); + if(!ignore_cis_vcc) + goto next_entry; + } + } + + if (cfg->vpp1.present & (1 << CISTPL_POWER_VNOM)) + link->conf.Vpp1 = link->conf.Vpp2 = + cfg->vpp1.param[CISTPL_POWER_VNOM] / 10000; + else if (dflt.vpp1.present & (1 << CISTPL_POWER_VNOM)) + link->conf.Vpp1 = link->conf.Vpp2 = + dflt.vpp1.param[CISTPL_POWER_VNOM] / 10000; + + /* Do we need to allocate an interrupt? */ + if (cfg->irq.IRQInfo1 || dflt.irq.IRQInfo1) + link->conf.Attributes |= CONF_ENABLE_IRQ; + + /* IO window settings */ + link->io.NumPorts1 = link->io.NumPorts2 = 0; + if ((cfg->io.nwin > 0) || (dflt.io.nwin > 0)) { + cistpl_io_t *io = + (cfg->io.nwin) ? &cfg->io : &dflt.io; + link->io.Attributes1 = IO_DATA_PATH_WIDTH_AUTO; + if (!(io->flags & CISTPL_IO_8BIT)) + link->io.Attributes1 = + IO_DATA_PATH_WIDTH_16; + if (!(io->flags & CISTPL_IO_16BIT)) + link->io.Attributes1 = + IO_DATA_PATH_WIDTH_8; + link->io.IOAddrLines = + io->flags & CISTPL_IO_LINES_MASK; + link->io.BasePort1 = io->win[0].base; + link->io.NumPorts1 = io->win[0].len; + if (io->nwin > 1) { + link->io.Attributes2 = + link->io.Attributes1; + link->io.BasePort2 = io->win[1].base; + link->io.NumPorts2 = io->win[1].len; + } + + /* This reserves IO space but doesn't actually enable it */ + CFG_CHECK(RequestIO, link->handle, &link->io); + } + + + /* If we got this far, we're cool! */ + + break; + + next_entry: + if (link->io.NumPorts1) + CardServices(ReleaseIO, link->handle, &link->io); + last_ret = CardServices(GetNextTuple, handle, &tuple); + if (last_ret == CS_NO_MORE_ITEMS) { + printk(KERN_ERR "GetNextTuple(). No matching CIS configuration, " + "maybe you need the ignore_cis_vcc=1 parameter.\n"); + goto cs_failed; + } + } + + /* + * Allocate an interrupt line. Note that this does not assign + * a handler to the interrupt, unless the 'Handler' member of + * the irq structure is initialized. + */ + if (link->conf.Attributes & CONF_ENABLE_IRQ) { + int i; + + link->irq.Attributes = IRQ_TYPE_EXCLUSIVE | IRQ_HANDLE_PRESENT; + link->irq.IRQInfo1 = IRQ_INFO2_VALID | IRQ_LEVEL_ID; + if (irq_list[0] == -1) + link->irq.IRQInfo2 = irq_mask; + else + for (i=0; i<4; i++) + link->irq.IRQInfo2 |= 1 << irq_list[i]; + + link->irq.Handler = orinoco_interrupt; + link->irq.Instance = dev; + + CS_CHECK(RequestIRQ, link->handle, &link->irq); + } + + /* We initialize the hermes structure before completing PCMCIA + * configuration just in case the interrupt handler gets + * called. */ + hermes_struct_init(hw, link->io.BasePort1, + HERMES_IO, HERMES_16BIT_REGSPACING); + + /* + * This actually configures the PCMCIA socket -- setting up + * the I/O windows and the interrupt mapping, and putting the + * card and host interface into "Memory and IO" mode. + */ + CS_CHECK(RequestConfiguration, link->handle, &link->conf); + + /* Ok, we have the configuration, prepare to register the netdev */ + dev->base_addr = link->io.BasePort1; + dev->irq = link->irq.AssignedIRQ; + SET_MODULE_OWNER(dev); + card->node.major = card->node.minor = 0; + + /* register_netdev will give us an ethX name */ + dev->name[0] = '\0'; + /* Tell the stack we exist */ + if (register_netdev(dev) != 0) { + printk(KERN_ERR "orinoco_cs: register_netdev() failed\n"); + goto failed; + } + + /* At this point, the dev_node_t structure(s) needs to be + * initialized and arranged in a linked list at link->dev. */ + strcpy(card->node.dev_name, dev->name); + link->dev = &card->node; /* link->dev being non-NULL is also + used to indicate that the + net_device has been registered */ + link->state &= ~DEV_CONFIG_PENDING; + + /* Finally, report what we've done */ + printk(KERN_DEBUG "%s: index 0x%02x: Vcc %d.%d", + dev->name, link->conf.ConfigIndex, + link->conf.Vcc / 10, link->conf.Vcc % 10); + if (link->conf.Vpp1) + printk(", Vpp %d.%d", link->conf.Vpp1 / 10, + link->conf.Vpp1 % 10); + if (link->conf.Attributes & CONF_ENABLE_IRQ) + printk(", irq %d", link->irq.AssignedIRQ); + if (link->io.NumPorts1) + printk(", io 0x%04x-0x%04x", link->io.BasePort1, + link->io.BasePort1 + link->io.NumPorts1 - 1); + if (link->io.NumPorts2) + printk(" & 0x%04x-0x%04x", link->io.BasePort2, + link->io.BasePort2 + link->io.NumPorts2 - 1); + printk("\n"); + + return; + + cs_failed: + orinoco_cs_error(link->handle, last_fn, last_ret); + + failed: + orinoco_cs_release((u_long) link); +} /* orinoco_cs_config */ + +/* + * After a card is removed, orinoco_cs_release() will unregister the + * device, and release the PCMCIA configuration. If the device is + * still open, this will be postponed until it is closed. + */ +static void +orinoco_cs_release(u_long arg) +{ + dev_link_t *link = (dev_link_t *) arg; + struct net_device *dev = link->priv; + struct orinoco_private *priv = dev->priv; + unsigned long flags; + + /* We're committed to taking the device away now, so mark the + * hardware as unavailable */ + spin_lock_irqsave(&priv->lock, flags); + priv->hw_unavailable++; + spin_unlock_irqrestore(&priv->lock, flags); + + /* Don't bother checking to see if these succeed or not */ + CardServices(ReleaseConfiguration, link->handle); + if (link->io.NumPorts1) + CardServices(ReleaseIO, link->handle, &link->io); + if (link->irq.AssignedIRQ) + CardServices(ReleaseIRQ, link->handle, &link->irq); + link->state &= ~DEV_CONFIG; +} /* orinoco_cs_release */ + +/* + * The card status event handler. Mostly, this schedules other stuff + * to run after an event is received. + */ +static int +orinoco_cs_event(event_t event, int priority, + event_callback_args_t * args) +{ + dev_link_t *link = args->client_data; + struct net_device *dev = link->priv; + struct orinoco_private *priv = dev->priv; + struct orinoco_pccard *card = priv->card; + int err = 0; + unsigned long flags; + + switch (event) { + case CS_EVENT_CARD_REMOVAL: + link->state &= ~DEV_PRESENT; + if (link->state & DEV_CONFIG) { + orinoco_lock(priv, &flags); + + netif_device_detach(dev); + priv->hw_unavailable++; + + orinoco_unlock(priv, &flags); + } + break; + + case CS_EVENT_CARD_INSERTION: + link->state |= DEV_PRESENT | DEV_CONFIG_PENDING; + orinoco_cs_config(link); + break; + + case CS_EVENT_PM_SUSPEND: + link->state |= DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_RESET_PHYSICAL: + /* Mark the device as stopped, to block IO until later */ + if (link->state & DEV_CONFIG) { + /* This is probably racy, but I can't think of + a better way, short of rewriting the PCMCIA + layer to not suck :-( */ + if (! test_bit(0, &card->hard_reset_in_progress)) { + spin_lock_irqsave(&priv->lock, flags); + + err = __orinoco_down(dev); + if (err) + printk(KERN_WARNING "%s: %s: Error %d downing interface\n", + dev->name, + event == CS_EVENT_PM_SUSPEND ? "SUSPEND" : "RESET_PHYSICAL", + err); + + netif_device_detach(dev); + priv->hw_unavailable++; + + spin_unlock_irqrestore(&priv->lock, flags); + } + + 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) { + /* FIXME: should we double check that this is + * the same card as we had before */ + CardServices(RequestConfiguration, link->handle, + &link->conf); + + if (! test_bit(0, &card->hard_reset_in_progress)) { + err = orinoco_reinit_firmware(dev); + if (err) { + printk(KERN_ERR "%s: Error %d re-initializing firmware\n", + dev->name, err); + break; + } + + spin_lock_irqsave(&priv->lock, flags); + + netif_device_attach(dev); + priv->hw_unavailable--; + + if (priv->open && ! priv->hw_unavailable) { + err = __orinoco_up(dev); + if (err) + printk(KERN_ERR "%s: Error %d restarting card\n", + dev->name, err); + + } + + spin_unlock_irqrestore(&priv->lock, flags); + } + } + break; + } + + return err; +} /* orinoco_cs_event */ + +/********************************************************************/ +/* Module initialization */ +/********************************************************************/ + +/* Can't be declared "const" or the whole __initdata section will + * become const */ +static char version[] __initdata = "orinoco_cs.c 0.13e (David Gibson <hermes@gibson.dropbear.id.au> and others)"; + +static int __init +init_orinoco_cs(void) +{ + servinfo_t serv; + + printk(KERN_DEBUG "%s\n", version); + + CardServices(GetCardServicesInfo, &serv); + if (serv.Revision != CS_RELEASE_CODE) { + printk(KERN_NOTICE "orinoco_cs: Card Services release " + "does not match!\n"); + return -EINVAL; + } + + register_pccard_driver(&dev_info, &orinoco_cs_attach, &orinoco_cs_detach); + + return 0; +} + +static void __exit +exit_orinoco_cs(void) +{ + unregister_pccard_driver(&dev_info); + + if (dev_list) + DEBUG(0, "orinoco_cs: Removing leftover devices.\n"); + while (dev_list != NULL) { + if (dev_list->state & DEV_CONFIG) + orinoco_cs_release((u_long) dev_list); + orinoco_cs_detach(dev_list); + } +} + +module_init(init_orinoco_cs); +module_exit(exit_orinoco_cs); + |