/* $OpenBSD: intc.c,v 1.3 2014/07/12 18:44:41 tedu Exp $ */
/*
 * Copyright (c) 2007,2009 Dale Rahn <drahn@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/queue.h>
#include <sys/malloc.h>
#include <sys/device.h>
#include <sys/evcount.h>
#include <machine/bus.h>
#include <armv7/armv7/armv7var.h>
#include "intc.h"

#define INTC_NUM_IRQ intc_nirq
#define INTC_NUM_BANKS (intc_nirq/32)
#define INTC_MAX_IRQ 128
#define INTC_MAX_BANKS (INTC_MAX_IRQ/32)

/* registers */
#define	INTC_REVISION		0x00	/* R */
#define	INTC_SYSCONFIG		0x10	/* RW */
#define		INTC_SYSCONFIG_AUTOIDLE		0x1
#define		INTC_SYSCONFIG_SOFTRESET	0x2
#define	INTC_SYSSTATUS		0x14	/* R */
#define		INTC_SYSSYSTATUS_RESETDONE	0x1
#define	INTC_SIR_IRQ		0x40	/* R */	
#define	INTC_SIR_FIQ		0x44	/* R */
#define	INTC_CONTROL		0x48	/* RW */
#define		INTC_CONTROL_NEWIRQ	0x1
#define		INTC_CONTROL_NEWFIQ	0x2
#define		INTC_CONTROL_GLOBALMASK	0x1
#define	INTC_PROTECTION		0x4c	/* RW */
#define		INTC_PROTECTION_PROT 1	/* only privileged mode */
#define	INTC_IDLE		0x50	/* RW */

#define INTC_IRQ_TO_REG(i)	(((i) >> 5) & 0x3)
#define INTC_IRQ_TO_REGi(i)	((i) & 0x1f)
#define	INTC_ITRn(i)		0x80+(0x20*i)+0x00	/* R */
#define	INTC_MIRn(i)		0x80+(0x20*i)+0x04	/* RW */
#define	INTC_CLEARn(i)		0x80+(0x20*i)+0x08	/* RW */
#define	INTC_SETn(i)		0x80+(0x20*i)+0x0c	/* RW */
#define	INTC_ISR_SETn(i)	0x80+(0x20*i)+0x10	/* RW */
#define	INTC_ISR_CLEARn(i)	0x80+(0x20*i)+0x14	/* RW */
#define INTC_PENDING_IRQn(i)	0x80+(0x20*i)+0x18	/* R */
#define INTC_PENDING_FIQn(i)	0x80+(0x20*i)+0x1c	/* R */

#define INTC_ILRn(i)		0x100+(4*i)
#define		INTC_ILR_IRQ	0x0		/* not of FIQ */
#define		INTC_ILR_FIQ	0x1
#define		INTC_ILR_PRIs(pri)	((pri) << 2)
#define		INTC_ILR_PRI(reg)	(((reg) >> 2) & 0x2f)
#define		INTC_MIN_PRI	63
#define		INTC_STD_PRI	32
#define		INTC_MAX_PRI	0

struct intrhand {
	TAILQ_ENTRY(intrhand) ih_list;	/* link on intrq list */
	int (*ih_func)(void *);		/* handler */
	void *ih_arg;			/* arg for handler */
	int ih_ipl;			/* IPL_* */
	int ih_irq;			/* IRQ number */
	struct evcount	ih_count;
	char *ih_name;
};

struct intrq {
	TAILQ_HEAD(, intrhand) iq_list;	/* handler list */
	int iq_irq;			/* IRQ to mask while handling */
	int iq_levels;			/* IPL_*'s this IRQ has */
	int iq_ist;			/* share type */
};

volatile int softint_pending;

struct intrq intc_handler[INTC_MAX_IRQ];
u_int32_t intc_smask[NIPL];
u_int32_t intc_imask[INTC_MAX_BANKS][NIPL];

bus_space_tag_t		intc_iot;
bus_space_handle_t	intc_ioh;
int			intc_nirq;

void	intc_attach(struct device *, struct device *, void *);
int	intc_spllower(int new);
int	intc_splraise(int new);
void	intc_setipl(int new);
void	intc_calc_mask(void);

struct cfattach	intc_ca = {
	sizeof (struct device), NULL, intc_attach
};

struct cfdriver intc_cd = {
	NULL, "intc", DV_DULL
};

int intc_attached = 0;

void
intc_attach(struct device *parent, struct device *self, void *args)
{
	struct armv7_attach_args *aa = args;
	int i;
	u_int32_t rev;

	intc_iot = aa->aa_iot;
	if (bus_space_map(intc_iot, aa->aa_dev->mem[0].addr,
	    aa->aa_dev->mem[0].size, 0, &intc_ioh))
		panic("intc_attach: bus_space_map failed!");

	rev = bus_space_read_4(intc_iot, intc_ioh, INTC_REVISION);

	printf(" rev %d.%d\n", rev >> 4 & 0xf, rev & 0xf);

	/* software reset of the part? */
	/* set protection bit (kernel only)? */
#if 0
	bus_space_write_4(intc_iot, intc_ioh, INTC_PROTECTION,
	     INTC_PROTECTION_PROT);
#endif

	/* enable interface clock power saving mode */
	bus_space_write_4(intc_iot, intc_ioh, INTC_SYSCONFIG,
	    INTC_SYSCONFIG_AUTOIDLE);

	switch (board_id) {
	case BOARD_ID_AM335X_BEAGLEBONE:
		intc_nirq = 128;
		break;
	default:
		intc_nirq = 96;
		break;
	}

	/* mask all interrupts */
	for (i = 0; i < INTC_NUM_BANKS; i++)
		bus_space_write_4(intc_iot, intc_ioh, INTC_MIRn(i), 0xffffffff);

	for (i = 0; i < INTC_NUM_IRQ; i++) {
		bus_space_write_4(intc_iot, intc_ioh, INTC_ILRn(i),
		    INTC_ILR_PRIs(INTC_MIN_PRI)|INTC_ILR_IRQ);

		TAILQ_INIT(&intc_handler[i].iq_list);
	}

	intc_calc_mask();
	bus_space_write_4(intc_iot, intc_ioh, INTC_CONTROL,
	    INTC_CONTROL_NEWIRQ);

	intc_attached = 1;

	/* insert self as interrupt handler */
	arm_set_intr_handler(intc_splraise, intc_spllower, intc_splx,
	    intc_setipl,
	    intc_intr_establish, intc_intr_disestablish, intc_intr_string,
	    intc_irq_handler);

	intc_setipl(IPL_HIGH);  /* XXX ??? */
	enable_interrupts(I32_bit);
}

void
intc_calc_mask(void)
{
	struct cpu_info *ci = curcpu();
	int irq;
	struct intrhand *ih;
	int i;

	for (irq = 0; irq < INTC_NUM_IRQ; irq++) {
		int max = IPL_NONE;
		int min = IPL_HIGH;
		TAILQ_FOREACH(ih, &intc_handler[irq].iq_list, ih_list) {
			if (ih->ih_ipl > max)
				max = ih->ih_ipl;

			if (ih->ih_ipl < min)
				min = ih->ih_ipl;
		}

		intc_handler[irq].iq_irq = max;

		if (max == IPL_NONE)
			min = IPL_NONE;

#ifdef DEBUG_INTC
		if (min != IPL_NONE) {
			printf("irq %d to block at %d %d reg %d bit %d\n",
			    irq, max, min, INTC_IRQ_TO_REG(irq),
			    INTC_IRQ_TO_REGi(irq));
		}
#endif
		/* Enable interrupts at lower levels, clear -> enable */
		for (i = 0; i < min; i++)
			intc_imask[INTC_IRQ_TO_REG(irq)][i] &=
			    ~(1 << INTC_IRQ_TO_REGi(irq));
		for (; i <= IPL_HIGH; i++)
			intc_imask[INTC_IRQ_TO_REG(irq)][i] |=
			    1 << INTC_IRQ_TO_REGi(irq);
		/* XXX - set enable/disable, priority */ 
		bus_space_write_4(intc_iot, intc_ioh, INTC_ILRn(irq),
		    INTC_ILR_PRIs(NIPL-max)|INTC_ILR_IRQ);
	}
	arm_init_smask();
	intc_setipl(ci->ci_cpl);
}

void
intc_splx(int new)
{
	struct cpu_info *ci = curcpu();
	intc_setipl(new);

	if (ci->ci_ipending & arm_smask[ci->ci_cpl])
		arm_do_pending_intr(ci->ci_cpl);
}

int
intc_spllower(int new)
{
	struct cpu_info *ci = curcpu();
	int old = ci->ci_cpl;
	intc_splx(new);
	return (old);
}

int
intc_splraise(int new)
{
	struct cpu_info *ci = curcpu();
	int old;
	old = ci->ci_cpl;

	/*
	 * setipl must always be called because there is a race window
	 * where the variable is updated before the mask is set
	 * an interrupt occurs in that window without the mask always
	 * being set, the hardware might not get updated on the next
	 * splraise completely messing up spl protection.
	 */
	if (old > new)
		new = old;

	intc_setipl(new);
  
	return (old);
}

void
intc_setipl(int new)
{
	struct cpu_info *ci = curcpu();
	int i;
	int psw;
	if (intc_attached == 0)
		return;

	psw = disable_interrupts(I32_bit);
#if 0
	{
		volatile static int recursed = 0;
		if (recursed == 0) {
			recursed = 1;
			if (new != 12) 
				printf("setipl %d\n", new);
			recursed = 0;
		}
	}
#endif
	ci->ci_cpl = new;
	for (i = 0; i < INTC_NUM_BANKS; i++)
		bus_space_write_4(intc_iot, intc_ioh,
		    INTC_MIRn(i), intc_imask[i][new]);
	bus_space_write_4(intc_iot, intc_ioh, INTC_CONTROL,
	    INTC_CONTROL_NEWIRQ);
	restore_interrupts(psw);
}

void
intc_intr_bootstrap(vaddr_t addr)
{
	int i, j;
	extern struct bus_space armv7_bs_tag;
	intc_iot = &armv7_bs_tag;
	intc_ioh = addr;
	for (i = 0; i < INTC_NUM_BANKS; i++)
		for (j = 0; j < NIPL; j++)
			intc_imask[i][j] = 0xffffffff;
}

void
intc_irq_handler(void *frame)
{
	int irq, pri, s;
	struct intrhand *ih;
	void *arg;

	irq = bus_space_read_4(intc_iot, intc_ioh, INTC_SIR_IRQ);
#ifdef DEBUG_INTC
	printf("irq %d fired\n", irq);
#endif

	pri = intc_handler[irq].iq_irq;
	s = intc_splraise(pri);
	TAILQ_FOREACH(ih, &intc_handler[irq].iq_list, ih_list) {
		if (ih->ih_arg != 0)
			arg = ih->ih_arg;
		else
			arg = frame;

		if (ih->ih_func(arg)) 
			ih->ih_count.ec_count++;

	}
	bus_space_write_4(intc_iot, intc_ioh, INTC_CONTROL,
	    INTC_CONTROL_NEWIRQ);

	intc_splx(s);
}

void *
intc_intr_establish(int irqno, int level, int (*func)(void *),
    void *arg, char *name)
{
	int psw;
	struct intrhand *ih;

	if (irqno < 0 || irqno >= INTC_NUM_IRQ)
		panic("intc_intr_establish: bogus irqnumber %d: %s",
		     irqno, name);
	psw = disable_interrupts(I32_bit);

	/* no point in sleeping unless someone can free memory. */
	ih = (struct intrhand *)malloc (sizeof *ih, M_DEVBUF,
	    cold ? M_NOWAIT : M_WAITOK);
	if (ih == NULL)
		panic("intr_establish: can't malloc handler info");
	ih->ih_func = func;
	ih->ih_arg = arg;
	ih->ih_ipl = level;
	ih->ih_irq = irqno;
	ih->ih_name = name;

	TAILQ_INSERT_TAIL(&intc_handler[irqno].iq_list, ih, ih_list);

	if (name != NULL)
		evcount_attach(&ih->ih_count, name, &ih->ih_irq);

#ifdef DEBUG_INTC
	printf("intc_intr_establish irq %d level %d [%s]\n", irqno, level,
	    name);
#endif
	intc_calc_mask();
	
	restore_interrupts(psw);
	return (ih);
}

void
intc_intr_disestablish(void *cookie)
{
	int psw;
	struct intrhand *ih = cookie;
	int irqno = ih->ih_irq;
	psw = disable_interrupts(I32_bit);
	TAILQ_REMOVE(&intc_handler[irqno].iq_list, ih, ih_list);
	if (ih->ih_name != NULL)
		evcount_detach(&ih->ih_count);
	free(ih, M_DEVBUF, 0);
	restore_interrupts(psw);
}

const char *
intc_intr_string(void *cookie)
{
	return "huh?";
}


#if 0
int intc_tst(void *a);

int
intc_tst(void *a)
{
	printf("inct_tst called\n");
	bus_space_write_4(intc_iot, intc_ioh, INTC_ISR_CLEARn(0), 2);
	return 1;
}

void intc_test(void);
void intc_test(void)
{
	void * ih;
	printf("about to register handler\n");
	ih = intc_intr_establish(1, IPL_BIO, intc_tst, NULL, "intctst");

	printf("about to set bit\n");
	bus_space_write_4(intc_iot, intc_ioh, INTC_ISR_SETn(0), 2);

	printf("about to clear bit\n");
	bus_space_write_4(intc_iot, intc_ioh, INTC_ISR_CLEARn(0), 2);

	printf("about to remove handler\n");
	intc_intr_disestablish(ih);

	printf("done\n");
}
#endif
