/*
 * strm.c
 *
 * DSP-BIOS Bridge driver support functions for TI OMAP processors.
 *
 * DSP/BIOS Bridge Stream Manager.
 *
 * Copyright (C) 2005-2006 Texas Instruments, Inc.
 *
 * This package is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

#include <linux/types.h>

/*  ----------------------------------- Host OS */
#include <dspbridge/host_os.h>

/*  ----------------------------------- DSP/BIOS Bridge */
#include <dspbridge/dbdefs.h>

/*  ----------------------------------- OS Adaptation Layer */
#include <dspbridge/sync.h>

/*  ----------------------------------- Bridge Driver */
#include <dspbridge/dspdefs.h>

/*  ----------------------------------- Resource Manager */
#include <dspbridge/nodepriv.h>

/*  ----------------------------------- Others */
#include <dspbridge/cmm.h>

/*  ----------------------------------- This */
#include <dspbridge/strm.h>

#include <dspbridge/resourcecleanup.h>

/*  ----------------------------------- Defines, Data Structures, Typedefs */
#define DEFAULTTIMEOUT      10000
#define DEFAULTNUMBUFS      2

/*
 *  ======== strm_mgr ========
 *  The strm_mgr contains device information needed to open the underlying
 *  channels of a stream.
 */
struct strm_mgr {
	struct dev_object *dev_obj;	/* Device for this processor */
	struct chnl_mgr *chnl_mgr;	/* Channel manager */
	/* Function interface to Bridge driver */
	struct bridge_drv_interface *intf_fxns;
};

/*
 *  ======== strm_object ========
 *  This object is allocated in strm_open().
 */
struct strm_object {
	struct strm_mgr *strm_mgr_obj;
	struct chnl_object *chnl_obj;
	u32 dir;		/* DSP_TONODE or DSP_FROMNODE */
	u32 timeout;
	u32 num_bufs;		/* Max # of bufs allowed in stream */
	u32 bufs_in_strm;	/* Current # of bufs in stream */
	u32 bytes;		/* bytes transferred since idled */
	/* STREAM_IDLE, STREAM_READY, ... */
	enum dsp_streamstate strm_state;
	void *user_event;	/* Saved for strm_get_info() */
	enum dsp_strmmode strm_mode;	/* STRMMODE_[PROCCOPY][ZEROCOPY]... */
	u32 dma_chnl_id;	/* DMA chnl id */
	u32 dma_priority;	/* DMA priority:DMAPRI_[LOW][HIGH] */
	u32 segment_id;		/* >0 is SM segment.=0 is local heap */
	u32 buf_alignment;	/* Alignment for stream bufs */
	/* Stream's SM address translator */
	struct cmm_xlatorobject *xlator;
};

/*  ----------------------------------- Function Prototypes */
static int delete_strm(struct strm_object *stream_obj);

/*
 *  ======== strm_allocate_buffer ========
 *  Purpose:
 *      Allocates buffers for a stream.
 */
int strm_allocate_buffer(struct strm_res_object *strmres, u32 usize,
				u8 **ap_buffer, u32 num_bufs,
				struct process_context *pr_ctxt)
{
	int status = 0;
	u32 alloc_cnt = 0;
	u32 i;
	struct strm_object *stream_obj = strmres->stream;

	if (stream_obj) {
		/*
		 * Allocate from segment specified at time of stream open.
		 */
		if (usize == 0)
			status = -EINVAL;

	} else {
		status = -EFAULT;
	}

	if (status)
		goto func_end;

	for (i = 0; i < num_bufs; i++) {
		(void)cmm_xlator_alloc_buf(stream_obj->xlator, &ap_buffer[i],
					   usize);
		if (ap_buffer[i] == NULL) {
			status = -ENOMEM;
			alloc_cnt = i;
			break;
		}
	}
	if (status)
		strm_free_buffer(strmres, ap_buffer, alloc_cnt, pr_ctxt);

	if (status)
		goto func_end;

	drv_proc_update_strm_res(num_bufs, strmres);

func_end:
	return status;
}

/*
 *  ======== strm_close ========
 *  Purpose:
 *      Close a stream opened with strm_open().
 */
int strm_close(struct strm_res_object *strmres,
		      struct process_context *pr_ctxt)
{
	struct bridge_drv_interface *intf_fxns;
	struct chnl_info chnl_info_obj;
	int status = 0;
	struct strm_object *stream_obj = strmres->stream;

	if (!stream_obj) {
		status = -EFAULT;
	} else {
		/* Have all buffers been reclaimed? If not, return
		 * -EPIPE */
		intf_fxns = stream_obj->strm_mgr_obj->intf_fxns;
		status =
		    (*intf_fxns->chnl_get_info) (stream_obj->chnl_obj,
						     &chnl_info_obj);

		if (chnl_info_obj.cio_cs > 0 || chnl_info_obj.cio_reqs > 0)
			status = -EPIPE;
		else
			status = delete_strm(stream_obj);
	}

	if (status)
		goto func_end;

	idr_remove(pr_ctxt->stream_id, strmres->id);
func_end:
	dev_dbg(bridge, "%s: stream_obj: %p, status 0x%x\n", __func__,
		stream_obj, status);
	return status;
}

/*
 *  ======== strm_create ========
 *  Purpose:
 *      Create a STRM manager object.
 */
int strm_create(struct strm_mgr **strm_man,
		       struct dev_object *dev_obj)
{
	struct strm_mgr *strm_mgr_obj;
	int status = 0;

	*strm_man = NULL;
	/* Allocate STRM manager object */
	strm_mgr_obj = kzalloc(sizeof(struct strm_mgr), GFP_KERNEL);
	if (strm_mgr_obj == NULL)
		status = -ENOMEM;
	else
		strm_mgr_obj->dev_obj = dev_obj;

	/* Get Channel manager and Bridge function interface */
	if (!status) {
		status = dev_get_chnl_mgr(dev_obj, &(strm_mgr_obj->chnl_mgr));
		if (!status) {
			(void)dev_get_intf_fxns(dev_obj,
						&(strm_mgr_obj->intf_fxns));
		}
	}

	if (!status)
		*strm_man = strm_mgr_obj;
	else
		kfree(strm_mgr_obj);

	return status;
}

/*
 *  ======== strm_delete ========
 *  Purpose:
 *      Delete the STRM Manager Object.
 */
void strm_delete(struct strm_mgr *strm_mgr_obj)
{
	kfree(strm_mgr_obj);
}

/*
 *  ======== strm_free_buffer ========
 *  Purpose:
 *      Frees the buffers allocated for a stream.
 */
int strm_free_buffer(struct strm_res_object *strmres, u8 ** ap_buffer,
			    u32 num_bufs, struct process_context *pr_ctxt)
{
	int status = 0;
	u32 i = 0;
	struct strm_object *stream_obj = strmres->stream;

	if (!stream_obj)
		status = -EFAULT;

	if (!status) {
		for (i = 0; i < num_bufs; i++) {
			status =
			    cmm_xlator_free_buf(stream_obj->xlator,
						ap_buffer[i]);
			if (status)
				break;
			ap_buffer[i] = NULL;
		}
	}
	drv_proc_update_strm_res(num_bufs - i, strmres);

	return status;
}

/*
 *  ======== strm_get_info ========
 *  Purpose:
 *      Retrieves information about a stream.
 */
int strm_get_info(struct strm_object *stream_obj,
			 struct stream_info *stream_info,
			 u32 stream_info_size)
{
	struct bridge_drv_interface *intf_fxns;
	struct chnl_info chnl_info_obj;
	int status = 0;
	void *virt_base = NULL;	/* NULL if no SM used */

	if (!stream_obj) {
		status = -EFAULT;
	} else {
		if (stream_info_size < sizeof(struct stream_info)) {
			/* size of users info */
			status = -EINVAL;
		}
	}
	if (status)
		goto func_end;

	intf_fxns = stream_obj->strm_mgr_obj->intf_fxns;
	status =
	    (*intf_fxns->chnl_get_info) (stream_obj->chnl_obj,
						  &chnl_info_obj);
	if (status)
		goto func_end;

	if (stream_obj->xlator) {
		/* We have a translator */
		cmm_xlator_info(stream_obj->xlator, (u8 **) &virt_base, 0,
				stream_obj->segment_id, false);
	}
	stream_info->segment_id = stream_obj->segment_id;
	stream_info->strm_mode = stream_obj->strm_mode;
	stream_info->virt_base = virt_base;
	stream_info->user_strm->number_bufs_allowed = stream_obj->num_bufs;
	stream_info->user_strm->number_bufs_in_stream = chnl_info_obj.cio_cs +
	    chnl_info_obj.cio_reqs;
	/* # of bytes transferred since last call to DSPStream_Idle() */
	stream_info->user_strm->number_bytes = chnl_info_obj.bytes_tx;
	stream_info->user_strm->sync_object_handle = chnl_info_obj.event_obj;
	/* Determine stream state based on channel state and info */
	if (chnl_info_obj.state & CHNL_STATEEOS) {
		stream_info->user_strm->ss_stream_state = STREAM_DONE;
	} else {
		if (chnl_info_obj.cio_cs > 0)
			stream_info->user_strm->ss_stream_state = STREAM_READY;
		else if (chnl_info_obj.cio_reqs > 0)
			stream_info->user_strm->ss_stream_state =
			    STREAM_PENDING;
		else
			stream_info->user_strm->ss_stream_state = STREAM_IDLE;

	}
func_end:
	return status;
}

/*
 *  ======== strm_idle ========
 *  Purpose:
 *      Idles a particular stream.
 */
int strm_idle(struct strm_object *stream_obj, bool flush_data)
{
	struct bridge_drv_interface *intf_fxns;
	int status = 0;

	if (!stream_obj) {
		status = -EFAULT;
	} else {
		intf_fxns = stream_obj->strm_mgr_obj->intf_fxns;

		status = (*intf_fxns->chnl_idle) (stream_obj->chnl_obj,
						      stream_obj->timeout,
						      flush_data);
	}

	dev_dbg(bridge, "%s: stream_obj: %p flush_data: 0x%x status: 0x%x\n",
		__func__, stream_obj, flush_data, status);
	return status;
}

/*
 *  ======== strm_issue ========
 *  Purpose:
 *      Issues a buffer on a stream
 */
int strm_issue(struct strm_object *stream_obj, u8 *pbuf, u32 ul_bytes,
		      u32 ul_buf_size, u32 dw_arg)
{
	struct bridge_drv_interface *intf_fxns;
	int status = 0;
	void *tmp_buf = NULL;

	if (!stream_obj) {
		status = -EFAULT;
	} else {
		intf_fxns = stream_obj->strm_mgr_obj->intf_fxns;

		if (stream_obj->segment_id != 0) {
			tmp_buf = cmm_xlator_translate(stream_obj->xlator,
						       (void *)pbuf,
						       CMM_VA2DSPPA);
			if (tmp_buf == NULL)
				status = -ESRCH;

		}
		if (!status) {
			status = (*intf_fxns->chnl_add_io_req)
			    (stream_obj->chnl_obj, pbuf, ul_bytes, ul_buf_size,
			     (u32) tmp_buf, dw_arg);
		}
		if (status == -EIO)
			status = -ENOSR;
	}

	dev_dbg(bridge, "%s: stream_obj: %p pbuf: %p ul_bytes: 0x%x dw_arg:"
		" 0x%x status: 0x%x\n", __func__, stream_obj, pbuf,
		ul_bytes, dw_arg, status);
	return status;
}

/*
 *  ======== strm_open ========
 *  Purpose:
 *      Open a stream for sending/receiving data buffers to/from a task or
 *      XDAIS socket node on the DSP.
 */
int strm_open(struct node_object *hnode, u32 dir, u32 index,
		     struct strm_attr *pattr,
		     struct strm_res_object **strmres,
		     struct process_context *pr_ctxt)
{
	struct strm_mgr *strm_mgr_obj;
	struct bridge_drv_interface *intf_fxns;
	u32 ul_chnl_id;
	struct strm_object *strm_obj = NULL;
	s8 chnl_mode;
	struct chnl_attr chnl_attr_obj;
	int status = 0;
	struct cmm_object *hcmm_mgr = NULL;	/* Shared memory manager hndl */

	void *stream_res;

	*strmres = NULL;
	if (dir != DSP_TONODE && dir != DSP_FROMNODE) {
		status = -EPERM;
	} else {
		/* Get the channel id from the node (set in node_connect()) */
		status = node_get_channel_id(hnode, dir, index, &ul_chnl_id);
	}
	if (!status)
		status = node_get_strm_mgr(hnode, &strm_mgr_obj);

	if (!status) {
		strm_obj = kzalloc(sizeof(struct strm_object), GFP_KERNEL);
		if (strm_obj == NULL) {
			status = -ENOMEM;
		} else {
			strm_obj->strm_mgr_obj = strm_mgr_obj;
			strm_obj->dir = dir;
			strm_obj->strm_state = STREAM_IDLE;
			strm_obj->user_event = pattr->user_event;
			if (pattr->stream_attr_in != NULL) {
				strm_obj->timeout =
				    pattr->stream_attr_in->timeout;
				strm_obj->num_bufs =
				    pattr->stream_attr_in->num_bufs;
				strm_obj->strm_mode =
				    pattr->stream_attr_in->strm_mode;
				strm_obj->segment_id =
				    pattr->stream_attr_in->segment_id;
				strm_obj->buf_alignment =
				    pattr->stream_attr_in->buf_alignment;
				strm_obj->dma_chnl_id =
				    pattr->stream_attr_in->dma_chnl_id;
				strm_obj->dma_priority =
				    pattr->stream_attr_in->dma_priority;
				chnl_attr_obj.uio_reqs =
				    pattr->stream_attr_in->num_bufs;
			} else {
				strm_obj->timeout = DEFAULTTIMEOUT;
				strm_obj->num_bufs = DEFAULTNUMBUFS;
				strm_obj->strm_mode = STRMMODE_PROCCOPY;
				strm_obj->segment_id = 0;	/* local mem */
				strm_obj->buf_alignment = 0;
				strm_obj->dma_chnl_id = 0;
				strm_obj->dma_priority = 0;
				chnl_attr_obj.uio_reqs = DEFAULTNUMBUFS;
			}
			chnl_attr_obj.reserved1 = NULL;
			/* DMA chnl flush timeout */
			chnl_attr_obj.reserved2 = strm_obj->timeout;
			chnl_attr_obj.event_obj = NULL;
			if (pattr->user_event != NULL)
				chnl_attr_obj.event_obj = pattr->user_event;

		}
	}
	if (status)
		goto func_cont;

	if ((pattr->virt_base == NULL) || !(pattr->virt_size > 0))
		goto func_cont;

	/* No System DMA */
	/* Get the shared mem mgr for this streams dev object */
	status = dev_get_cmm_mgr(strm_mgr_obj->dev_obj, &hcmm_mgr);
	if (!status) {
		/*Allocate a SM addr translator for this strm. */
		status = cmm_xlator_create(&strm_obj->xlator, hcmm_mgr, NULL);
		if (!status) {
			/*  Set translators Virt Addr attributes */
			status = cmm_xlator_info(strm_obj->xlator,
						 (u8 **) &pattr->virt_base,
						 pattr->virt_size,
						 strm_obj->segment_id, true);
		}
	}
func_cont:
	if (!status) {
		/* Open channel */
		chnl_mode = (dir == DSP_TONODE) ?
		    CHNL_MODETODSP : CHNL_MODEFROMDSP;
		intf_fxns = strm_mgr_obj->intf_fxns;
		status = (*intf_fxns->chnl_open) (&(strm_obj->chnl_obj),
						      strm_mgr_obj->chnl_mgr,
						      chnl_mode, ul_chnl_id,
						      &chnl_attr_obj);
		if (status) {
			/*
			 * over-ride non-returnable status codes so we return
			 * something documented
			 */
			if (status != -ENOMEM && status !=
			    -EINVAL && status != -EPERM) {
				/*
				 * We got a status that's not return-able.
				 * Assert that we got something we were
				 * expecting (-EFAULT isn't acceptable,
				 * strm_mgr_obj->chnl_mgr better be valid or we
				 * assert here), and then return -EPERM.
				 */
				status = -EPERM;
			}
		}
	}
	if (!status) {
		status = drv_proc_insert_strm_res_element(strm_obj,
							&stream_res, pr_ctxt);
		if (status)
			delete_strm(strm_obj);
		else
			*strmres = (struct strm_res_object *)stream_res;
	} else {
		(void)delete_strm(strm_obj);
	}

	dev_dbg(bridge, "%s: hnode: %p dir: 0x%x index: 0x%x pattr: %p "
		"strmres: %p status: 0x%x\n", __func__,
		hnode, dir, index, pattr, strmres, status);
	return status;
}

/*
 *  ======== strm_reclaim ========
 *  Purpose:
 *      Relcaims a buffer from a stream.
 */
int strm_reclaim(struct strm_object *stream_obj, u8 ** buf_ptr,
			u32 *nbytes, u32 *buff_size, u32 *pdw_arg)
{
	struct bridge_drv_interface *intf_fxns;
	struct chnl_ioc chnl_ioc_obj;
	int status = 0;
	void *tmp_buf = NULL;

	if (!stream_obj) {
		status = -EFAULT;
		goto func_end;
	}
	intf_fxns = stream_obj->strm_mgr_obj->intf_fxns;

	status =
	    (*intf_fxns->chnl_get_ioc) (stream_obj->chnl_obj,
					    stream_obj->timeout,
					    &chnl_ioc_obj);
	if (!status) {
		*nbytes = chnl_ioc_obj.byte_size;
		if (buff_size)
			*buff_size = chnl_ioc_obj.buf_size;

		*pdw_arg = chnl_ioc_obj.arg;
		if (!CHNL_IS_IO_COMPLETE(chnl_ioc_obj)) {
			if (CHNL_IS_TIMED_OUT(chnl_ioc_obj)) {
				status = -ETIME;
			} else {
				/* Allow reclaims after idle to succeed */
				if (!CHNL_IS_IO_CANCELLED(chnl_ioc_obj))
					status = -EPERM;

			}
		}
		/* Translate zerocopy buffer if channel not canceled. */
		if (!status
		    && (!CHNL_IS_IO_CANCELLED(chnl_ioc_obj))
		    && (stream_obj->strm_mode == STRMMODE_ZEROCOPY)) {
			/*
			 *  This is a zero-copy channel so chnl_ioc_obj.buf
			 *  contains the DSP address of SM. We need to
			 *  translate it to a virtual address for the user
			 *  thread to access.
			 *  Note: Could add CMM_DSPPA2VA to CMM in the future.
			 */
			tmp_buf = cmm_xlator_translate(stream_obj->xlator,
						       chnl_ioc_obj.buf,
						       CMM_DSPPA2PA);
			if (tmp_buf != NULL) {
				/* now convert this GPP Pa to Va */
				tmp_buf = cmm_xlator_translate(stream_obj->
							       xlator,
							       tmp_buf,
							       CMM_PA2VA);
			}
			if (tmp_buf == NULL)
				status = -ESRCH;

			chnl_ioc_obj.buf = tmp_buf;
		}
		*buf_ptr = chnl_ioc_obj.buf;
	}
func_end:
	dev_dbg(bridge, "%s: stream_obj: %p buf_ptr: %p nbytes: %p "
		"pdw_arg: %p status 0x%x\n", __func__, stream_obj,
		buf_ptr, nbytes, pdw_arg, status);
	return status;
}

/*
 *  ======== strm_register_notify ========
 *  Purpose:
 *      Register to be notified on specific events for this stream.
 */
int strm_register_notify(struct strm_object *stream_obj, u32 event_mask,
				u32 notify_type, struct dsp_notification
				* hnotification)
{
	struct bridge_drv_interface *intf_fxns;
	int status = 0;

	if (!stream_obj) {
		status = -EFAULT;
	} else if ((event_mask & ~((DSP_STREAMIOCOMPLETION) |
				   DSP_STREAMDONE)) != 0) {
		status = -EINVAL;
	} else {
		if (notify_type != DSP_SIGNALEVENT)
			status = -ENOSYS;

	}
	if (!status) {
		intf_fxns = stream_obj->strm_mgr_obj->intf_fxns;

		status =
		    (*intf_fxns->chnl_register_notify) (stream_obj->
							    chnl_obj,
							    event_mask,
							    notify_type,
							    hnotification);
	}

	return status;
}

/*
 *  ======== strm_select ========
 *  Purpose:
 *      Selects a ready stream.
 */
int strm_select(struct strm_object **strm_tab, u32 strms,
		       u32 *pmask, u32 utimeout)
{
	u32 index;
	struct chnl_info chnl_info_obj;
	struct bridge_drv_interface *intf_fxns;
	struct sync_object **sync_events = NULL;
	u32 i;
	int status = 0;

	*pmask = 0;
	for (i = 0; i < strms; i++) {
		if (!strm_tab[i]) {
			status = -EFAULT;
			break;
		}
	}
	if (status)
		goto func_end;

	/* Determine which channels have IO ready */
	for (i = 0; i < strms; i++) {
		intf_fxns = strm_tab[i]->strm_mgr_obj->intf_fxns;
		status = (*intf_fxns->chnl_get_info) (strm_tab[i]->chnl_obj,
							  &chnl_info_obj);
		if (status) {
			break;
		} else {
			if (chnl_info_obj.cio_cs > 0)
				*pmask |= (1 << i);

		}
	}
	if (!status && utimeout > 0 && *pmask == 0) {
		/* Non-zero timeout */
		sync_events = kmalloc(strms * sizeof(struct sync_object *),
								GFP_KERNEL);

		if (sync_events == NULL) {
			status = -ENOMEM;
		} else {
			for (i = 0; i < strms; i++) {
				intf_fxns =
				    strm_tab[i]->strm_mgr_obj->intf_fxns;
				status = (*intf_fxns->chnl_get_info)
				    (strm_tab[i]->chnl_obj, &chnl_info_obj);
				if (status)
					break;
				else
					sync_events[i] =
					    chnl_info_obj.sync_event;

			}
		}
		if (!status) {
			status =
			    sync_wait_on_multiple_events(sync_events, strms,
							 utimeout, &index);
			if (!status) {
				/* Since we waited on the event, we have to
				 * reset it */
				sync_set_event(sync_events[index]);
				*pmask = 1 << index;
			}
		}
	}
func_end:
	kfree(sync_events);

	return status;
}

/*
 *  ======== delete_strm ========
 *  Purpose:
 *      Frees the resources allocated for a stream.
 */
static int delete_strm(struct strm_object *stream_obj)
{
	struct bridge_drv_interface *intf_fxns;
	int status = 0;

	if (stream_obj) {
		if (stream_obj->chnl_obj) {
			intf_fxns = stream_obj->strm_mgr_obj->intf_fxns;
			/* Channel close can fail only if the channel handle
			 * is invalid. */
			status = (*intf_fxns->chnl_close)
					(stream_obj->chnl_obj);
		}
		/* Free all SM address translator resources */
		kfree(stream_obj->xlator);
		kfree(stream_obj);
	} else {
		status = -EFAULT;
	}
	return status;
}