/*
 *  linux/arch/i386/kernel/mca.c
 *  Written by Martin Kolinek, February 1996
 *
 * Changes:
 *
 *	Chris Beauregard July 28th, 1996
 *	- Fixed up integrated SCSI detection
 *
 *	Chris Beauregard August 3rd, 1996
 *	- Made mca_info local
 *	- Made integrated registers accessible through standard function calls
 *	- Added name field
 *	- More sanity checking
 *
 *	Chris Beauregard August 9th, 1996
 *	- Rewrote /proc/mca
 *
 *	Chris Beauregard January 7th, 1997
 *	- Added basic NMI-processing
 *	- Added more information to mca_info structure
 *
 *	David Weinehall October 12th, 1998
 *	- Made a lot of cleaning up in the source
 *	- Added use of save_flags / restore_flags
 *	- Added the 'driver_loaded' flag in MCA_adapter
 *	- Added an alternative implemention of ZP Gu's mca_find_unused_adapter
 *
 *	David Weinehall March 24th, 1999
 *	- Fixed the output of 'Driver Installed' in /proc/mca/pos
 *	- Made the Integrated Video & SCSI show up even if they have id 0000
 *
 *	Alexander Viro November 9th, 1999
 *	- Switched to regular procfs methods
 *
 *	Alfred Arnold & David Weinehall August 23rd, 2000
 *	- Added support for Planar POS-registers
 */

#include <linux/module.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/mca.h>
#include <asm/system.h>
#include <asm/io.h>
#include <linux/proc_fs.h>
#include <linux/mman.h>
#include <linux/config.h>
#include <linux/mm.h>
#include <linux/pagemap.h>
#include <linux/ioport.h>
#include <asm/uaccess.h>
#include <linux/init.h>
#include <asm/arch_hooks.h>

/* This structure holds MCA information. Each (plug-in) adapter has
 * eight POS registers. Then the machine may have integrated video and
 * SCSI subsystems, which also have eight POS registers.
 * Finally, the motherboard (planar) has got POS-registers.
 * Other miscellaneous information follows.
 */

typedef enum {
	MCA_ADAPTER_NORMAL = 0,
	MCA_ADAPTER_NONE = 1,
	MCA_ADAPTER_DISABLED = 2,
	MCA_ADAPTER_ERROR = 3
} MCA_AdapterStatus;

struct MCA_adapter {
	MCA_AdapterStatus status;	/* is there a valid adapter? */
	int id;				/* adapter id value */
	unsigned char pos[8];		/* POS registers */
	int driver_loaded;		/* is there a driver installed? */
					/* 0 - No, 1 - Yes */
	char name[48];			/* adapter-name provided by driver */
	char procname[8];		/* name of /proc/mca file */
	MCA_ProcFn procfn;		/* /proc info callback */
	void* dev;			/* device/context info for callback */
};

struct MCA_info {
	/* one for each of the 8 possible slots, plus one for integrated SCSI
	 * and one for integrated video.
	 */

	struct MCA_adapter slot[MCA_NUMADAPTERS];

	/* two potential addresses for integrated SCSI adapter - this will
	 * track which one we think it is.
	 */

	unsigned char which_scsi;
};

/* The mca_info structure pointer. If MCA bus is present, the function
 * mca_probe() is invoked. The function puts motherboard, then all
 * adapters into setup mode, allocates and fills an MCA_info structure,
 * and points this pointer to the structure. Otherwise the pointer
 * is set to zero.
 */

static struct MCA_info* mca_info = NULL;

/*
 * Motherboard register spinlock. Untested on SMP at the moment, but
 * are there any MCA SMP boxes?
 *
 * Yes - Alan
 */
static spinlock_t mca_lock = SPIN_LOCK_UNLOCKED;

/* MCA registers */

#define MCA_MOTHERBOARD_SETUP_REG	0x94
#define MCA_ADAPTER_SETUP_REG		0x96
#define MCA_POS_REG(n)			(0x100+(n))

#define MCA_ENABLED	0x01	/* POS 2, set if adapter enabled */

/*--------------------------------------------------------------------*/

#ifdef CONFIG_PROC_FS
static void mca_do_proc_init(void);
#endif

/*--------------------------------------------------------------------*/

/* Build the status info for the adapter */

static void mca_configure_adapter_status(int slot) {
	mca_info->slot[slot].status = MCA_ADAPTER_NONE;

	mca_info->slot[slot].id = mca_info->slot[slot].pos[0]
		+ (mca_info->slot[slot].pos[1] << 8);

	if(!mca_info->slot[slot].id && slot < MCA_MAX_SLOT_NR) {

		/* id = 0x0000 usually indicates hardware failure,
		 * however, ZP Gu (zpg@castle.net> reports that his 9556
		 * has 0x0000 as id and everything still works. There
		 * also seem to be an adapter with id = 0x0000; the
		 * NCR Parallel Bus Memory Card. Until this is confirmed,
		 * however, this code will stay.
		 */

		mca_info->slot[slot].status = MCA_ADAPTER_ERROR;

		return;
	} else if(mca_info->slot[slot].id != 0xffff) {

		/* 0xffff usually indicates that there's no adapter,
		 * however, some integrated adapters may have 0xffff as
		 * their id and still be valid. Examples are on-board
		 * VGA of the 55sx, the integrated SCSI of the 56 & 57,
		 * and possibly also the 95 ULTIMEDIA.
		 */

		mca_info->slot[slot].status = MCA_ADAPTER_NORMAL;
	}

	if((mca_info->slot[slot].id == 0xffff ||
	   mca_info->slot[slot].id == 0x0000) && slot >= MCA_MAX_SLOT_NR) {
		int j;

		for(j = 2; j < 8; j++) {
			if(mca_info->slot[slot].pos[j] != 0xff) {
				mca_info->slot[slot].status = MCA_ADAPTER_NORMAL;
				break;
			}
		}
	}

	if(!(mca_info->slot[slot].pos[2] & MCA_ENABLED)) {

		/* enabled bit is in POS 2 */

		mca_info->slot[slot].status = MCA_ADAPTER_DISABLED;
	}
} /* mca_configure_adapter_status */

/*--------------------------------------------------------------------*/

struct resource mca_standard_resources[] = {
	{ "system control port B (MCA)", 0x60, 0x60 },
	{ "arbitration (MCA)", 0x90, 0x90 },
	{ "card Select Feedback (MCA)", 0x91, 0x91 },
	{ "system Control port A (MCA)", 0x92, 0x92 },
	{ "system board setup (MCA)", 0x94, 0x94 },
	{ "POS (MCA)", 0x96, 0x97 },
	{ "POS (MCA)", 0x100, 0x107 }
};

#define MCA_STANDARD_RESOURCES	(sizeof(mca_standard_resources)/sizeof(struct resource))

int __init mca_init(void)
{
	unsigned int i, j;

	/* WARNING: Be careful when making changes here. Putting an adapter
	 * and the motherboard simultaneously into setup mode may result in
	 * damage to chips (according to The Indispensible PC Hardware Book
	 * by Hans-Peter Messmer). Also, we disable system interrupts (so
	 * that we are not disturbed in the middle of this).
	 */

	/* Make sure the MCA bus is present */

	if(!MCA_bus)
		return -ENODEV;
	printk(KERN_INFO "Micro Channel bus detected.\n");

	/* Allocate MCA_info structure (at address divisible by 8) */

	mca_info = (struct MCA_info *)kmalloc(sizeof(struct MCA_info), GFP_KERNEL);

	if(mca_info == NULL) {
		printk(KERN_ERR "Failed to allocate memory for mca_info!");
		return -ENOMEM;
	}
	memset(mca_info, 0, sizeof(struct MCA_info));

	/*
	 * We do not expect many MCA interrupts during initialization,
	 * but let us be safe:
	 */
	spin_lock_irq(&mca_lock);

	/* Make sure adapter setup is off */

	outb_p(0, MCA_ADAPTER_SETUP_REG);

	/* Read motherboard POS registers */

	outb_p(0x7f, MCA_MOTHERBOARD_SETUP_REG);
	mca_info->slot[MCA_MOTHERBOARD].name[0] = 0;
	for(j=0; j<8; j++) {
		mca_info->slot[MCA_MOTHERBOARD].pos[j] = inb_p(MCA_POS_REG(j));
	}
	mca_configure_adapter_status(MCA_MOTHERBOARD);

	/* Put motherboard into video setup mode, read integrated video
	 * POS registers, and turn motherboard setup off.
	 */

	outb_p(0xdf, MCA_MOTHERBOARD_SETUP_REG);
	mca_info->slot[MCA_INTEGVIDEO].name[0] = 0;
	for(j=0; j<8; j++) {
		mca_info->slot[MCA_INTEGVIDEO].pos[j] = inb_p(MCA_POS_REG(j));
	}
	mca_configure_adapter_status(MCA_INTEGVIDEO);

	/* Put motherboard into scsi setup mode, read integrated scsi
	 * POS registers, and turn motherboard setup off.
	 *
	 * It seems there are two possible SCSI registers. Martin says that
	 * for the 56,57, 0xf7 is the one, but fails on the 76.
	 * Alfredo (apena@vnet.ibm.com) says
	 * 0xfd works on his machine. We'll try both of them. I figure it's
	 * a good bet that only one could be valid at a time. This could
	 * screw up though if one is used for something else on the other
	 * machine.
	 */

	outb_p(0xf7, MCA_MOTHERBOARD_SETUP_REG);
	mca_info->slot[MCA_INTEGSCSI].name[0] = 0;
	for(j=0; j<8; j++) {
		if((mca_info->slot[MCA_INTEGSCSI].pos[j] = inb_p(MCA_POS_REG(j))) != 0xff)
		{
			/* 0xff all across means no device. 0x00 means
			 * something's broken, but a device is probably there.
			 * However, if you get 0x00 from a motherboard
			 * register it won't matter what we find.  For the
			 * record, on the 57SLC, the integrated SCSI
			 * adapter has 0xffff for the adapter ID, but
			 * nonzero for other registers.
			 */

			mca_info->which_scsi = 0xf7;
		}
	}
	if(!mca_info->which_scsi) {

		/* Didn't find it at 0xf7, try somewhere else... */
		mca_info->which_scsi = 0xfd;

		outb_p(0xfd, MCA_MOTHERBOARD_SETUP_REG);
		for(j=0; j<8; j++)
			mca_info->slot[MCA_INTEGSCSI].pos[j] = inb_p(MCA_POS_REG(j));
	}
	mca_configure_adapter_status(MCA_INTEGSCSI);

	/* Turn off motherboard setup */

	outb_p(0xff, MCA_MOTHERBOARD_SETUP_REG);

	/* Now loop over MCA slots: put each adapter into setup mode, and
	 * read its POS registers. Then put adapter setup off.
	 */

	for(i=0; i<MCA_MAX_SLOT_NR; i++) {
		outb_p(0x8|(i&0xf), MCA_ADAPTER_SETUP_REG);
		for(j=0; j<8; j++) {
			mca_info->slot[i].pos[j]=inb_p(MCA_POS_REG(j));
		}
		mca_info->slot[i].name[0] = 0;
		mca_info->slot[i].driver_loaded = 0;
		mca_configure_adapter_status(i);
	}
	outb_p(0, MCA_ADAPTER_SETUP_REG);

	/* Enable interrupts and return memory start */
	spin_unlock_irq(&mca_lock);

	for (i = 0; i < MCA_STANDARD_RESOURCES; i++)
		request_resource(&ioport_resource, mca_standard_resources + i);

#ifdef CONFIG_PROC_FS
	mca_do_proc_init();
#endif

	return 0;
}

subsys_initcall(mca_init);

/*--------------------------------------------------------------------*/

static void mca_handle_nmi_slot(int slot, int check_flag)
{
	if(slot < MCA_MAX_SLOT_NR) {
		printk(KERN_CRIT "NMI: caused by MCA adapter in slot %d (%s)\n", slot+1,
			mca_info->slot[slot].name);
	} else if(slot == MCA_INTEGSCSI) {
		printk(KERN_CRIT "NMI: caused by MCA integrated SCSI adapter (%s)\n",
			mca_info->slot[slot].name);
	} else if(slot == MCA_INTEGVIDEO) {
		printk(KERN_CRIT "NMI: caused by MCA integrated video adapter (%s)\n",
			mca_info->slot[slot].name);
	} else if(slot == MCA_MOTHERBOARD) {
		printk(KERN_CRIT "NMI: caused by motherboard (%s)\n",
			mca_info->slot[slot].name);
	}

	/* More info available in POS 6 and 7? */

	if(check_flag) {
		unsigned char pos6, pos7;

		pos6 = mca_read_pos(slot, 6);
		pos7 = mca_read_pos(slot, 7);

		printk(KERN_CRIT "NMI: POS 6 = 0x%x, POS 7 = 0x%x\n", pos6, pos7);
	}

} /* mca_handle_nmi_slot */

/*--------------------------------------------------------------------*/

void mca_handle_nmi(void)
{

	int i;
	unsigned char pos5;

	/* First try - scan the various adapters and see if a specific
	 * adapter was responsible for the error.
	 */

	for(i = 0; i < MCA_NUMADAPTERS; i++) 
	{
		/* Bit 7 of POS 5 is reset when this adapter has a hardware
		 * error. Bit 7 it reset if there's error information
		 * available in POS 6 and 7.
		 */
		pos5 = mca_read_pos(i, 5);

		if(!(pos5 & 0x80)) {
			mca_handle_nmi_slot(i, !(pos5 & 0x40));
			return;
		}
	}
	mca_nmi_hook();
} /* mca_handle_nmi */

/*--------------------------------------------------------------------*/

/**
 *	mca_find_adapter - scan for adapters
 *	@id:	MCA identification to search for
 *	@start:	starting slot
 *
 *	Search the MCA configuration for adapters matching the 16bit
 *	ID given. The first time it should be called with start as zero
 *	and then further calls made passing the return value of the
 *	previous call until %MCA_NOTFOUND is returned.
 *
 *	Disabled adapters are not reported.
 */

int mca_find_adapter(int id, int start)
{
	if(mca_info == NULL || id == 0xffff) {
		return MCA_NOTFOUND;
	}

	for(; start >= 0 && start < MCA_NUMADAPTERS; start++) {

		/* Not sure about this. There's no point in returning
		 * adapters that aren't enabled, since they can't actually
		 * be used. However, they might be needed for statistical
		 * purposes or something... But if that is the case, the
		 * user is free to write a routine that manually iterates
		 * through the adapters.
		 */

		if(mca_info->slot[start].status == MCA_ADAPTER_DISABLED) {
			continue;
		}

		if(id == mca_info->slot[start].id) {
			return start;
		}
	}

	return MCA_NOTFOUND;
} /* mca_find_adapter() */

EXPORT_SYMBOL(mca_find_adapter);

/*--------------------------------------------------------------------*/

/**
 *	mca_find_unused_adapter - scan for unused adapters
 *	@id:	MCA identification to search for
 *	@start:	starting slot
 *
 *	Search the MCA configuration for adapters matching the 16bit
 *	ID given. The first time it should be called with start as zero
 *	and then further calls made passing the return value of the
 *	previous call until %MCA_NOTFOUND is returned.
 *
 *	Adapters that have been claimed by drivers and those that
 *	are disabled are not reported. This function thus allows a driver
 *	to scan for further cards when some may already be driven.
 */

int mca_find_unused_adapter(int id, int start)
{
	if(mca_info == NULL || id == 0xffff) {
		return MCA_NOTFOUND;
	}

	for(; start >= 0 && start < MCA_NUMADAPTERS; start++) {

		/* not sure about this. There's no point in returning
		 * adapters that aren't enabled, since they can't actually
		 * be used. However, they might be needed for statistical
		 * purposes or something... But if that is the case, the
		 * user is free to write a routine that manually iterates
		 * through the adapters.
		 */

		if(mca_info->slot[start].status == MCA_ADAPTER_DISABLED ||
		   mca_info->slot[start].driver_loaded) {
			continue;
		}

		if(id == mca_info->slot[start].id) {
			return start;
		}
	}

	return MCA_NOTFOUND;
} /* mca_find_unused_adapter() */

EXPORT_SYMBOL(mca_find_unused_adapter);

/*--------------------------------------------------------------------*/

/**
 *	mca_read_stored_pos - read POS register from boot data
 *	@slot: slot number to read from
 *	@reg:  register to read from
 *
 *	Fetch a POS value that was stored at boot time by the kernel
 *	when it scanned the MCA space. The register value is returned.
 *	Missing or invalid registers report 0.
 */

unsigned char mca_read_stored_pos(int slot, int reg)
{
	if(slot < 0 || slot >= MCA_NUMADAPTERS || mca_info == NULL) return 0;
	if(reg < 0 || reg >= 8) return 0;
	return mca_info->slot[slot].pos[reg];
} /* mca_read_stored_pos() */

EXPORT_SYMBOL(mca_read_stored_pos);

/*--------------------------------------------------------------------*/

/**
 *	mca_read_pos - read POS register from card
 *	@slot: slot number to read from
 *	@reg:  register to read from
 *
 *	Fetch a POS value directly from the hardware to obtain the
 *	current value. This is much slower than mca_read_stored_pos and
 *	may not be invoked from interrupt context. It handles the
 *	deep magic required for onboard devices transparently.
 */

unsigned char mca_read_pos(int slot, int reg)
{
	unsigned int byte = 0;
	unsigned long flags;

	if(slot < 0 || slot >= MCA_NUMADAPTERS || mca_info == NULL) return 0;
	if(reg < 0 || reg >= 8) return 0;

	spin_lock_irqsave(&mca_lock, flags);

	/* Make sure motherboard setup is off */

	outb_p(0xff, MCA_MOTHERBOARD_SETUP_REG);

	/* Read in the appropriate register */

	if(slot == MCA_INTEGSCSI && mca_info->which_scsi) {

		/* Disable adapter setup, enable motherboard setup */

		outb_p(0, MCA_ADAPTER_SETUP_REG);
		outb_p(mca_info->which_scsi, MCA_MOTHERBOARD_SETUP_REG);

		byte = inb_p(MCA_POS_REG(reg));
		outb_p(0xff, MCA_MOTHERBOARD_SETUP_REG);
	} else if(slot == MCA_INTEGVIDEO) {

		/* Disable adapter setup, enable motherboard setup */

		outb_p(0, MCA_ADAPTER_SETUP_REG);
		outb_p(0xdf, MCA_MOTHERBOARD_SETUP_REG);

		byte = inb_p(MCA_POS_REG(reg));
		outb_p(0xff, MCA_MOTHERBOARD_SETUP_REG);
	} else if(slot == MCA_MOTHERBOARD) {

		/* Disable adapter setup, enable motherboard setup */
		outb_p(0, MCA_ADAPTER_SETUP_REG);
		outb_p(0x7f, MCA_MOTHERBOARD_SETUP_REG);

		byte = inb_p(MCA_POS_REG(reg));
		outb_p(0xff, MCA_MOTHERBOARD_SETUP_REG);
	} else if(slot < MCA_MAX_SLOT_NR) {

		/* Make sure motherboard setup is off */

		outb_p(0xff, MCA_MOTHERBOARD_SETUP_REG);

		/* Read the appropriate register */

		outb_p(0x8|(slot&0xf), MCA_ADAPTER_SETUP_REG);
		byte = inb_p(MCA_POS_REG(reg));
		outb_p(0, MCA_ADAPTER_SETUP_REG);
	}

	/* Make sure the stored values are consistent, while we're here */

	mca_info->slot[slot].pos[reg] = byte;

	spin_unlock_irqrestore(&mca_lock, flags);

	return byte;
} /* mca_read_pos() */

EXPORT_SYMBOL(mca_read_pos);

/*--------------------------------------------------------------------*/

/**
 *	mca_write_pos - read POS register from card
 *	@slot: slot number to read from
 *	@reg:  register to read from
 *	@byte: byte to write to the POS registers
 *
 *	Store a POS value directly from the hardware. You should not
 *	normally need to use this function and should have a very good
 *	knowledge of MCA bus before you do so. Doing this wrongly can
 *	damage the hardware.
 *
 *	This function may not be used from interrupt context.
 *
 *	Note that this a technically a Bad Thing, as IBM tech stuff says
 *	you should only set POS values through their utilities.
 *	However, some devices such as the 3c523 recommend that you write
 *	back some data to make sure the configuration is consistent.
 *	I'd say that IBM is right, but I like my drivers to work.
 *
 *	This function can't do checks to see if multiple devices end up
 *	with the same resources, so you might see magic smoke if someone
 *	screws up.
 */

void mca_write_pos(int slot, int reg, unsigned char byte)
{
	unsigned long flags;

	if(slot < 0 || slot >= MCA_MAX_SLOT_NR)
		return;
	if(reg < 0 || reg >= 8)
		return;
	if(mca_info == NULL)
		return;

	spin_lock_irqsave(&mca_lock, flags);

	/* Make sure motherboard setup is off */

	outb_p(0xff, MCA_MOTHERBOARD_SETUP_REG);

	/* Read in the appropriate register */

	outb_p(0x8|(slot&0xf), MCA_ADAPTER_SETUP_REG);
	outb_p(byte, MCA_POS_REG(reg));
	outb_p(0, MCA_ADAPTER_SETUP_REG);

	spin_unlock_irqrestore(&mca_lock, flags);

	/* Update the global register list, while we have the byte */

	mca_info->slot[slot].pos[reg] = byte;
} /* mca_write_pos() */

EXPORT_SYMBOL(mca_write_pos);

/*--------------------------------------------------------------------*/

/**
 *	mca_set_adapter_name - Set the description of the card
 *	@slot: slot to name
 *	@name: text string for the namen
 *
 *	This function sets the name reported via /proc for this
 *	adapter slot. This is for user information only. Setting a
 *	name deletes any previous name.
 */

void mca_set_adapter_name(int slot, char* name)
{
	if(mca_info == NULL) return;

	if(slot >= 0 && slot < MCA_NUMADAPTERS) {
		if(name != NULL) {
			strncpy(mca_info->slot[slot].name, name,
				sizeof(mca_info->slot[slot].name)-1);
			mca_info->slot[slot].name[
				sizeof(mca_info->slot[slot].name)-1] = 0;
		} else {
			mca_info->slot[slot].name[0] = 0;
		}
	}
}

EXPORT_SYMBOL(mca_set_adapter_name);

/**
 *	mca_set_adapter_procfn - Set the /proc callback
 *	@slot: slot to configure
 *	@procfn: callback function to call for /proc
 *	@dev: device information passed to the callback
 *
 *	This sets up an information callback for /proc/mca/slot?.  The
 *	function is called with the buffer, slot, and device pointer (or
 *	some equally informative context information, or nothing, if you
 *	prefer), and is expected to put useful information into the
 *	buffer.  The adapter name, ID, and POS registers get printed
 *	before this is called though, so don't do it again.
 *
 *	This should be called with a %NULL @procfn when a module
 *	unregisters, thus preventing kernel crashes and other such
 *	nastiness.
 */

void mca_set_adapter_procfn(int slot, MCA_ProcFn procfn, void* dev)
{
	if(mca_info == NULL) return;

	if(slot >= 0 && slot < MCA_NUMADAPTERS) {
		mca_info->slot[slot].procfn = procfn;
		mca_info->slot[slot].dev = dev;
	}
}

EXPORT_SYMBOL(mca_set_adapter_procfn);

/**
 *	mca_is_adapter_used - check if claimed by driver
 *	@slot:	slot to check
 *
 *	Returns 1 if the slot has been claimed by a driver
 */

int mca_is_adapter_used(int slot)
{
	return mca_info->slot[slot].driver_loaded;
}

EXPORT_SYMBOL(mca_is_adapter_used);

/**
 *	mca_mark_as_used - claim an MCA device
 *	@slot:	slot to claim
 *	FIXME:  should we make this threadsafe
 *
 *	Claim an MCA slot for a device driver. If the
 *	slot is already taken the function returns 1,
 *	if it is not taken it is claimed and 0 is
 *	returned.
 */

int mca_mark_as_used(int slot)
{
	if(mca_info->slot[slot].driver_loaded) return 1;
	mca_info->slot[slot].driver_loaded = 1;
	return 0;
}

EXPORT_SYMBOL(mca_mark_as_used);

/**
 *	mca_mark_as_unused - release an MCA device
 *	@slot:	slot to claim
 *
 *	Release the slot for other drives to use.
 */

void mca_mark_as_unused(int slot)
{
	mca_info->slot[slot].driver_loaded = 0;
}

EXPORT_SYMBOL(mca_mark_as_unused);

/**
 *	mca_get_adapter_name - get the adapter description
 *	@slot:	slot to query
 *
 *	Return the adapter description if set. If it has not been
 *	set or the slot is out range then return NULL.
 */

char *mca_get_adapter_name(int slot)
{
	if(mca_info == NULL) return 0;

	if(slot >= 0 && slot < MCA_NUMADAPTERS) {
		return mca_info->slot[slot].name;
	}

	return 0;
}

EXPORT_SYMBOL(mca_get_adapter_name);

/**
 *	mca_isadapter - check if the slot holds an adapter
 *	@slot:	slot to query
 *
 *	Returns zero if the slot does not hold an adapter, non zero if
 *	it does.
 */

int mca_isadapter(int slot)
{
	if(mca_info == NULL) return 0;

	if(slot >= 0 && slot < MCA_NUMADAPTERS) {
		return ((mca_info->slot[slot].status == MCA_ADAPTER_NORMAL)
			|| (mca_info->slot[slot].status == MCA_ADAPTER_DISABLED));
	}

	return 0;
}

EXPORT_SYMBOL(mca_isadapter);


/**
 *	mca_isadapter - check if the slot holds an adapter
 *	@slot:	slot to query
 *
 *	Returns a non zero value if the slot holds an enabled adapter
 *	and zero for any other case.
 */

int mca_isenabled(int slot)
{
	if(mca_info == NULL) return 0;

	if(slot >= 0 && slot < MCA_NUMADAPTERS) {
		return (mca_info->slot[slot].status == MCA_ADAPTER_NORMAL);
	}

	return 0;
}

EXPORT_SYMBOL(mca_isenabled);

/*--------------------------------------------------------------------*/

#ifdef CONFIG_PROC_FS

int get_mca_info(char *page, char **start, off_t off,
				 int count, int *eof, void *data)
{
	int i, j, len = 0;

	if(MCA_bus && mca_info != NULL) {
		/* Format POS registers of eight MCA slots */

		for(i=0; i<MCA_MAX_SLOT_NR; i++) {
			len += sprintf(page+len, "Slot %d: ", i+1);
			for(j=0; j<8; j++)
				len += sprintf(page+len, "%02x ", mca_info->slot[i].pos[j]);
			len += sprintf(page+len, " %s\n", mca_info->slot[i].name);
		}

		/* Format POS registers of integrated video subsystem */

		len += sprintf(page+len, "Video : ");
		for(j=0; j<8; j++)
			len += sprintf(page+len, "%02x ", mca_info->slot[MCA_INTEGVIDEO].pos[j]);
		len += sprintf(page+len, " %s\n", mca_info->slot[MCA_INTEGVIDEO].name);

		/* Format POS registers of integrated SCSI subsystem */

		len += sprintf(page+len, "SCSI  : ");
		for(j=0; j<8; j++)
			len += sprintf(page+len, "%02x ", mca_info->slot[MCA_INTEGSCSI].pos[j]);
		len += sprintf(page+len, " %s\n", mca_info->slot[MCA_INTEGSCSI].name);

		/* Format POS registers of motherboard */

		len += sprintf(page+len, "Planar: ");
		for(j=0; j<8; j++)
			len += sprintf(page+len, "%02x ", mca_info->slot[MCA_MOTHERBOARD].pos[j]);
		len += sprintf(page+len, " %s\n", mca_info->slot[MCA_MOTHERBOARD].name);
	} else {
		/* Leave it empty if MCA not detected - this should *never*
		 * happen!
		 */
	}

	if (len <= off+count) *eof = 1;
	*start = page + off;
	len -= off;
	if (len>count) len = count;
	if (len<0) len = 0;
	return len;
}

/*--------------------------------------------------------------------*/

static int mca_default_procfn(char* buf, struct MCA_adapter *p)
{
	int len = 0, i;
	int slot = p - mca_info->slot;

	/* Print out the basic information */

	if(slot < MCA_MAX_SLOT_NR) {
		len += sprintf(buf+len, "Slot: %d\n", slot+1);
	} else if(slot == MCA_INTEGSCSI) {
		len += sprintf(buf+len, "Integrated SCSI Adapter\n");
	} else if(slot == MCA_INTEGVIDEO) {
		len += sprintf(buf+len, "Integrated Video Adapter\n");
	} else if(slot == MCA_MOTHERBOARD) {
		len += sprintf(buf+len, "Motherboard\n");
	}
	if(p->name[0]) {

		/* Drivers might register a name without /proc handler... */

		len += sprintf(buf+len, "Adapter Name: %s\n",
			p->name);
	} else {
		len += sprintf(buf+len, "Adapter Name: Unknown\n");
	}
	len += sprintf(buf+len, "Id: %02x%02x\n",
		p->pos[1], p->pos[0]);
	len += sprintf(buf+len, "Enabled: %s\nPOS: ",
		mca_isenabled(slot) ? "Yes" : "No");
	for(i=0; i<8; i++) {
		len += sprintf(buf+len, "%02x ", p->pos[i]);
	}
	len += sprintf(buf+len, "\nDriver Installed: %s",
		mca_is_adapter_used(slot) ? "Yes" : "No");
	buf[len++] = '\n';
	buf[len] = 0;

	return len;
} /* mca_default_procfn() */

static int get_mca_machine_info(char* page, char **start, off_t off,
				 int count, int *eof, void *data)
{
	int len = 0;

	len += sprintf(page+len, "Model Id: 0x%x\n", machine_id);
	len += sprintf(page+len, "Submodel Id: 0x%x\n", machine_submodel_id);
	len += sprintf(page+len, "BIOS Revision: 0x%x\n", BIOS_revision);

	if (len <= off+count) *eof = 1;
	*start = page + off;
	len -= off;
	if (len>count) len = count;
	if (len<0) len = 0;
	return len;
}

static int mca_read_proc(char *page, char **start, off_t off,
				 int count, int *eof, void *data)
{
	struct MCA_adapter *p = (struct MCA_adapter *)data;
	int len = 0;

	/* Get the standard info */

	len = mca_default_procfn(page, p);

	/* Do any device-specific processing, if there is any */

	if(p->procfn) {
		len += p->procfn(page+len, p-mca_info->slot, p->dev);
	}
	if (len <= off+count) *eof = 1;
	*start = page + off;
	len -= off;
	if (len>count) len = count;
	if (len<0) len = 0;
	return len;
} /* mca_read_proc() */

/*--------------------------------------------------------------------*/

void __init mca_do_proc_init(void)
{
	int i;
	struct proc_dir_entry *proc_mca;
	struct proc_dir_entry* node = NULL;
	struct MCA_adapter *p;

	if(mca_info == NULL) return;	/* Should never happen */

	proc_mca = proc_mkdir("mca", &proc_root);
	create_proc_read_entry("pos",0,proc_mca,get_mca_info,NULL);
	create_proc_read_entry("machine",0,proc_mca,get_mca_machine_info,NULL);

	/* Initialize /proc/mca entries for existing adapters */

	for(i = 0; i < MCA_NUMADAPTERS; i++) {
		p = &mca_info->slot[i];
		p->procfn = 0;

		if(i < MCA_MAX_SLOT_NR) sprintf(p->procname,"slot%d", i+1);
		else if(i == MCA_INTEGVIDEO) sprintf(p->procname,"video");
		else if(i == MCA_INTEGSCSI) sprintf(p->procname,"scsi");
		else if(i == MCA_MOTHERBOARD) sprintf(p->procname,"planar");

		if(!mca_isadapter(i)) continue;

		node = create_proc_read_entry(p->procname, 0, proc_mca,
						mca_read_proc, (void *)p);

		if(node == NULL) {
			printk("Failed to allocate memory for MCA proc-entries!");
			return;
		}
	}

} /* mca_do_proc_init() */

#endif
