/* Copyright (c) 2012, Code Aurora Forum. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * 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.
 */

#include <linux/init.h>
#include <linux/module.h>

#include "mpq_adapter.h"
#include "mpq_dvb_debug.h"


DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);

/* data-structure holding MPQ adapter information */
static struct
{
	/* MPQ adapter registered to dvb-core */
	struct dvb_adapter adapter;

	/* mutex protect against the data-structure */
	struct mutex mutex;

	/* List of stream interfaces registered to the MPQ adapter */
	struct {
		/* pointer to the stream buffer using for data tunneling */
		struct mpq_streambuffer *stream_buffer;

		/* callback triggered when the stream interface is registered */
		mpq_adapter_stream_if_callback callback;

		/* parameter passed to the callback function */
		void *user_param;
	} interfaces[MPQ_ADAPTER_MAX_NUM_OF_INTERFACES];
} mpq_info;


/**
 * Initialize MPQ DVB adapter module.
 *
 * Return     error status
 */
static int __init mpq_adapter_init(void)
{
	int i;
	int result;

	MPQ_DVB_DBG_PRINT("%s executed\n", __func__);

	mutex_init(&mpq_info.mutex);

	/* reset stream interfaces list */
	for (i = 0; i < MPQ_ADAPTER_MAX_NUM_OF_INTERFACES; i++) {
		mpq_info.interfaces[i].stream_buffer = NULL;
		mpq_info.interfaces[i].callback = NULL;
	}

	/* regsiter a new dvb-adapter to dvb-core */
	result = dvb_register_adapter(&mpq_info.adapter,
								  "Qualcomm DVB adapter",
								  THIS_MODULE,
								  NULL,
								  adapter_nr);

	if (result < 0) {
		MPQ_DVB_ERR_PRINT(
			"%s: dvb_register_adapter failed, errno %d\n",
			__func__,
			result);
	}

	return result;
}


/**
 * Cleanup MPQ DVB adapter module.
 */
static void __exit mpq_adapter_exit(void)
{
	MPQ_DVB_DBG_PRINT("%s executed\n", __func__);

	/* un-regsiter adapter from dvb-core */
	dvb_unregister_adapter(&mpq_info.adapter);
	mutex_destroy(&mpq_info.mutex);
}

struct dvb_adapter *mpq_adapter_get(void)
{
	return &mpq_info.adapter;
}
EXPORT_SYMBOL(mpq_adapter_get);


int mpq_adapter_register_stream_if(
		enum mpq_adapter_stream_if interface_id,
		struct mpq_streambuffer *stream_buffer)
{
	int ret;

	if (interface_id >= MPQ_ADAPTER_MAX_NUM_OF_INTERFACES) {
		ret = -EINVAL;
		goto register_failed;
	}

	if (mutex_lock_interruptible(&mpq_info.mutex)) {
		ret = -ERESTARTSYS;
		goto register_failed;
	}

	if (mpq_info.interfaces[interface_id].stream_buffer != NULL) {
		/* already registered interface */
		ret = -EINVAL;
		goto register_failed_unlock_mutex;
	}

	mpq_info.interfaces[interface_id].stream_buffer = stream_buffer;
	mutex_unlock(&mpq_info.mutex);

	/*
	 * If callback is installed, trigger it to notify that
	 * stream interface was registered.
	 */
	if (mpq_info.interfaces[interface_id].callback != NULL) {
		mpq_info.interfaces[interface_id].callback(
				interface_id,
				mpq_info.interfaces[interface_id].user_param);
	}

	return 0;

register_failed_unlock_mutex:
	mutex_unlock(&mpq_info.mutex);
register_failed:
	return ret;
}
EXPORT_SYMBOL(mpq_adapter_register_stream_if);


int mpq_adapter_unregister_stream_if(
		enum mpq_adapter_stream_if interface_id)
{
	if (interface_id >= MPQ_ADAPTER_MAX_NUM_OF_INTERFACES)
		return -EINVAL;

	if (mutex_lock_interruptible(&mpq_info.mutex))
		return -ERESTARTSYS;

	/* clear the registered interface */
	mpq_info.interfaces[interface_id].stream_buffer = NULL;

	mutex_unlock(&mpq_info.mutex);

	return 0;
}
EXPORT_SYMBOL(mpq_adapter_unregister_stream_if);


int mpq_adapter_get_stream_if(
		enum mpq_adapter_stream_if interface_id,
		struct mpq_streambuffer **stream_buffer)
{
	if ((interface_id >= MPQ_ADAPTER_MAX_NUM_OF_INTERFACES) ||
		(stream_buffer == NULL))
		return -EINVAL;

	if (mutex_lock_interruptible(&mpq_info.mutex))
		return -ERESTARTSYS;

	*stream_buffer = mpq_info.interfaces[interface_id].stream_buffer;

	mutex_unlock(&mpq_info.mutex);

	return 0;
}
EXPORT_SYMBOL(mpq_adapter_get_stream_if);


int mpq_adapter_notify_stream_if(
		enum mpq_adapter_stream_if interface_id,
		mpq_adapter_stream_if_callback callback,
		void *user_param)
{
	if (interface_id >= MPQ_ADAPTER_MAX_NUM_OF_INTERFACES)
		return -EINVAL;

	if (mutex_lock_interruptible(&mpq_info.mutex))
		return -ERESTARTSYS;

	mpq_info.interfaces[interface_id].callback = callback;
	mpq_info.interfaces[interface_id].user_param = user_param;

	mutex_unlock(&mpq_info.mutex);

	return 0;
}
EXPORT_SYMBOL(mpq_adapter_notify_stream_if);


module_init(mpq_adapter_init);
module_exit(mpq_adapter_exit);

MODULE_DESCRIPTION("Qualcomm MPQ adapter");
MODULE_LICENSE("GPL v2");

