/*****************************************************************************/
/* Copyright (c) 2008 NXP B.V.                                               */
/*                                                                           */
/* This program is free software; you can redistribute it and/or modify      */
/* it under the terms of the GNU General Public License as published by      */
/* the Free Software Foundation, using version 2 of the License.             */
/*                                                                           */
/* This program is distributed in the hope that it will be useful,           */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of            */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the              */
/* GNU General Public License for more details.                              */
/*                                                                           */
/* You should have received a copy of the GNU General Public License         */
/* along with this program; if not, write to the Free Software               */
/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307       */
/* USA.                                                                      */
/*                                                                           */
/*****************************************************************************/

#include "common.h"
#include "linux/interrupt.h"
#include <irq.h>
#include <pnx833x.h>

#if defined (CONFIG_TIME_DOCTOR)
#include "linux/timedoctor.h"
#endif

/* Internal interrupt data structure */
struct osal_int {
	volatile int disable_request;
	volatile int disable_confirm;
	int irq;
	int interruptible;
	int priority;
	void (*handler)(void *);
	void *arg;
};


static struct handle_table int_handle_table;
#define allocate_osal_int_handle(handle) \
	ALLOCATE_HANDLE_MACRO(&int_handle_table, handle, TMOSAL_ERR_UNKNOWN)
#define allocate_osal_int(oi) \
	ALLOCATE_ZEROED_MACRO(oi)
#define bind_osal_int_to_handle(handle, oi) \
	BIND_OBJECT_MACRO(&int_handle_table, handle, oi)
#define cancel_osal_int_handle(handle) \
	CANCEL_HANDLE_MACRO(&int_handle_table, handle)
#define get_osal_int(handle, oi) \
    GET_OBJECT_MACRO(&int_handle_table, (u32)handle, oi, TMOSAL_ERR_HANDLE)
#define put_osal_int(handle) \
	PUT_OBJECT_MACRO(&int_handle_table, (u32)handle)
#define mark_dead_osal_int(handle) \
    MARK_DEAD_OBJECT_MACRO(&int_handle_table, (u32)handle)


/* Interrupt name-to-number mapping */
#define DEFINE_IRQ(name, irq) { name, (irq) + PNX833X_PIC_IRQ_BASE},
struct {
	const char *name;
	int irq;
} int_mapping[] = {
#include "int_names.inc"
	{ 0, 0 }
};

static int name_to_irq(const char *name)
{
	int i;
	for (i = 0; int_mapping[i].name; i++) {
		if (!strcmp(int_mapping[i].name, name))
			return int_mapping[i].irq;
	}
	return -1;
}

static const char * irq_to_name(int irq)
{
	int i;
	for (i = 0; int_mapping[i].irq; i++) {
		if (int_mapping[i].irq == irq)
			return int_mapping[i].name;
	}
	return "osal_irq";
}


/* Internal interrupt handler. Called with interrupts enabled, but
 * with current irq masked. */
static irqreturn_t osal_handler(int irq, void *arg)
{
	struct osal_int *oi = (struct osal_int *)arg;

	if (unlikely(oi->disable_request))
	{
		disable_irq(oi->irq);
		oi->disable_confirm = 1;
	}
	else
	{
		if (likely(oi->handler))
		{
			unsigned long irqPriority = PNX833X_PIC_INT_PRIORITY;

#if defined (CONFIG_TIME_DOCTOR)
            if (osal_debug_level > 0)
            {
			    timeDoctor_Info(TDI_COMMAND(TDI_START, TDI_AGENT) | oi->irq,
				    irqs_disabled(), 0);
            }
#endif
			if (oi->interruptible)
			{
				PNX833X_PIC_INT_PRIORITY = irqPriority - 1;
			}
			oi->handler(oi->arg);
			PNX833X_PIC_INT_PRIORITY = irqPriority;
#if defined (CONFIG_TIME_DOCTOR)
			if (osal_debug_level > 0)
            {
                timeDoctor_Info(TDI_COMMAND(TDI_STOP, TDI_AGENT) | oi->irq,
				    irqs_disabled(), 0);
			}
#endif
		}
	}
	return IRQ_HANDLED;
}


/* Interrupt-related API */

tmErrorCode_t tmosalIntCreate(const String pkName,
		ptmosalIntHandle_t pioHandle,
		const tmosalIntCreateFlags_t kFlags)
{
	u32 handle;
	struct osal_int *oi;
	int res, irq;

	BUG_IF_INTERRUPT();
	BUG_IF_NOT_PREEMPTIBLE();

	if (unlikely(!pioHandle) || unlikely(!pkName))
		return TMOSAL_ERR_BAD_PARAM;

	irq = name_to_irq(pkName);
	if (unlikely(irq < 0))
		return TMOSAL_ERR_INT_INVALID;

	allocate_osal_int_handle(handle);
	allocate_osal_int(oi);
	if (!oi) {
		cancel_osal_int_handle(handle);
		return TMOSAL_ERR_UNKNOWN;
	}
	oi->irq = irq;
	oi->disable_request = 1;
	oi->disable_confirm = 0;

	res = request_irq(irq, osal_handler, 0, irq_to_name(irq), (void *)oi);
	if (res < 0) {
		kfree(oi);
		cancel_osal_int_handle(handle);
		return res == -EBUSY ? TMOSAL_ERR_INT_OWNED : TMOSAL_ERR_UNKNOWN;
	}

	bind_osal_int_to_handle(handle, oi);
	*pioHandle = (tmosalIntHandle_t)handle;
	return TM_OK;
}
EXPORT_SYMBOL(tmosalIntCreate);

tmErrorCode_t tmosalIntDestroy(const tmosalIntHandle_t kHandle)
{
	struct osal_int *oi;

	BUG_IF_INTERRUPT();

	get_osal_int(kHandle, oi);

	free_irq(oi->irq, oi);
	mark_dead_osal_int(kHandle);
	put_osal_int(kHandle);
	return TM_OK;
}
EXPORT_SYMBOL(tmosalIntDestroy);


tmErrorCode_t tmosalIntInstanceSetup(const tmosalIntHandle_t kHandle,
		const ptmosalIntDescriptor_t pkDescriptor)
{
	struct osal_int *oi;

	BUG_IF_INTERRUPT();

	if (unlikely(!pkDescriptor))
		return TMOSAL_ERR_BAD_PARAM;

	if (unlikely(!pkDescriptor->pHandler))
		return TMOSAL_ERR_INT_HANDLER;

	get_osal_int(kHandle, oi);

	oi->disable_request = !pkDescriptor->enabled;

	oi->arg = pkDescriptor->pContextArgument;
	oi->priority = pkDescriptor->priority;		/* not used currently */
	oi->interruptible = (pkDescriptor->interruptible != 0);
	oi->handler = pkDescriptor->pHandler;

	/* but note that if irq is already pending, it will raise after unlock */
	if ((!oi->disable_request) && (oi->disable_confirm))
	{
		enable_irq(oi->irq);
		oi->disable_confirm = 0;
	}

	put_osal_int(kHandle);
	return TM_OK;
}
EXPORT_SYMBOL(tmosalIntInstanceSetup);


tmErrorCode_t tmosalIntInstanceGetSetup(const tmosalIntHandle_t kHandle,
		ptmosalIntDescriptor_t pioDescriptor)
{
	struct osal_int *oi;

	if (unlikely(!pioDescriptor))
		return TMOSAL_ERR_BAD_PARAM;

	get_osal_int(kHandle, oi);
	pioDescriptor->pHandler = oi->handler;
	pioDescriptor->pContextArgument = oi->arg;
	pioDescriptor->priority = oi->priority;
	pioDescriptor->interruptible = oi->interruptible;
	pioDescriptor->enabled = !oi->disable_request;
	put_osal_int(kHandle);
	return TM_OK;
}
EXPORT_SYMBOL(tmosalIntInstanceGetSetup);


tmErrorCode_t tmosalIntRaise(const tmosalIntHandle_t kHandle)
{
	printk(KERN_ERR "tmosalIntRaise() is not supported\n");
	WARN_ON(1);
	return TMOSAL_ERR_UNKNOWN;
}
EXPORT_SYMBOL(tmosalIntRaise);

tmErrorCode_t tmosalIntClear(const tmosalIntHandle_t kHandle)
{
	/* This is a silent no-op currently */
	return TM_OK;
}
EXPORT_SYMBOL(tmosalIntClear);


int osal_int_submodule_init(void)
{
	return init_handle_table(&int_handle_table,
			sizeof(int_mapping) / sizeof(int_mapping[0]), "int");
}

void osal_int_submodule_uninit(void)
{
	uninit_handle_table(&int_handle_table);
}
