/*	$NetBSD: pic_ohare.c,v 1.14 2017/06/01 02:45:06 chs Exp $ */

/*-
 * Copyright (c) 2007 Michael Lorenz
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: pic_ohare.c,v 1.14 2017/06/01 02:45:06 chs Exp $");

#include "opt_interrupt.h"

#include <sys/param.h>
#include <sys/kmem.h>
#include <sys/kernel.h>

#include <machine/pio.h>

#include <dev/ofw/openfirm.h>

#include <machine/autoconf.h>
#include <arch/powerpc/pic/picvar.h>

static void ohare_enable_irq(struct pic_ops *, int, int);
static void ohare_reenable_irq(struct pic_ops *, int, int);
static void ohare_disable_irq(struct pic_ops *, int);
static int  ohare_get_irq(struct pic_ops *, int);
static void ohare_ack_irq(struct pic_ops *, int);
static void ohare_establish_irq(struct pic_ops *, int, int, int);

#define OHARE_NIRQ 32

struct ohare_ops {
	struct pic_ops pic;
	uint32_t pending_events;
	uint32_t enable_mask;
	uint32_t level_mask;
	uint32_t irqs[NIPL];			/* per priority level */
	uint32_t priority_masks[OHARE_NIRQ];	/* per IRQ */
};

static struct ohare_ops *setup_ohare(uint32_t, int);
static void setup_ohare2(uint32_t, int);
static inline void ohare_read_events(struct ohare_ops *);

#define INT_STATE_REG	((uint32_t)pic->pic_cookie + 0x20)
#define INT_ENABLE_REG	((uint32_t)pic->pic_cookie + 0x24)
#define INT_CLEAR_REG	((uint32_t)pic->pic_cookie + 0x28)
#define INT_LEVEL_REG	((uint32_t)pic->pic_cookie + 0x2c)

int init_ohare(void)
{
	uint32_t reg[5];
	uint32_t obio_base;
	uint32_t irq;
	int      ohare, ohare2, is_gc = 0;

	ohare = OF_finddevice("/bandit/ohare");
	if (ohare == -1) {
		ohare = OF_finddevice("/bandit/gc");
		is_gc = 1;
	}
		

	if (OF_getprop(ohare, "assigned-addresses", reg, sizeof(reg)) != 20) 
		return FALSE;

	obio_base = reg[2];
	aprint_normal("found %s PIC at %08x\n", 
	    is_gc ? "Grand Central" : "ohare", obio_base);
	setup_ohare(obio_base, is_gc);

	/* look for 2nd ohare */
	ohare2 = OF_finddevice("/bandit/pci106b,7");
	if (ohare2 == -1)
		goto done;

	if (OF_getprop(ohare2, "assigned-addresses", reg, sizeof(reg)) < 20)
		goto done;

	if (OF_getprop(ohare2, "AAPL,interrupts", &irq, sizeof(irq)) < 4)
		goto done;

	obio_base = reg[2];			
	aprint_normal("found ohare2 PIC at %08x, irq %d\n", obio_base, irq);
	setup_ohare2(obio_base, irq);	
done:
	return TRUE;
}

static struct ohare_ops *
setup_ohare(uint32_t addr, int is_gc)
{
	struct ohare_ops *ohare;
	struct pic_ops *pic;
	int i;

	ohare = kmem_alloc(sizeof(struct ohare_ops), KM_SLEEP);
	pic = &ohare->pic;

	pic->pic_numintrs = OHARE_NIRQ;
	pic->pic_cookie = (void *)addr;
	pic->pic_enable_irq = ohare_enable_irq;
	pic->pic_reenable_irq = ohare_reenable_irq;
	pic->pic_disable_irq = ohare_disable_irq;
	pic->pic_get_irq = ohare_get_irq;
	pic->pic_ack_irq = ohare_ack_irq;
	pic->pic_establish_irq = ohare_establish_irq;
	pic->pic_finish_setup = NULL;

	if (is_gc) {
	
		strcpy(pic->pic_name, "gc");
	} else {

		strcpy(pic->pic_name, "ohare");
	}
	ohare->level_mask = 0;

	for (i = 0; i < OHARE_NIRQ; i++)
		ohare->priority_masks[i] = 0;
	for (i = 0; i < NIPL; i++)
		ohare->irqs[i] = 0;
	pic_add(pic);
	ohare->pending_events = 0;
	ohare->enable_mask = 0;
	out32rb(INT_ENABLE_REG, 0);
	out32rb(INT_CLEAR_REG, 0xffffffff);
	return ohare;
}

static void
setup_ohare2(uint32_t addr, int irq)
{
	struct ohare_ops *pic;

	pic = setup_ohare(addr, 0);
	strcpy(pic->pic.pic_name, "ohare2");
	intr_establish(irq, IST_LEVEL, IPL_HIGH, pic_handle_intr, pic);
}

static void
ohare_enable_irq(struct pic_ops *pic, int irq, int type)
{
	struct ohare_ops *ohare = (struct ohare_ops *)pic;
	uint32_t mask = 1 << irq;

	ohare->enable_mask |= mask;
	out32rb(INT_ENABLE_REG, ohare->enable_mask);
}

static void
ohare_reenable_irq(struct pic_ops *pic, int irq, int type)
{
	struct ohare_ops *ohare = (struct ohare_ops *)pic;
	uint32_t levels;
	uint32_t mask = 1 << irq;

	ohare->enable_mask |= mask;
	out32rb(INT_ENABLE_REG, ohare->enable_mask);
	levels = in32rb(INT_LEVEL_REG);
	if (levels & mask) {
		pic_mark_pending(pic->pic_intrbase + irq);
		out32rb(INT_CLEAR_REG, mask);
	}
}

static void
ohare_disable_irq(struct pic_ops *pic, int irq)
{
	struct ohare_ops *ohare = (struct ohare_ops *)pic;
	uint32_t mask = 1 << irq;

	ohare->enable_mask &= ~mask;
	out32rb(INT_ENABLE_REG, ohare->enable_mask); 
}

static inline void
ohare_read_events(struct ohare_ops *ohare)
{
	struct pic_ops *pic = &ohare->pic;
	uint32_t irqs, events, levels;

	irqs = in32rb(INT_STATE_REG);
	events = irqs & ~ohare->level_mask;

	levels = in32rb(INT_LEVEL_REG) & ohare->enable_mask;
	events |= levels & ohare->level_mask;
	out32rb(INT_CLEAR_REG, events | irqs);
	ohare->pending_events |= events;

#if 0
	if (events != 0)
		aprint_error("%s: ev %08x\n", __func__, events);
#endif
}

static int
ohare_get_irq(struct pic_ops *pic, int mode)
{
	struct ohare_ops *ohare = (struct ohare_ops *)pic;
	uint32_t evt;
	uint16_t prio;
	int bit, mask, lvl;
#ifdef OHARE_DEBUG
	int bail = 0;
#endif

	if (ohare->pending_events == 0)
		ohare_read_events(ohare);

	if (ohare->pending_events == 0)
		return 255;

	bit = 31 - __builtin_clz(ohare->pending_events);
	mask = 1 << bit;

	if ((ohare->pending_events & ~mask) == 0) {

		ohare->pending_events = 0;
		return bit;
	}

	/*
	 * if we get here we have more than one irq pending so return them
	 * according to priority
	 */

	evt = ohare->pending_events & ~mask;
	prio = ohare->priority_masks[bit];
	while (evt != 0) {
		bit = 31 - __builtin_clz(evt);
		prio |= ohare->priority_masks[bit];
		evt &= ~(1 << bit);
#ifdef OHARE_DEBUG
		bail++;
		if (bail > 31)
			panic("hanging in ohare_get_irq");
#endif
	}
	lvl = 31 - __builtin_clz(prio);
	evt = ohare->pending_events & ohare->irqs[lvl];

	if (evt == 0) {
#ifdef OHARE_DEBUG
		aprint_verbose("%s: spurious interrupt\n", 
		    ohare->pic.pic_name);
		printf("levels: %08x\n", in32rb(INT_LEVEL_REG));
		printf("states: %08x\n", in32rb(INT_STATE_REG));
		printf("enable: %08x\n", in32rb(INT_ENABLE_REG));
		printf("events: %08x\n", ohare->pending_events);
#endif
		evt = ohare->pending_events;
	}

	bit = 31 - __builtin_clz(evt);
	mask = 1 << bit;
	ohare->pending_events &= ~mask;
	return bit;	
}

static void
ohare_ack_irq(struct pic_ops *pic, int irq)
{
}

static void
ohare_establish_irq(struct pic_ops *pic, int irq, int type, int pri)
{
	struct ohare_ops *ohare = (struct ohare_ops *)pic;
	uint32_t mask = (1 << irq);
	int realpri = min(NIPL, max(0, pri)), i;
	uint32_t level = 1 << realpri;

	KASSERT((irq >= 0) && (irq < OHARE_NIRQ));

	if (type == IST_LEVEL) {

		ohare->level_mask |= mask;
	} else {

		ohare->level_mask &= ~mask;
	}
	aprint_debug("mask: %08x\n", ohare->level_mask);
	ohare->priority_masks[irq] = level;
	for (i = 0; i < NIPL; i++)
		ohare->irqs[i] = 0;
		
	for (i = 0; i < OHARE_NIRQ; i++) {
		if (ohare->priority_masks[i] == 0)
			continue;
		level = 31 - __builtin_clz(ohare->priority_masks[i]);
		ohare->irqs[level] |= (1 << i);
	}
}
