
/*
 * Copyright 1991-1998 by Open Software Foundation, Inc. 
 *              All Rights Reserved 
 *  
 * Permission to use, copy, modify, and distribute this software and 
 * its documentation for any purpose and without fee is hereby granted, 
 * provided that the above copyright notice appears in all copies and 
 * that both the copyright notice and this permission notice appear in 
 * supporting documentation. 
 *  
 * OSF DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE 
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
 * FOR A PARTICULAR PURPOSE. 
 *  
 * IN NO EVENT SHALL OSF BE LIABLE FOR ANY SPECIAL, INDIRECT, OR 
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 
 * LOSS OF USE, DATA OR PROFITS, WHETHER IN ACTION OF CONTRACT, 
 * NEGLIGENCE, OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION 
 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 
 * 
 */
/*
 * MkLinux
 */

/*
 * Interrupt Handler for Performa (NuBus)
 *
 * -- David Gatwood, MkLinux Team (C) 1998-99
 *
 *
 * Adapted from M2 handler (PowerBook 5300)
 * 
 * -- Michael Burg, Apple Computer Inc. (C) 1998
 */


#include <mach_kgdb.h>
#include <debug.h>
#include <mach_kdb.h>
#include <mach_debug.h>
#include <linux_dev.h>
#include <kern/misc_protos.h>
#include <kern/assert.h>

#include <kgdb/gdb_defs.h>
#include <kgdb/kgdb_defs.h>	/* For kgdb_printf */

#include <ppc/spl.h>
#include <ppc/proc_reg.h>
#include <ppc/misc_protos.h>
#include <ppc/trap.h>
#include <ppc/exception.h>
#include <ppc/POWERMAC/powermac.h>
#include <ppc/POWERMAC/interrupts.h>
#include <ppc/POWERMAC/powermac_performa.h>


static void	performa_via1_interrupt (int device, void *ssp);
static void	performa_via2_interrupt (int device, void *ssp);
static void	performa_via1_portb_interrupt (int device, void *ssp);
// static void	performa_dma_interrupt (int device, void *ssp);
static void	performa_via2_slot_interrupt(int device, void *ssp);
static void	performa_f108_interrupt (int device, void *ssp);

extern int	pb1400_expansion_bay;

vm_offset_t	capella_base_virt = 0;


/* PDM interrupt support is very limited, a certain amount of
 * masking can be done using the ICR but we don't do this yet.
 * SPLs are currently all or nothing.
 */

/* Two identical functions with different names to allow single stepping
 * of performa_spl whilst in kdb. Define contents in a macro so that it stays
 * identical!
 */

#if	LINUX_DEV
extern unsigned long bh_active;
extern unsigned long bh_mask;

#define SPL_FUNCTION(NAME)						\
static spl_t NAME(spl_t lvl);						\
spl_t NAME(spl_t lvl)							\
{									\
	spl_t old_level;						\
									\
	old_level = get_interrupt_level();				\
									\
	lvl = lvl & ~SPL_LOWER_MASK;					\
	if (lvl == SPLLO) {						\
		if (bh_active & bh_mask) {                      	\
			/* disable interrupts here a la gnumach? */    	\
                        linux_soft_intr();                      	\
		}							\
		cpu_data[cpu_number()].interrupt_level = lvl;		\
		interrupt_enable();					\
	} else {							\
		interrupt_disable();					\
		cpu_data[cpu_number()].interrupt_level = lvl;		\
	}								\
									\
	/* Make sure that the write completes before we continue */	\
									\
	sync();								\
									\
	return old_level;						\
}

#else	/* !LINUX_DEV */

#define SPL_FUNCTION(NAME)						\
static spl_t NAME(spl_t lvl);						\
spl_t NAME(spl_t lvl)							\
{									\
	spl_t old_level;						\
									\
	old_level = get_interrupt_level();				\
									\
	lvl = lvl & ~SPL_LOWER_MASK;					\
	if (lvl == SPLLO) {						\
		cpu_data[cpu_number()].interrupt_level = lvl;		\
		interrupt_enable();					\
	} else {							\
		interrupt_disable();					\
		cpu_data[cpu_number()].interrupt_level = lvl;		\
	}								\
									\
	/* Make sure that the write completes before we continue */	\
									\
	sync();								\
									\
	return old_level;						\
}

#endif	/* !LINUX_DEV */

SPL_FUNCTION(performa_spl)

#if	MACH_KDB
SPL_FUNCTION(performa_db_spl)
#endif	/* MACH_KDB */


struct powermac_interrupt performa_via1_interrupts[7] = {
		{ 0,		0,	-1},			/* Cascade */
		{ 0,		0,	PMAC_DEV_HZTICK},
		{ 0,		0,	-1}, // PMAC_DEV_CUDA},		/* THIS IS WRONG!!! */
		{ 0,		0, 	-1},			/* VIA Data */
		{ 0,		0, 	-1},
		{ 0,		0,	PMAC_DEV_TIMER2},
		{ 0,		0,	PMAC_DEV_TIMER1}
};

struct powermac_interrupt performa_via1_portb_interrupts[8] = {
		{ 0,		0,	-1},			/* reserved */
		{ 0,		0,	-1},			/* reserved */
		{ 0,		0,	-1},			/* reserved */
		{ 0,		0, 	PMAC_DEV_CUDA},		/* CUDA ADB */
		{ 0,		0, 	-1},			/* reserved */
		{ 0,		0,	-1},			/* reserved */
		{ 0,		0,	-1},			/* reserved */
		{ 0,		0,	-1}			/* reserved */
};


struct powermac_interrupt performa_via2_interrupts[8] = {
		{ 0,		0,	-1 },			/* SCSI DMA IRQ (not used) */
		{ 0, 		0,	-1 },			/* any slot */
 {performa_via2_slot_interrupt,	0,	-1},			/* reserved (0) */
		{ 0,		0,	PMAC_DEV_SCSI0 },	/* SCSI IRQ */
		{ 0,		0,	PMAC_DMA_AUDIO_OUT },	/* Really Sound IRQ!!! */
		{ 0, 		0,	-1 },			/* any button */
		{ 0,		0,	-1 },			/* reserved (0) */
		{ 0,		0,	-1 }			/* IFR IRQ */
};

struct powermac_interrupt performa_via2_slot_interrupts[8] = {
 {performa_f108_interrupt, 	0,	-1 },			/* Ethernet IRQ */
		{ 0,		0,	-1 },			/* reserved (1) */
		{ 0,		0,	-1 },			/* reserved (1) */
		{ 0,		0,	-1 },			/* reserved (1) */
		{ 0,		0,	-1 },			/* reserved (1) */
		{ 0,		0,	-1 },			/* Slot E IRQ */
		{ 0,		0,	-1 },			/* Video IRQ */
		{ 0,		0,	-1 },			/* reserved (1) */
};


struct powermac_interrupt performa_f108_interrupts[8] = {
		{ 0,		0,	-1 },			/* reserved */
		{ 0,		0,	-1 },			/* reserved */
		{ 0,		0,	-1 },			/* IR Interrupt */
		{ 0,		0,	-1 },			/* Closed Caption Interrupt */
		{ 0, 		0,	PMAC_DEV_ATA1 },	/* ATA1 */
		{ 0,		0,	PMAC_DEV_ATA0 },	/* ATA0 */
		{ 0, 		0,	PMAC_DEV_VBL },		/* Keystone */
		{ 0,		0,	-1 }			/* f108 interrupt output */
};

#if 0
struct powermac_interrupt performa_icr_interrupts[8] = {
 { performa_via1_interrupt,	0,	-1 },			/* Cascade Interrupts */
 { performa_via2_interrupt,	0,	-1 },
		{ 0,		0,	PMAC_DEV_SCC },
		{ 0,		0,	PMAC_DEV_ETHERNET },
		{ 0,		0,	-1 },			/* PDM DMA Interrupt */
		{ 0,		0,	PMAC_DEV_NMI},
		{ 0,		0,	-1 },			/* INT Mode bit */
		{ 0,		0,	-1 }			/* ACK Bit */
};
#endif

static void performa_interrupt( int type, struct ppc_saved_state *ssp,
		   unsigned int dsisr, unsigned int dar, spl_t old_spl);

#define LEN(s)	(sizeof(s)/sizeof(s[0]))

static struct powermac_interrupt *
performa_find_entry(int device, struct powermac_interrupt *list, int len)
{
	int	i;

	for (i = 0; i < len; i++, list++)
		if (list->i_device == device)
			return 	list;

	return NULL;
}



static void
performa_register_ofint(int device, spl_t level,
		void (*handler)(int, void *))
{
	panic("Attempt to register OFW interrupt on PERFORMA/LEGACY class machine");
}

static void
performa_register_int(int device, spl_t level,
		void (*handler)(int, void *))
{
	struct powermac_interrupt *entry;
	volatile unsigned char	*enable_int = NULL;
	int		bit;
	spl_t		s = splhigh();

	if (entry = performa_find_entry(device, performa_via1_interrupts, LEN(performa_via1_interrupts))) {
		enable_int = (unsigned char *) PERFORMA_VIA1_IER;
		bit = entry - performa_via1_interrupts;
		goto found;
	}

	if (entry = performa_find_entry(device, performa_via1_portb_interrupts, LEN(performa_via1_portb_interrupts)))
		goto found;

	if (entry = performa_find_entry(device, performa_via2_interrupts, LEN(performa_via2_interrupts))) {
		enable_int = (unsigned char *) PERFORMA_VIA2_IER;
		bit = entry - performa_via2_interrupts;
		goto found;
	}

	if (entry = performa_find_entry(device, performa_via2_slot_interrupts, LEN(performa_via2_interrupts))) 
		goto found;

#if 0

	if (entry = performa_find_entry(device, performa_icr_interrupts, LEN(performa_icr_interrupts))) {
		goto found;
	}

#endif

	if (entry = performa_find_entry(device, performa_f108_interrupts, LEN(performa_f108_interrupts))) {
		f108_register_int(device, level, handler);
		goto done;
	}

	panic("performa_register_int: Interrupt %d is not supported on this platform.", device);

found:
	entry->i_level = level;
	entry->i_handler = handler;

	if (enable_int)  {
		*enable_int = (*enable_int) | 0x80 | (1<<bit);
		eieio();
	}

done:

	splx(s);
}

void
performa_interrupt_initialize() 
{
	pmac_register_int	= performa_register_int;
	pmac_register_ofint	= performa_register_ofint;
	platform_interrupt	= performa_interrupt;
	platform_spl		= performa_spl;
#if	MACH_KDB
	platform_db_spl		= performa_db_spl;
#endif	/* MACH_KDB */

#if 0
printf("interrupt_initialize called.  Setting PERFORMA_ICR\n");
	// No such frigging thing, that I can find....  What were the
	// designers thinking!?!

	*(v_u_long*)PERFORMA_ICR		= 0x80; eieio();

	// Okay, there's the Capella Bridge with something kind of close.

	(void)pmap_add_physical_memory(PERFORMA_CAPELLA_BASE_PHYS,
		(PERFORMA_CAPELLA_BASE_PHYS + PERFORMA_CAPELLA_SIZE),
		FALSE, PTE_WIMG_IO);
	capella_base_virt = io_map(PERFORMA_CAPELLA_BASE_PHYS,
		PERFORMA_CAPELLA_SIZE);
	*(v_u_long*)(capella_base_virt + CAPELLA_INT_REG_OFFSET) = 0x00;
#else
	capella_base_virt = POWERMAC_IO(PERFORMA_CAPELLA_BASE_PHYS);
	*(v_u_long*)(capella_base_virt + CAPELLA_INT_REG_OFFSET) = 0x00;
#endif

printf("setting PERFORMA_VIA1_PCR");

	*(v_u_char *)PERFORMA_VIA1_PCR	= 0x00; eieio();

	/* VIAs are old... 
	 * quick lesson:
	 * to set one or more bits,   write (0x80 | BITS),
	 * to reset one or more bits, write (0x00 | BITS)
	 * ie. bit 7 holds the new value for the set bits in the mask
	 */

printf("setting PERFORMA_VIA1_IER\n");
	*(v_u_char *)PERFORMA_VIA1_IER	= 0x7f;	eieio();

printf("setting PERFORMA_VIA1_IFR\n");
	*(v_u_char *)PERFORMA_VIA1_IFR	= 0x7f;	eieio();

printf("setting PERFORMA_VIA2_IFR\n");
	*(v_u_char *)PERFORMA_VIA2_IFR	= 0x7f;	eieio();

printf("setting PERFORMA_F108_IFR\n");
	// bits 0 and 1 are interrupt enablers, leave them at 0.  
	// Bits 2 and 3 have to be set to 1 to clear those
	// interrupts.  I'm not sure about 4-7, but we'll assume
	// that a 1 clears them as well.
	*(v_u_char *)PERFORMA_F108_IFR	= 0xfc; eieio();

printf("done.\n");
}

static void
performa_interrupt(int type, struct ppc_saved_state *ssp,
	unsigned int dsisr, unsigned int dar, spl_t old_spl)
{
	unsigned long irq, bit;
	unsigned long value;

	struct powermac_interrupt *handler;

#if 0

	irq = *(v_u_long*)PERFORMA_ICR; eieio();
	/* Acknowledge interrupt */
	*(v_u_long*)PERFORMA_ICR		= 0x80; eieio();

	irq &= 0x7;
	handler = performa_icr_interrupts;

	for (bit = 0; bit < 7; bit++, handler++) {
		if (irq & (1<<bit)) {
			if (handler->i_handler) 
				handler->i_handler(handler->i_device, ssp);
		}
	}
#else

	performa_via1_interrupt(0, ssp);
	performa_via1_portb_interrupt(0, ssp);
	performa_via2_interrupt(0, ssp);

#if 1
	// This is cascaded into slot interrupts, which is, in turn.
	// cascaded into VIA2.
	// performa_f108_interrupt(0, ssp);
#else
	// if ((*(v_u_char *)PERFORMA_F108_IFR) & 0x80)
	// 	performa_f108_interrupt(0, ssp);
#endif
#endif
	/* Acknowledge the interrupt via Capella */
	*(v_u_long*)(capella_base_virt + CAPELLA_INT_REG_OFFSET) = 0x00;
}

void
performa_via1_interrupt(int device, void *ssp)
{
	register unsigned char intbits, bit;
	struct powermac_interrupt	*handler;

	intbits = via_reg(PERFORMA_VIA1_IFR);	/* get interrupts pending */
	eieio();
#if 0
	intbits &= via_reg(PERFORMA_VIA1_IER); 	/* only care about enabled */
	eieio();

	if (intbits == 0)
		return;
#endif

	/*
	 * Unflag interrupts we're about to process.
	 */
	via_reg(PERFORMA_VIA1_IFR) = intbits; eieio();

	handler = performa_via1_interrupts;
	for (bit = 0; bit < 7 ; bit++, handler++) {
		if(intbits & (1<<bit)) {
			if (handler->i_handler)  
				handler->i_handler(handler->i_device, ssp);
		}
	}

}


static void
performa_via1_portb_interrupt(int device, void *ssp)
{
	register unsigned char		intbits, bit;
	struct powermac_interrupt	*handler;


	intbits  = via_reg(PERFORMA_VIA1_PORTB_IFR);
	eieio();

	handler = performa_via1_portb_interrupts;

	/*
	 * Unflag interrupts we're about to process.
	 */
	/* via_reg(PERFORMA_VIA1_PORTB_IFR) = intbits; */

	for (bit = 0; bit < 7; bit++, handler++) {
		if(intbits & (1<<bit)){
			if (handler->i_handler) 
				handler->i_handler(handler->i_device, ssp);
		}
	}


}


static void
performa_via2_interrupt(int device, void *ssp)
{
	register unsigned char		intbits, bit;
	struct powermac_interrupt	*handler;


	intbits  = via_reg(PERFORMA_VIA2_IFR); eieio();
#if 0
	intbits &= via_reg(PERFORMA_VIA2_IER); /* only care about enabled ints*/
	eieio();

	if (intbits == 0)
		return;
#endif

	handler = performa_via2_interrupts;

	/*
	 * Unflag interrupts we're about to process.
	 */
	via_reg(PERFORMA_VIA2_IFR) = intbits; eieio();

	for (bit = 0; bit < 7; bit++, handler++) {
		if(intbits & (1<<bit)){
			if (handler->i_handler) 
				handler->i_handler(handler->i_device, ssp);
		}
	}


}


static void
performa_via2_slot_interrupt(int device, void *ssp)
{
	register unsigned char		intbits, bit;
	struct powermac_interrupt	*handler;


	intbits  = ~via_reg(PERFORMA_VIA2_SLOT_IFR);/* Slot interrupts are reversed of everything else */
	eieio();
#if 0
	intbits &= via_reg(PERFORMA_VIA2_SLOT_IER); /* only care about enabled ints*/
	eieio();
#endif

	handler = performa_via2_slot_interrupts;

	/*
	 * Unflag interrupts we're about to process.
	 */
	/* via_reg(PERFORMA_VIA2_SLOT_IFR) = intbits; */

	for (bit = 0; bit < 7; bit++, handler++) {
		if(intbits & (1<<bit)){
			if (handler->i_handler) 
				handler->i_handler(handler->i_device, ssp);
		}
	}


}


static void
performa_f108_interrupt(int device, void *ssp)
{
	register unsigned char		intbits, bit;
	struct powermac_interrupt	*handler;


	intbits  = via_reg(PERFORMA_F108_IFR); /* should this be inverted? */
	// intbits  = ~via_reg(PERFORMA_F108_IFR);/* Slot interrupts are reversed of everything else */
	eieio();
#if 0
	intbits &= via_reg(PERFORMA_F108_IER); /* only care about enabled ints*/
	eieio();
#endif

	handler = performa_f108_interrupts;

	/*
	 * Unflag interrupts we're about to process.
	 */
	/* via_reg(PERFORMA_F108_IFR) = intbits; */

	for (bit = 0; bit < 7; bit++, handler++) {
		if(intbits & (1<<bit)){
			if (bit == 6) {
		/* Special Case.. only VBL bit is allowed to be cleared */
				//via_reg(PERFORMA_F108_IFR) = 0x40; eieio();
			}

			if (handler->i_handler) 
				handler->i_handler(handler->i_device, ssp);
		}
	}


}


#if 0
void
performa_dma_interrupt(int device, void *ssp)
{
	register unsigned char	bit, intbits;
	struct powermac_interrupt	*handler;

	intbits  = via_reg(PDM_DMA_AUDIO); eieio();
	handler = &performa_dma_interrupts[7];
	for (bit = 0; bit < 2; bit++, handler++) {
		if(intbits & (1<<bit)){
			if (handler->i_handler) {
				handler->i_handler(handler->i_device, ssp);
			} else printf("{!audio dma %d}", bit);
		}
	}

	intbits  = via_reg(PDM_DMA_IFR); eieio();

	handler = performa_dma_interrupts;
	for (bit = 0; bit < 7; bit++, handler++) {
		if(intbits & (1<<bit)){
			if (handler->i_handler) {
				handler->i_handler(handler->i_device, ssp);
			} else printf("{!dma %d}", bit);
		}
	}
}
#endif

void f108_register_int(int device, spl_t level,
		void (*handler)(int, void *))
{
	struct powermac_interrupt *entry;
	int bit;

	entry = performa_find_entry(device, performa_f108_interrupts, LEN(performa_f108_interrupts));
	bit = entry - performa_via1_interrupts;

	switch(bit)
	    {
		case 0: /* IR Interrupt Enable */
		case 1: /* Close Caption Int Enable */
			printf("f108_register_int: Attempt to register an invalid interrupt\n");
			return;
	    }
	entry->i_level = level;
	entry->i_handler = handler;
	return;
}
