RFC: kernel-based PCMCIA ...

Werner Almesberger (almesber@lrc.di.epfl.ch)
Tue, 18 May 1999 09:28:26 +0200 (MET DST)


... for getting access to storage cards in early stages of porting,
where user space development is still difficult. (E.g. because there's
no libc port, no distribution, etc.)

This patch is vs. 2.3.3. Documentation is included.

Comments ?

- Werner (rushing off to the airport)

---------------------------------- cut here -----------------------------------

--- linux.orig/Documentation/Configure.help Tue May 18 03:19:17 1999
+++ linux/Documentation/Configure.help Tue May 18 03:31:41 1999
@@ -1598,6 +1598,16 @@
Documentation/mca.txt (and especially the web page given there)
before attempting to build an MCA bus kernel.

+Kernel-based PCMCIA support
+CONFIG_KERNEL_PCMCIA
+ Enable limited support for PCMCIA/CompactFlash devices directly in
+ the kernel. This should be used only during early development of
+ ports to PDAs, embedded systems, etc. See Documentation/PCMCIA.txt
+ for details. If unsure, say N.
+
+ See http://hyper.stanford.edu/HyperNews/get/pcmcia/home.html for
+ regular PCMCIA support under Linux.
+
SGI Visual Workstation support
CONFIG_VISWS
The SGI Visual Workstation series is an IA32-based workstation
--- /dev/null Tue May 5 22:32:27 1998
+++ linux/Documentation/PCMCIA.txt Tue May 18 03:31:41 1999
@@ -0,0 +1,36 @@
+Kernel based PCMCIA/CompactFlash access
+---------------------------------------
+
+On many portable and embedded platforms, the only persistent mass-storage
+is in a PCMCIA or CompactFlash slot. This can greatly complicate porting
+Linux to such a device, because PCMCIA support under Linux lives largely
+in user space and requires modules. This means that a fairly complete
+user-space (cross-)development environment, with recent libraries, etc.,
+is necessary to implement PCMCIA support. It also means that a large
+portion of the system has to be operational before work on PCMCIA support
+can begin.
+
+An alternative to using persistent storage is to use initrd. This has the
+disadvantages that the entire file system has to fit into RAM, and also
+that the initrd image has to be downloaded at least after each change.
+
+The kernel based PCMCIA/CF support (in drivers/pcmcia) solves this
+problem by giving the kernel direct access to the PCMCIA bus, and
+allowing the use of the most common storage devices, i.e. the ones which
+are accessed exactly like an IDE hard disk. The main component is a
+parser for the CIS (Card Information Structure), which describes the type
+and operation modes of a PCMCIA or CompactFlash card. In addition to
+this, only a few interface functions and the IDE driver are needed to
+build fully functional disk access into the kernel.
+
+All this is intentionally kept very simple: there is no support for hot-
+plugging, not all of the CIS is decoded, support for variable voltage is
+cumbersome, only one device can be detected, etc. Such advanced features
+are better implemented in the user-space PCMCIA support, instead of
+duplicating functionality in the kernel.
+
+In order to make kernel-based PCMCIA support available, you need to add
+
+bool 'Simple kernel-based PCMCIA Support' CONFIG_KERNEL_PCMCIA
+
+to a suitable architecture-specific config.in file.
--- linux.orig/Makefile Sat May 15 01:04:47 1999
+++ linux/Makefile Tue May 18 03:31:41 1999
@@ -186,6 +186,10 @@
DRIVERS := $(DRIVERS) drivers/net/irda/irda_drivers.a
endif

+ifeq ($(CONFIG_KERNEL_PCMCIA),y)
+DRIVERS := $(DRIVERS) drivers/pcmcia/pcmcia.a
+endif
+
include arch/$(ARCH)/Makefile

.S.s:
--- linux.orig/drivers/Makefile Mon May 10 19:18:34 1999
+++ linux/drivers/Makefile Tue May 18 03:31:41 1999
@@ -10,7 +10,7 @@
SUB_DIRS := block char net misc sound
MOD_SUB_DIRS := $(SUB_DIRS)
ALL_SUB_DIRS := $(SUB_DIRS) pci scsi sbus cdrom isdn pnp \
- macintosh video dio zorro fc4 usb
+ macintosh video dio zorro fc4 usb pcmcia

ifdef CONFIG_DIO
SUB_DIRS += dio
@@ -99,6 +99,11 @@
ifeq ($(CONFIG_FC4),m)
MOD_SUB_DIRS += fc4
endif
+endif
+
+ifeq ($(CONFIG_KERNEL_PCMCIA),y)
+SUB_DIRS += pcmcia
+ALL_SUB_DIRS += pcmcia
endif

# When MOD_LIST_NAME is set, make will try to add $(MOD_SUB_DIRS).o to
--- linux.orig/drivers/block/genhd.c Tue May 18 03:19:17 1999
+++ linux/drivers/block/genhd.c Tue May 18 03:31:41 1999
@@ -59,6 +59,7 @@
extern int chr_dev_init(void);
extern int blk_dev_init(void);
extern int scsi_dev_init(void);
+extern void pcmcia_init(void);
extern int net_dev_init(void);

#ifdef CONFIG_PPC
@@ -1340,6 +1341,9 @@
#endif
#ifdef CONFIG_SCSI
scsi_dev_init();
+#endif
+#ifdef CONFIG_KERNEL_PCMCIA
+ pcmcia_init();
#endif
#ifdef CONFIG_INET
net_dev_init();
--- /dev/null Tue May 5 22:32:27 1998
+++ linux/drivers/pcmcia/Makefile Tue May 18 09:39:56 1999
@@ -0,0 +1,28 @@
+#
+# Makefile for the kernel-based PCMCIA/CF drivers.
+#
+# Note! Dependencies are done automagically by 'make dep', which also
+# removes any old dependencies. DON'T put your own dependencies here
+# unless it's something special (ie not a .c file).
+#
+# Note 2! The CFLAGS definitions are now inherited from the
+# parent makes..
+#
+
+SUB_DIRS :=
+MOD_SUB_DIRS :=
+ALL_SUB_DIRS :=
+
+L_TARGET := pcmcia.a
+M_OBJS :=
+L_OBJS := cis.o
+LX_OBJS :=
+
+ifeq ($(CONFIG_ARCH_PSION),y)
+ L_OBJS += psion.o
+endif
+ifeq ($(CONFIG_ARCH_GEOFOX),y)
+ L_OBJS += geofox.o
+endif
+
+include $(TOPDIR)/Rules.make
--- /dev/null Tue May 5 22:32:27 1998
+++ linux/drivers/pcmcia/cis.c Tue May 18 09:38:18 1999
@@ -0,0 +1,447 @@
+/*
+ * cis.c - Simplistic CIS scanner and PCMCIA device finder (disks only)
+ *
+ * Hacked 1998,1999 by Werner Almesberger
+ */
+
+/*
+ * In the DOS world, this would be part of a "point enabler". The point is to
+ * avoid having a complete PCMCIA stack with tons of drivers if you already
+ * know exactly what you'll find. Too bad if you're wrong.
+ *
+ * Anyway, this has a lot less overhead than the normal Linux way of using
+ * PCMCIA, i.e. cardmgr+modules, requiring libc, probably insmod, etc., so
+ * it's more useful while we're still struggling with trivial problems.
+ */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/pcmcia.h>
+
+
+/*
+ * The following code reads the CIS, determines if the card is in fact an ATA
+ * disk drive, and selects a suitable configuration. A configuration is
+ * accepted if:
+ * - it uses the Vcc available for the PCMCIA/CF slot
+ * - it doesn't need Vpp
+ * - it allows 8 and 16 bit bus accesses
+ * - it defines a fixed IO port range (so we don't have to select one)
+ */
+
+
+#define MAX_CIS_SIZE 0x200
+#define MAX_IO_AREAS 2
+
+struct power {
+ unsigned long v_max,v_min,v_nom;
+ unsigned long i_pdwn,i_peak,i_avg,i_static;
+};
+
+struct cfg_data {
+ struct power vcc,vpp1,vpp2;
+ int bus;
+ unsigned long io_base[MAX_IO_AREAS];
+ unsigned long io_size[MAX_IO_AREAS];
+};
+
+static u8 selected __initdata = 0;
+static struct cfg_data selected_cfg __initdata = { { 0, }, },
+ curr_cfg __initdata = { { 0, }, }, dfl_cfg __initdata = { { 0, }, };
+
+static unsigned long curr __initdata = 0, next __initdata = 0;
+static int got_version __initdata = 0, got_function __initdata = 0,
+ got_ata __initdata = 0, got_config __initdata = 0;
+static unsigned long fcr_base __initdata = 0;
+ /* FCR base address in attrib mem */
+static unsigned long fcr_mask __initdata = 0;
+ /* Registers available in FCR */
+static int verbose __initdata = 0;
+
+#define CISTPL_VERS_1 0x15
+#define CISTPL_CONFIG 0x1a
+#define CISTPL_CFTABLE_ENTRY 0x1b
+#define CISTPL_FUNCID 0x21
+#define CISTPL_FUNCE 0x22
+#define CISTPL_END 0xff
+
+
+static int __init next_cis_tupel(u8 *res)
+{
+ *res = cis_read(next);
+ if (*res == CISTPL_END) return 0;
+ curr = next+4;
+ if (curr >= MAX_CIS_SIZE) return -1;
+ next = curr+cis_read(curr-2)*2;
+ if (next >= MAX_CIS_SIZE) return -1;
+ return 0;
+}
+
+
+static int __init next_cis_byte(u8 *res)
+{
+ if (curr == next) return -1;
+ *res = cis_read(curr);
+ curr += 2;
+ return 0;
+}
+
+
+static int __init skip_cis_bytes(int bytes)
+{
+ if (curr+bytes*2 > next) return -1;
+ curr += bytes*2;
+ return 0;
+}
+
+
+static int __init get_value(int mult,unsigned long *res)
+{
+ static unsigned long xlat[] = __initlocaldata {
+ 10, 12, 13, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 70, 80, 90 };
+ u8 v;
+ int i;
+
+ if (next_cis_byte(&v)) return -1;
+ mult /= 10; /* because xlat is scaled *10 */
+ for (i = 0; i < (v & 7); i++) mult *= 10;
+ *res = xlat[(v >> 3) & 15]*mult;
+ mult /= 10;
+ while (v & 0x80) {
+ if (next_cis_byte(&v)) return -1;
+ if ((v & 0x7f) > 99) {
+ printk(KERN_WARNING " Don't grok power ext 0x%02x\n",
+ v & 0x7f);
+ return 0;
+ }
+ *res += (v & 0x7f)*mult;
+ mult /= 100;
+ }
+ return 0;
+}
+
+
+static int __init parse_power(struct power *power)
+{
+ u8 mask;
+
+ if (next_cis_byte(&mask)) return -1; /* TPCE_PD */
+ if ((mask & 0x01) && get_value(10,&power->v_nom)) return -1;
+ if ((mask & 0x02) && get_value(10,&power->v_min)) return -1;
+ if ((mask & 0x04) && get_value(10,&power->v_max)) return -1;
+ if ((mask & 0x08) && get_value(100,&power->i_static)) return -1;
+ if ((mask & 0x10) && get_value(100,&power->i_avg)) return -1;
+ if ((mask & 0x20) && get_value(100,&power->i_peak)) return -1;
+ if ((mask & 0x40) && get_value(100,&power->i_pdwn)) return -1;
+ return 0;
+}
+
+
+static int __init parse_entry(u8 fs)
+{
+ curr_cfg = dfl_cfg;
+ if ((fs & 3) && parse_power(&curr_cfg.vcc)) return -1;
+ if ((fs & 3) > 1 && parse_power(&curr_cfg.vpp1)) return -1;
+ if ((fs & 3) > 2 && parse_power(&curr_cfg.vpp2)) return -1;
+ if (fs & 4) {
+ u8 td;
+
+ if (next_cis_byte(&td)) return -1; /* TPCE_TD */
+ if ((td & 3) != 3)
+ if (skip_cis_bytes(1)) return -1;
+ if (((td >> 2) & 7) != 7)
+ if (skip_cis_bytes(1)) return -1;
+ }
+ if (fs & 8) {
+ u8 io;
+ u8 sizes;
+ int areas,i,j;
+
+ if (next_cis_byte(&io)) return -1; /* TPCE_IO */
+ for (i = 0; i < MAX_IO_AREAS; i++)
+ curr_cfg.io_base[i] = curr_cfg.io_size[i] = 0;
+ if ((io >> 5) & 3) curr_cfg.bus = (io >> 5) & 3;
+ else {
+ printk(KERN_WARNING " Unrecognized IO bus size (0)\n");
+ return 0;
+ }
+ if (!(io & 0x80)) return 0;
+ if (next_cis_byte(&sizes)) return -1;
+ areas = (sizes & 15)+1;
+ if (areas > MAX_IO_AREAS) areas = MAX_IO_AREAS;
+ for (i = 0; i < areas; i++) {
+ for (j = 0; j < ((sizes >> 4) & 3); j++) {
+ u8 v;
+
+ if (next_cis_byte(&v)) return -1;
+ curr_cfg.io_base[i] |= v << (j*8);
+ }
+ for (j = 0; j < (sizes >> 6); j++) {
+ u8 v;
+
+ if (next_cis_byte(&v)) return -1;
+ curr_cfg.io_size[i] |= v << (j*8);
+ }
+ /*if (!curr_cfg.io_size)
+ curr_cfg.io_size[i] = 1 << (io & 0x1f);*/
+ }
+ }
+ return 0;
+}
+
+
+static void __init print_pow_prm(unsigned long value,const char *suffix,
+ const char *unit)
+{
+ char s[2];
+
+ while (*suffix && !(value % 1000)) {
+ value /= 1000;
+ suffix++;
+ }
+ if (*suffix && value > 1000) {
+ s[0] = suffix[1];
+ printk("%ld.",value/1000);
+ if (value % 10) printk("%03ld",value % 1000);
+ else if (value % 100) printk("%02ld",(value % 1000)/10);
+ else printk("%ld",(value % 1000)/100);
+ }
+ else {
+ s[0] = suffix[0];
+ printk("%ld",value);
+ }
+ s[1] = 0;
+ printk("%s%s",s,unit);
+}
+
+
+static void __init print_power(const char *name,struct power power)
+{
+ if (!power.v_nom && !power.v_min && !power.v_max) return;
+ printk(KERN_INFO " %s: ",name);
+ print_pow_prm(power.v_nom,"um","V");
+ printk(" (");
+ print_pow_prm(power.v_min,"um","V");
+ printk("-");
+ print_pow_prm(power.v_max,"um","V");
+ printk(")");
+ if (!power.i_pdwn && !power.i_peak && !power.i_avg && !power.i_static) {
+ printk("\n");
+ return;
+ }
+ printk(" current ");
+ if (power.i_avg) {
+ print_pow_prm(power.i_avg,"num","A");
+ printk(" (");
+ }
+ if (power.i_pdwn) {
+ print_pow_prm(power.i_pdwn,"num","A");
+ printk(",");
+ }
+ print_pow_prm(power.i_static,"num","A");
+ printk("-");
+ if (power.i_peak) print_pow_prm(power.i_peak,"num","A");
+ if (power.i_avg) printk(")");
+ printk("\n");
+}
+
+
+static void __init print_entry(void)
+{
+ static const char *bsz[] __initlocaldata = { "???","8","16","8/16" };
+ int i;
+
+ print_power("Vcc",curr_cfg.vcc);
+ print_power("Vpp1",curr_cfg.vpp1);
+ print_power("Vpp2",curr_cfg.vpp2);
+ if (!curr_cfg.bus && !curr_cfg.io_base[0] && !curr_cfg.io_size[0])
+ return;
+ printk(KERN_INFO " IO");
+ for (i = 0; i < MAX_IO_AREAS; i++)
+ if (curr_cfg.io_base[i] || curr_cfg.io_size[i])
+ printk(" 0x%lx-0x%lx",curr_cfg.io_base[i],
+ curr_cfg.io_base[i]+curr_cfg.io_size[i]);
+ if (curr_cfg.bus) printk(" bus %s bit",bsz[curr_cfg.bus]);
+ printk("\n");
+}
+
+
+static int __init accept_cfg(int millivolts)
+{
+ unsigned long mv;
+
+ if (!curr_cfg.vcc.v_nom) return 0;
+ if (curr_cfg.vpp1.v_nom || curr_cfg.vpp2.v_nom) return 0;
+ if (!curr_cfg.bus || !curr_cfg.io_base[0] || !curr_cfg.io_size[0] ||
+ !curr_cfg.io_base[1] || !curr_cfg.io_size[1])
+ return 0;
+ if (curr_cfg.bus != 3) return 0;
+ /* don't know what accesses the IDE driver will attempt, so we
+ require 8 and 16 bit */
+ mv = curr_cfg.vcc.v_nom/1000; /* convert to mV */
+ /*
+ * Allow 10% tolerance.
+ */
+ if (millivolts < mv-mv/10 || millivolts> mv+mv/10) return 0;
+ return 1;
+}
+
+
+static int __init parse_tupel(u8 tupel,int millivolts)
+{
+ u8 v,v2;
+ int first,i;
+
+ switch (tupel) {
+ case CISTPL_VERS_1:
+ got_version = 1;
+ if (skip_cis_bytes(2)) return -1;
+ /* TPLLV1_MAJOR, TPLLV1_MINOR */
+ printk(KERN_INFO "PCMCIA device:");
+ first = 1; /* TPLLV1_INFO */
+ while (1) {
+ if (next_cis_byte(&v)) return -1;
+ if (v == 0xff) break;
+ printk("%s %c",first ? "" : ";",v);
+ first = 0;
+ while (1) {
+ if (next_cis_byte(&v)) return -1;
+ if (!v || v == 0xff) break;
+ printk("%c",v);
+ }
+ }
+ printk("\n");
+ break;
+ case CISTPL_CONFIG:
+ got_config = 1;
+ if (next_cis_byte(&v)) return -1; /* TPCC_SZ */
+ if (skip_cis_bytes(1)) return -1; /* TPCC_LAST */
+ fcr_base = 0; /* TPCC_RADR */
+ for (i = 0; i <= (v & 3); i++) {
+ if (next_cis_byte(&v2)) return -1;
+ fcr_base |= v2 << (8*i);
+ }
+ fcr_mask = 0; /* TPCC_RMSK */
+ for (i = 0; i <= ((v >> 2) & 15); i++) {
+ if (next_cis_byte(&v2)) return -1;
+ fcr_mask |= v2 << (8*i);
+ }
+ break;
+ case CISTPL_CFTABLE_ENTRY:
+ if (next_cis_byte(&v)) return -1; /* TPCE_INDX */
+ if (v & 0x80)
+ if (skip_cis_bytes(1)) return -1; /* TPCE_IF */
+ if (next_cis_byte(&v2)) return -1; /* TPCE_FS */
+ if (parse_entry(v2)) return -1;
+ if (v & 0x40) dfl_cfg = curr_cfg;
+ if (!selected && accept_cfg(millivolts)) {
+ selected = v & 0x3f;
+ selected_cfg = curr_cfg;
+ print_entry();
+ }
+ break;
+ case CISTPL_FUNCID:
+ if (next_cis_byte(&v)) return -1; /* TPFID_FUNCTION */
+ if (v == 0x04) got_function = 1;
+ else printk(KERN_WARNING " Unrecognized device "
+ "function 0x%02x\n",v);
+ break;
+ case CISTPL_FUNCE:
+ if (!got_function) break;
+ if (next_cis_byte(&v)) return -1; /* TPLFE_TYPE */
+ switch (v) {
+ case 1:
+ if (next_cis_byte(&v)) return -1;
+ /* TPLFE_DATA */
+ if (v == 1) break;
+ printk(KERN_WARNING " Unrecognized "
+ "interface 0x%02x\n",v);
+ return 0;
+ case 2:
+ if (next_cis_byte(&v)) return -1;
+ /* TPLFE_DATA */
+ if (v & 3) {
+ printk(KERN_WARNING " Device "
+ "needs Vpp (not "
+ "supported)\n");
+ return 0;
+ }
+ break;
+ default:
+ printk(KERN_WARNING " Unrecognized "
+ "TPLFE_TYPE 0x%02x\n",v);
+ return 0;
+ }
+ got_ata = 1;
+ break;
+ default:
+ if (verbose)
+ printk(KERN_DEBUG " (Ignoring tupel "
+ "0x%02x)\n",tupel);
+ }
+ return 0;
+}
+
+
+int __init parse_cis(int millivolts,unsigned long *fcr,u8 *cfg,
+ unsigned long *io_base0,unsigned long *io_base1)
+{
+ u8 tupel;
+
+ curr = next = 0;
+ got_version = got_function = got_ata = got_config = 0;
+ while (1) {
+ if (next_cis_tupel(&tupel)) return -1;
+ if (tupel == CISTPL_END) break;
+ if (parse_tupel(tupel,millivolts)) return -1;
+ }
+ if (!got_version || !got_function || !got_ata || !got_config ||
+ !selected) {
+ printk(KERN_INFO "Device skipped (reason:%s%s%s%s%s)\n",
+ got_version ? "" : " Bad version",
+ got_function ? "" : " No function",
+ got_ata ? "" : " Not ATA",
+ got_config ? "" : " No configuration",
+ selected ? "" : " Not selected");
+ return 0;
+ }
+ printk(KERN_INFO "Device selected. FCR at 0x%lx, mask 0x%lx. "
+ "Configuration index 0x%02x\n",fcr_base,fcr_mask,selected);
+ *fcr = fcr_base;
+ *cfg = selected;
+ *io_base0 = selected_cfg.io_base[0];
+ *io_base1 = selected_cfg.io_base[1];
+ return 1;
+}
+
+
+/*
+ * If we didn't find any suitable device (or if we simply failed to understand
+ * the CIS), we dump the raw CIS contents now.
+ */
+
+
+void __init dump_cis(void)
+{
+ unsigned long p;
+
+ printk("CIS dump follows:\n");
+ p = 0;
+ while (cis_read(p) != 0xff) {
+ int i;
+
+ if (p >= MAX_CIS_SIZE) {
+ printk("TOO MUCH !\n");
+ return;
+ }
+ printk(" %02x:",cis_read(p));
+ p += 2;
+ for (i = cis_read(p); i; i--) {
+ p += 2;
+ printk(" %02x",cis_read(p));
+ }
+ printk("\n");
+ p += 2;
+ }
+}
--- /dev/null Tue May 5 22:32:27 1998
+++ linux/drivers/pcmcia/skeleton.c Tue May 18 09:39:23 1999
@@ -0,0 +1,57 @@
+/*
+ * skeleton.c - Skeleton for system-specific PCMCIA functions
+ *
+ * Hacked 1999 by Werner Almesberger
+ */
+
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/hdreg.h>
+#include <linux/pcmcia.h>
+
+
+u8 __init cis_read(unsigned long offset)
+{
+ /*
+ * From psion.c:
+
+ return *(u8 *) PCMCIA_ATTR8(offset);
+
+ */
+ return 0; /* dummy */
+}
+
+
+void __init pcmcia_init(void)
+{
+ unsigned long fcr,io0,io1;
+ u8 cfg;
+ int res;
+
+ /* Power up PCMCIA slot, enable drivers, check if card present */
+ /* Obtain available voltage, or - if fixed - just use that constant
+ (e.g. 3.3 V) */
+ res = parse_cis(3300,&fcr,&cfg,&io0,&io1);
+ if (res < 0) {
+ printk(KERN_WARNING
+ "CompactFlash/PCMCIA card absent or invalid.\n");
+ return;
+ }
+ if (!res) {
+ dump_cis();
+ return;
+ }
+ /* Write 0 to CCSR (which is at address "fcr"+2) to clear pending
+ interrupts */
+ /* Write "cfg" into COR (which is at address "fcr") */
+ /* Route interrupts, enable PCMCIA IO space, etc. */
+ /*
+ * Register IDE driver:
+
+ if (ide_register(io0,io1,IRQ_...) < 0)
+ printk("pcmcia_init: ide_register failed\n");
+
+ */
+}
--- /dev/null Tue May 5 22:32:27 1998
+++ linux/include/linux/pcmcia.h Tue May 18 03:31:41 1999
@@ -0,0 +1,44 @@
+#ifndef _LINUX_PCMCIA_H_
+#define _LINUX_PCMCIA_H_
+
+/*
+ * PCMCIA device finder functions
+ *
+ * Written 1999 by Werner Almesberger
+ */
+
+#include <linux/types.h>
+
+
+/*
+ * Find a disk device. "millivolts" is the available Vcc. Returns the base
+ * address of the configuration registers in *fcr, the index of the selected
+ * configuration in *cfg, and the base addresses of the two IO ranges in
+ * *io_base0 and *io_base1. (Unused IO ranges are returned as 0.) The return
+ * value is positive if a suitable device has been found, zero if not, and
+ * negative if the CIS was absent or unparseable.
+ */
+
+int parse_cis(int millivolts,unsigned long *fcr,u8 *cfg,
+ unsigned long *io_base0,unsigned long *io_base1);
+
+/*
+ * Dump the CIS content using printk.
+ */
+
+void dump_cis(void);
+
+/*
+ * Read a byte from the CIS at the specified offset. This function needs to be
+ * provided by the architecture-specific interface.
+ */
+
+u8 cis_read(unsigned long offset);
+
+/*
+ * Enable the PCMCIA bus, call parse_cis, and activate device
+ */
+
+void pcmcia_init(void);
+
+#endif

-- 
  _________________________________________________________________________
 / Werner Almesberger, DI-ICA,EPFL,CH   werner.almesberger@lrc.di.epfl.ch /
/_IN_R_131__Tel_+41_21_693_6621__Fax_+41_21_693_6610_____________________/

- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.rutgers.edu Please read the FAQ at http://www.tux.org/lkml/