/*****************************************************************************/
/* 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"

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

/* Internal OSAL event representation */
struct osal_event {
	int active;	/* non-zero if event was activated */
	int autoclear;	/* non-zero if event should be cleared when delivered */
	wait_queue_head_t wait;
	int dead;			/* if non-zero, event was destroyed */
};


static struct handle_table event_handle_table;
#define allocate_osal_event_handle(handle) \
	ALLOCATE_HANDLE_MACRO(&event_handle_table, handle, TMOSAL_ERR_EVT_FULL)
#define allocate_osal_event(oe) \
	ALLOCATE_ZEROED_MACRO(oe)
#define bind_osal_event_to_handle(handle, oe) \
	BIND_OBJECT_MACRO(&event_handle_table, handle, oe)
#define cancel_osal_event_handle(handle) \
	CANCEL_HANDLE_MACRO(&event_handle_table, handle)
#define get_osal_event(handle, oe) \
	GET_OBJECT_MACRO(&event_handle_table, handle, oe, TMOSAL_ERR_HANDLE)
#define put_osal_event(handle) \
	PUT_OBJECT_MACRO(&event_handle_table, handle)
#define mark_dead_osal_event(handle) \
	MARK_DEAD_OBJECT_MACRO(&event_handle_table, handle)


/* Event API */

tmErrorCode_t tmosalEventCreate(ptmosalEventHandle_t pioEventHandle,
		const tmosalEventCreateFlags_t kFlags)
{
	u32 handle;
	struct osal_event *oe;

	BUG_IF_INTERRUPT();
	BUG_IF_NOT_PREEMPTIBLE();

	if (unlikely(!pioEventHandle))
		return TMOSAL_ERR_BAD_PARAM;

	allocate_osal_event_handle(handle);
	allocate_osal_event(oe);
	if (unlikely(!oe)) {
		cancel_osal_event_handle(handle);
		return TMOSAL_ERR_OUT_OF_MEMORY;
	}

	if (kFlags & tmosalEventAutoClear)
		oe->autoclear = 1;
	init_waitqueue_head(&oe->wait);

	STAT_HANDLE_CREATE(curEventNr, maxEventNr);
	bind_osal_event_to_handle(handle, oe);
	*pioEventHandle = handle;
	return TM_OK;

}
EXPORT_SYMBOL(tmosalEventCreate);


tmErrorCode_t tmosalEventDestroy(const tmosalEventHandle_t kEventHandle)
{
	struct osal_event *oe;
	unsigned long flags;
	int empty;
	tmErrorCode_t ret;
	
	get_osal_event(kEventHandle, oe);
	spin_lock_irqsave(&oe->wait.lock, flags);
	oe->dead = 1;
	empty = list_empty(&oe->wait.task_list);
	spin_unlock_irqrestore(&oe->wait.lock, flags);

	if (likely(empty))
		ret = TM_OK;
	else {
		wake_up(&oe->wait);
		if (!in_interrupt() && preemptible())
			schedule();
		/* event will be deallocated by waiting task */
		ret = TMOSAL_ERR_EVT_TASK;
	}

	STAT_HANDLE_DESTROY(curEventNr);
	mark_dead_osal_event(kEventHandle);
	put_osal_event(kEventHandle);
	return ret;
}
EXPORT_SYMBOL(tmosalEventDestroy);


tmErrorCode_t tmosalEventSend(const tmosalEventHandle_t kEventHandle,
		const tmosalEventSendFlags_t kFlags)
{
	struct osal_event *oe;
	unsigned long flags;
	int waiter_exists;

	get_osal_event(kEventHandle, oe);
	spin_lock_irqsave(&oe->wait.lock, flags);
	oe->active = 1;
	waiter_exists = !list_empty(&oe->wait.task_list);
	spin_unlock_irqrestore(&oe->wait.lock, flags);

#if defined (CONFIG_TIME_DOCTOR)
    if (osal_debug_level > 0)
    {
        char name[9];
        sprintf(name, "%07XE", (unsigned int)kEventHandle);
        timeDoctor_Info(TDI_COMMAND(TDI_START, TDI_EVENT),
                    CHAR2_ARRAY_TO_U32(name), CHAR_ARRAY_TO_U32(name));
    }
#endif

	if (unlikely(waiter_exists)) {
		/* it could be better to use wake_up_locked() here, to avoid
		   races. Unfortunately, it is not exported. But there is no
		   much harm anyway, because extra call to wake_up() will just
		   wakeup threads, which will check situation and go sleep
		   again if needed */
		wake_up(&oe->wait);
		if (preemptible() && !in_interrupt())
			schedule();
	}

	put_osal_event(kEventHandle);
	return TM_OK;
}
EXPORT_SYMBOL(tmosalEventSend);


tmErrorCode_t tmosalEventWait(const tmosalEventHandle_t kEventHandle,
		const pInt32 pkTimeout, const tmosalEventWaitFlags_t kFlags)
{
	struct osal_event *oe;
	unsigned long flags;
	int jif;
	int loop_count = 0;
	tmErrorCode_t ret;
	DECLARE_WAITQUEUE(wait, current);
			
	BUG_IF_SLEEP_IN_ATOMIC(pkTimeout);

	get_osal_event(kEventHandle, oe);

	jif = (pkTimeout && *pkTimeout > 0) ? msecs_to_jiffies(*pkTimeout) : 0;
	
	spin_lock_irqsave(&oe->wait.lock, flags);
	
	wait.flags = 0;		/* non-exclusive */
	__add_wait_queue(&oe->wait, &wait);

	while (1) {
		/* check for dead */
		if (unlikely(oe->dead)) {
			ret = TMOSAL_ERR_EVT_DEL;
			break;
		}
		
		/* check for event-ready condition */
		if(kFlags == tmosalEventWaitFlagAlways)
		{
			if(loop_count)
			{
				ret = TM_OK;
				break;
			}
		}
		else
		{
			if (oe->active) {
				if (oe->autoclear)
					oe->active = 0;
				ret = TM_OK;
				break;
			}
		}
	
		/* check for timeout condition */
		if (pkTimeout && !jif) {
			ret = TMOSAL_ERR_EVT_TIMEOUT;
			break;
		}

		/* will sleep */

		set_current_state(TASK_INTERRUPTIBLE);
		spin_unlock_irqrestore(&oe->wait.lock, flags);

#if defined (CONFIG_TIME_DOCTOR)
        if (osal_debug_level > 0)
        {
            char name[9];
            sprintf(name, "%07XE", (unsigned int)kEventHandle);
            timeDoctor_Info(TDI_COMMAND(TDI_STOP, TDI_EVENT),
                        CHAR2_ARRAY_TO_U32(name), CHAR_ARRAY_TO_U32(name));
        }
#endif

		if (pkTimeout)
			jif = schedule_timeout(jif);
		else
			schedule();
		spin_lock_irqsave(&oe->wait.lock, flags);
		
		++loop_count;

		/* will check the wakeup reason on new iteration */
	}

	__remove_wait_queue(&oe->wait, &wait);
	spin_unlock_irqrestore(&oe->wait.lock, flags);
	put_osal_event(kEventHandle);
	return ret;
}
EXPORT_SYMBOL(tmosalEventWait);


tmErrorCode_t tmosalEventReset(tmosalEventHandle_t kEventHandle)
{
	struct osal_event *oe;
	unsigned long flags;

	/* locking probably needed only in SMP case, to ensure that
	 * other CPU's won't get value changed while lock is held */
	get_osal_event(kEventHandle, oe);
	spin_lock_irqsave(&oe->wait.lock, flags);
	oe->active = 0;
	spin_unlock_irqrestore(&oe->wait.lock, flags);
	put_osal_event(kEventHandle);

	return TM_OK;
}
EXPORT_SYMBOL(tmosalEventReset);


int osal_event_submodule_init(void)
{
	return init_handle_table(&event_handle_table, EVENT_HANDLE_TABLE_INITIAL_SIZE, "event");
}

void osal_event_submodule_uninit(void)
{
	uninit_handle_table(&event_handle_table);
}
