/*
 * linux/arch/arm/kernel/ecard.c
 *
 *  Find all installed expansion cards, and handle interrupts from them.
 *
 * Copyright 1995,1996,1997 Russell King
 *
 * Created from information from Acorns RiscOS3 PRMs
 *
 * 08-Dec-1996	RMK	Added code for the 9'th expansion card - the ether podule slot.
 * 06-May-1997  RMK	Added blacklist for cards whose loader doesn't work.
 * 12-Sep-1997	RMK	Created new handling of interrupt enables/disables - cards can
 *			now register their own routine to control interrupts (recommended).
 * 29-Sep-1997	RMK	Expansion card interrupt hardware not being re-enabled on reset from
 *			Linux. (Caused cards not to respond under RiscOS without hard reset).
 */

#define ECARD_C

#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/mm.h>
#include <linux/malloc.h>

#include <asm/irq-no.h>
#include <asm/ecard.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/hardware.h>
#include <asm/arch/irq.h>

#ifdef CONFIG_ARCH_ARC
#include <asm/arch/oldlatches.h>
#else
#define oldlatch_init()
#endif

#define BLACKLIST_NAME(m,p,s)	{ m, p, NULL, s }
#define BLACKLIST_LOADER(m,p,l)	{ m, p, l, NULL }
#define BLACKLIST_NOLOADER(m,p)	{ m, p, noloader, blacklisted_str }
#define BUS_ADDR(x) ((((unsigned long)(x)) << 2) + IO_BASE)

extern unsigned long atomwide_serial_loader[], oak_scsi_loader[], noloader[];
static const char blacklisted_str[] = "*loader blacklisted - not 32-bit compliant*";

static const struct expcard_blacklist {
	unsigned short	 manufacturer;
	unsigned short	 product;
	const loader_t 	 loader;
	const char	*type;
} blacklist[] = {
/* Cards without names */
    BLACKLIST_NAME(MANU_ACORN,		PROD_ACORN_ETHER1,	"Acorn Ether1"),

/* Cards with corrected loader */
  BLACKLIST_LOADER(MANU_ATOMWIDE,	PROD_ATOMWIDE_3PSERIAL,	atomwide_serial_loader),
  BLACKLIST_LOADER(MANU_OAK,		PROD_OAK_SCSI,		oak_scsi_loader),

/* Unsupported cards with no loader */
BLACKLIST_NOLOADER(MANU_ALSYSTEMS,	PROD_ALSYS_SCSIATAPI),
BLACKLIST_NOLOADER(MANU_MCS,		PROD_MCS_CONNECT32)
};

extern int setup_arm_irq(int, struct irqaction *);

/*
 * from linux/arch/arm/kernel/irq.c
 */
extern void do_ecard_IRQ(int irq, struct pt_regs *);

static ecard_t expcard[MAX_ECARDS];
static signed char irqno_to_expcard[16];
static unsigned int ecard_numcards, ecard_numirqcards;
static unsigned int have_expmask;
static unsigned long kmem;

static void ecard_def_irq_enable (ecard_t *ec, int irqnr)
{
#ifdef HAS_EXPMASK
	if (irqnr < 4 && have_expmask) {
		have_expmask |= 1 << irqnr;
		EXPMASK_ENABLE = have_expmask;
	}
#endif
}

static void ecard_def_irq_disable (ecard_t *ec, int irqnr)
{
#ifdef HAS_EXPMASK
	if (irqnr < 4 && have_expmask) {
		have_expmask &= ~(1 << irqnr);
		EXPMASK_ENABLE = have_expmask;
	}
#endif
}

static void ecard_def_fiq_enable (ecard_t *ec, int fiqnr)
{
	panic ("ecard_def_fiq_enable called - impossible");
}

static void ecard_def_fiq_disable (ecard_t *ec, int fiqnr)
{
	panic ("ecard_def_fiq_disable called - impossible");
}

static expansioncard_ops_t ecard_default_ops = {
	ecard_def_irq_enable,
	ecard_def_irq_disable,
	ecard_def_fiq_enable,
	ecard_def_fiq_disable
};

/*
 * Enable and disable interrupts from expansion cards.
 * (interrupts are disabled for these functions).
 *
 * They are not meant to be called directly, but via enable/disable_irq.
 */
void ecard_enableirq (unsigned int irqnr)
{
	if (irqnr < MAX_ECARDS && irqno_to_expcard[irqnr] != -1) {
		ecard_t *ec = expcard + irqno_to_expcard[irqnr];

		if (!ec->ops)
			ec->ops = &ecard_default_ops;

		if (ec->claimed && ec->ops->irqenable)
			ec->ops->irqenable (ec, irqnr);
		else
			printk (KERN_ERR "ecard: rejecting request to "
				"enable IRQs for %d\n", irqnr);
	}
}

void ecard_disableirq (unsigned int irqnr)
{
	if (irqnr < MAX_ECARDS && irqno_to_expcard[irqnr] != -1) {
		ecard_t *ec = expcard + irqno_to_expcard[irqnr];

		if (!ec->ops)
			ec->ops = &ecard_default_ops;

		if (ec->ops && ec->ops->irqdisable)
			ec->ops->irqdisable (ec, irqnr);
	}
}

void ecard_enablefiq (unsigned int fiqnr)
{
	if (fiqnr < MAX_ECARDS && irqno_to_expcard[fiqnr] != -1) {
		ecard_t *ec = expcard + irqno_to_expcard[fiqnr];

		if (!ec->ops)
			ec->ops = &ecard_default_ops;

		if (ec->claimed && ec->ops->fiqenable)
			ec->ops->fiqenable (ec, fiqnr);
		else
			printk (KERN_ERR "ecard: rejecting request to "
				"enable FIQs for %d\n", fiqnr);
	}
}

void ecard_disablefiq (unsigned int fiqnr)
{
	if (fiqnr < MAX_ECARDS && irqno_to_expcard[fiqnr] != -1) {
		ecard_t *ec = expcard + irqno_to_expcard[fiqnr];

		if (!ec->ops)
			ec->ops = &ecard_default_ops;

		if (ec->ops->fiqdisable)
			ec->ops->fiqdisable (ec, fiqnr);
	}
}

static void *ecard_malloc(int len)
{
	int r;

	len = (len + 3) & ~3;

	if (kmem) {
		r = kmem;
		kmem += len;
		return (void *)r;
	} else
		return kmalloc(len, GFP_KERNEL);
}

static void ecard_irq_noexpmask(int intr_no, void *dev_id, struct pt_regs *regs)
{
	const int num_cards = ecard_numirqcards;
	int i, called = 0;

	mask_irq (IRQ_EXPANSIONCARD);
	for (i = 0; i < num_cards; i++) {
		if (expcard[i].claimed && expcard[i].irq &&
		    (!expcard[i].irqmask ||
		     expcard[i].irqaddr[0] & expcard[i].irqmask)) {
			do_ecard_IRQ(expcard[i].irq, regs);
			called ++;
		}
	}
	cli ();
	unmask_irq (IRQ_EXPANSIONCARD);
	if (called == 0)
		printk (KERN_WARNING "Wild interrupt from backplane?\n");
}

#ifdef HAS_EXPMASK
static unsigned char priority_masks[] =
{
	0xf0, 0xf1, 0xf3, 0xf7, 0xff, 0xff, 0xff, 0xff
};

static unsigned char first_set[] =
{
	0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00,
	0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00
};

static void ecard_irq_expmask (int intr_no, void *dev_id, struct pt_regs *regs)
{
	const unsigned int statusmask = 15;
	unsigned int status;

	status = EXPMASK_STATUS & statusmask;
	if (status) {
		unsigned int irqno;
		ecard_t *ec;
again:
		irqno = first_set[status];
		ec = expcard + irqno_to_expcard[irqno];
		if (ec->claimed) {
			unsigned int oldexpmask;
			/*
			 * this ugly code is so that we can operate a prioritorising system.
			 * Card 0 	highest priority
			 * Card 1
			 * Card 2
			 * Card 3	lowest priority
			 * Serial cards should go in 0/1, ethernet/scsi in 2/3
			 * otherwise you will lose serial data at high speeds!
			 */
			oldexpmask = have_expmask;
			EXPMASK_ENABLE = (have_expmask &= priority_masks[irqno]);
			sti ();
			do_ecard_IRQ (ec->irq, regs);
			cli ();
			EXPMASK_ENABLE = have_expmask = oldexpmask;
			status = EXPMASK_STATUS & statusmask;
			if (status)
				goto again;
		} else {
			printk (KERN_WARNING "card%d: interrupt from unclaimed card???\n", irqno);
			EXPMASK_ENABLE = (have_expmask &= ~(1 << irqno));
		}
	} else
		printk (KERN_WARNING "Wild interrupt from backplane (masks)\n");
}

static int ecard_checkirqhw (void)
{
	int found;

	EXPMASK_ENABLE = 0x00;
	EXPMASK_STATUS = 0xff;
	found = ((EXPMASK_STATUS & 15) == 0);
	EXPMASK_ENABLE = 0xff;

	return found;
}
#endif

static void ecard_readbytes (void *addr, ecard_t *ec, int off, int len, int useld)
{
	extern int ecard_loader_read(int off, volatile unsigned int pa, loader_t loader);
	unsigned char *a = (unsigned char *)addr;

	if (ec->slot_no == 8) {
		static unsigned int lowaddress;
		unsigned int laddr, haddr;
		unsigned char byte = 0; /* keep gcc quiet */

		laddr = off & 4095;	/* number of bytes to read from offset + base addr */
		haddr = off >> 12;	/* offset into card from base addr */

		if (haddr > 256)
			return;

		/*
		 * If we require a low address or address 0, then reset, and start again...
		 */
		if (!off || lowaddress > laddr) {
			outb (0, ec->podaddr);
			lowaddress = 0;
		}
		while (lowaddress <= laddr) {
			byte = inb (ec->podaddr + haddr);
			lowaddress += 1;
		}
		while (len--) {
			*a++ = byte;
			if (len) {
				byte = inb (ec->podaddr + haddr);
				lowaddress += 1;
			}
		}
	} else {
		if (!useld || !ec->loader) {
			while(len--)
				*a++ = inb(ec->podaddr + (off++));
		} else {
			while(len--) {
				*(unsigned long *)0x108 = 0; /* hack for some loaders!!! */
				*a++ = ecard_loader_read(off++, BUS_ADDR(ec->podaddr), ec->loader);
			}
		}
	}
}

/*
 * This is called to reset the loaders for each expansion card on reboot.
 *
 * This is required to make sure that the card is in the correct state
 * that RiscOS expects it to be.
 */
void ecard_reset (int card)
{
	extern int ecard_loader_reset (volatile unsigned int pa, loader_t loader);

	if (card >= ecard_numcards)
		return;
    
	if (card < 0) {
		for (card = 0; card < ecard_numcards; card++)
			if (expcard[card].loader)
				ecard_loader_reset (BUS_ADDR(expcard[card].podaddr),
							expcard[card].loader);
	} else
		if (expcard[card].loader)
			ecard_loader_reset (BUS_ADDR(expcard[card].podaddr),
						expcard[card].loader);

#ifdef HAS_EXPMASK
	if (have_expmask) {
		have_expmask |= ~0;
		EXPMASK_ENABLE = have_expmask;
	}
#endif
}

static unsigned int ecard_startcard;

void ecard_startfind (void)
{
	ecard_startcard = 0;
}

ecard_t *ecard_find (int cld, const card_ids *cids)
{
	int card;
	if (!cids) {
		for (card = ecard_startcard; card < ecard_numcards; card++)
			if (!expcard[card].claimed &&
			    ((expcard[card].cld.ecld ^ cld) & 0x78) == 0)
				break;
	} else {
		for (card = ecard_startcard; card < ecard_numcards; card++) {
			unsigned int manufacturer, product;
			int i;

			if (expcard[card].claimed)
				continue;

			manufacturer = expcard[card].cld.manufacturer;
			product = expcard[card].cld.product;

			for (i = 0; cids[i].manufacturer != 65535; i++)
				if (manufacturer == cids[i].manufacturer &&
				    product == cids[i].product)
					break;

			if (cids[i].manufacturer != 65535)
				break;
		}
	}
	ecard_startcard = card + 1;
	return card < ecard_numcards ? &expcard[card] : NULL;
}

int ecard_readchunk (struct in_chunk_dir *cd, ecard_t *ec, int id, int num)
{
	struct ex_chunk_dir excd;
	int index = 16;
	int useld = 0;

	while(1) {
		ecard_readbytes(&excd, ec, index, 8, useld);
		index += 8;
		if (c_id(&excd) == 0) {
			if (!useld && ec->loader) {
				useld = 1;
				index = 0;
				continue;
			}
			return 0;
		}
		if (c_id(&excd) == 0xf0) { /* link */
			index = c_start(&excd);
			continue;
		}
		if (c_id(&excd) == 0x80) { /* loader */
			if (!ec->loader) {
				ec->loader = (loader_t)ecard_malloc(c_len(&excd));
				ecard_readbytes(ec->loader, ec, (int)c_start(&excd), c_len(&excd), useld);
			}
			continue;
		}
		if (c_id(&excd) == id && num-- == 0)
			break;
	}

	if (c_id(&excd) & 0x80) {
		switch (c_id(&excd) & 0x70) {
		case 0x70:
			ecard_readbytes((unsigned char *)excd.d.string, ec,
					(int)c_start(&excd), c_len(&excd), useld);
			break;
		case 0x00:
			break;
		}
	}
	cd->start_offset = c_start(&excd);
	memcpy (cd->d.string, excd.d.string, 256);
	return 1;
}

unsigned int ecard_address (ecard_t *ec, card_type_t memc, card_speed_t speed)
{
	switch (ec->slot_no) {
	case 0:
	case 1:
	case 2:
	case 3:
		return (memc ? MEMCECIO_BASE : IOCECIO_BASE + (speed << 17)) + (ec->slot_no << 12);
#ifdef IOCEC4IO_BASE
	case 4:
	case 5:
	case 6:
	case 7:
		return (memc ? 0 : IOCEC4IO_BASE + (speed << 17)) + ((ec->slot_no - 4) << 12);
#endif
#ifdef MEMCEC8IO_BASE
	case 8:
		return MEMCEC8IO_BASE;
#endif
	}
	return 0;
}

/*
 * Probe for an expansion card.
 *
 * If bit 1 of the first byte of the card is set,
 * then the card does not exist.
 */
static int ecard_probe (int card, int freeslot)
{
	ecard_t *ec = expcard + freeslot;
	struct ex_ecld excld;
	const char *card_desc = NULL;
	int i;

	irqno_to_expcard[card] = -1;

	ec->slot_no = card;
	if ((ec->podaddr = ecard_address (ec, 0, ECARD_SYNC)) == 0)
		return 0;

	excld.r_ecld = 2;
	ecard_readbytes (&excld, ec, 0, 16, 0);
	if (excld.r_ecld & 2)
		return 0;

	irqno_to_expcard[card] = freeslot;

	ec->irq = -1;
	ec->fiq = -1;
	ec->cld.ecld = e_ecld(&excld);
	ec->cld.manufacturer = e_manu(&excld);
	ec->cld.product = e_prod(&excld);
	ec->cld.country = e_country(&excld);
	ec->cld.fiqmask = e_fiqmask(&excld);
	ec->cld.irqmask = e_irqmask(&excld);
	ec->cld.fiqaddr = e_fiqaddr(&excld);
	ec->cld.irqaddr = e_irqaddr(&excld);
	ec->fiqaddr =
	ec->irqaddr = (unsigned char *)BUS_ADDR(ec->podaddr);
	ec->fiqmask = 4;
	ec->irqmask = 1;
	ec->ops = &ecard_default_ops;

	for (i = 0; i < sizeof (blacklist) / sizeof (*blacklist); i++)
		if (blacklist[i].manufacturer == ec->cld.manufacturer &&
		    blacklist[i].product == ec->cld.product) {
			ec->loader = blacklist[i].loader;
			card_desc = blacklist[i].type;
			break;
		}

	if (card != 8) {
		ec->irq = 32 + card;
#if 0
		ec->fiq = 96 + card;
#endif
	} else {
		ec->irq = 11;
		ec->fiq = -1;
	}

	if ((ec->cld.ecld & 0x78) == 0) {
		struct in_chunk_dir incd;
		printk ("\n  %d: [%04X:%04X] ", card, ec->cld.manufacturer, ec->cld.product);
		if (e_is (&excld)) {
			ec->fiqmask = e_fiqmask (&excld);
			ec->irqmask = e_irqmask (&excld);
			ec->fiqaddr += e_fiqaddr (&excld);
			ec->irqaddr += e_irqaddr (&excld);
		}
		if (!card_desc && e_cd (&excld) && ecard_readchunk (&incd, ec, 0xf5, 0))
			card_desc = incd.d.string;
		if (card_desc)
			printk ("%s", card_desc);
		else
			printk ("*Unknown*");
	} else
		printk("\n  %d: Simple card %d\n", card, (ec->cld.ecld >> 3) & 15);
	return 1;
}

static struct irqaction irqexpansioncard = { ecard_irq_noexpmask, SA_INTERRUPT, 0, "expansion cards", NULL, NULL };

/*
 * Initialise the expansion card system.
 * Locate all hardware - interrupt management and
 * actual cards.
 */
unsigned long ecard_init(unsigned long start_mem)
{
	int i, nc = 0;

	kmem = (start_mem | 3) & ~3;
	memset (expcard, 0, sizeof (expcard));

#ifdef HAS_EXPMASK
	if (ecard_checkirqhw()) {
		printk (KERN_DEBUG "Expansion card interrupt management hardware found\n");
		irqexpansioncard.handler = ecard_irq_expmask;
		have_expmask = -1;
	}
#endif
	printk("Installed expansion cards:");

	/*
	 * First of all, probe all cards on the expansion card interrupt line
	 */
	for (i = 0; i < 4; i++)
		if (ecard_probe (i, nc))
			nc += 1;
		else
			have_expmask &= ~(1<<i);

	ecard_numirqcards = nc;

	/*
	 * Now probe other cards with different interrupt lines
	 */
#ifdef MEMCEC8IO_BASE
	if (ecard_probe (8, nc))
		nc += 1;
#endif
	printk("\n");
	ecard_numcards = nc;

	if (nc && setup_arm_irq(IRQ_EXPANSIONCARD, &irqexpansioncard)) {
		printk ("Could not allocate interrupt for expansion cards\n");
		return kmem;
	}
	
#ifdef HAS_EXPMASK
	if (nc && have_expmask)
		EXPMASK_ENABLE = have_expmask;
#endif
	oldlatch_init ();
	start_mem = kmem;
	kmem = 0;
	return start_mem;	
}
