/* Copyright (c) 2011, 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.
 */

/* Resource management for the SPS device driver. */

#include <linux/types.h>	/* u32 */
#include <linux/kernel.h>	/* pr_info() */
#include <linux/mutex.h>	/* mutex */
#include <linux/list.h>		/* list_head */
#include <linux/slab.h>		/* kzalloc() */
#include <linux/memory.h>	/* memset */

#include "spsi.h"
#include "sps_core.h"

/* "Clear" value for the connection parameter struct */
#define SPSRM_CLEAR     0xcccccccc

/* Max BAM FIFO sizes */
#define SPSRM_MAX_DESC_FIFO_SIZE    0xffff
#define SPSRM_MAX_DATA_FIFO_SIZE    0xffff

/* Connection control struct pointer */
static struct sps_rm *sps_rm;

/**
 * Initialize resource manager module
 */
int sps_rm_init(struct sps_rm *rm, u32 options)
{
	/* Set the resource manager state struct pointer */
	sps_rm = rm;

	/* Initialize the state struct */
	INIT_LIST_HEAD(&sps_rm->connections_q);
	mutex_init(&sps_rm->lock);

	return 0;
}

/**
 * Initialize client state context
 *
 */
void sps_rm_config_init(struct sps_connect *connect)
{
	memset(connect, SPSRM_CLEAR, sizeof(*connect));
}

/**
 * Remove reference to connection mapping
 *
 * This function removes a reference from a connection mapping struct.
 *
 * @map - pointer to connection mapping struct
 *
 */
static void sps_rm_remove_ref(struct sps_connection *map)
{
	/* Free this connection */
	map->refs--;
	if (map->refs <= 0) {
		if (map->client_src != NULL || map->client_dest != NULL)
			SPS_ERR("Failed to allocate connection struct");

		list_del(&map->list);
		kfree(map);
	}
}

/**
 * Compare map to connect parameters
 *
 * This function compares client connect parameters to an allocated
 * connection mapping.
 *
 * @pipe - client context for SPS connection end point
 *
 * @return - true if match, false otherwise
 *
 */
static int sps_rm_map_match(const struct sps_connect *cfg,
			    const struct sps_connection *map)
{
	if (cfg->source != map->src.dev ||
	    cfg->destination != map->dest.dev)
		return false;

	if (cfg->src_pipe_index != SPSRM_CLEAR &&
	    cfg->src_pipe_index != map->src.pipe_index)
		return false;

	if (cfg->dest_pipe_index != SPSRM_CLEAR &&
	    cfg->dest_pipe_index != map->dest.pipe_index)
		return false;

	if (cfg->config != map->config)
		return false;

	if (cfg->desc.size != SPSRM_CLEAR) {
		if (cfg->desc.size != map->desc.size)
			return false;

		if (cfg->desc.phys_base != SPSRM_CLEAR &&
		    cfg->desc.base != (void *)SPSRM_CLEAR &&
		    (cfg->desc.phys_base != map->desc.phys_base ||
		     cfg->desc.base != map->desc.base)) {
			return false;
		}
	}

	if (cfg->data.size != SPSRM_CLEAR) {
		if (cfg->data.size != map->data.size)
			return false;

		if (cfg->data.phys_base != SPSRM_CLEAR &&
		    cfg->data.base != (void *)SPSRM_CLEAR &&
		    (cfg->data.phys_base != map->data.phys_base ||
		     cfg->data.base != map->data.base))
			return false;
	}

	return true;
}

/**
 * Find unconnected mapping
 *
 * This function finds an allocated a connection mapping.
 *
 * @pipe - client context for SPS connection end point
 *
 * @return - pointer to allocated connection mapping, or NULL if not found
 *
 */
static struct sps_connection *find_unconnected(struct sps_pipe *pipe)
{
	struct sps_connect *cfg = &pipe->connect;
	struct sps_connection *map;

	/* Has this connection already been allocated? */
	list_for_each_entry(map, &sps_rm->connections_q, list) {
		if (sps_rm_map_match(cfg, map))
			if ((cfg->mode == SPS_MODE_SRC
			     && map->client_src == NULL)
			    || (cfg->mode != SPS_MODE_SRC
				&& map->client_dest == NULL))
				return map;	/* Found */
	}

	return NULL;		/* Not Found */
}

/**
 * Assign connection to client
 *
 * This function assigns a connection to a client.
 *
 * @pipe - client context for SPS connection end point
 *
 * @map - connection mapping
 *
 * @return 0 on success, negative value on error
 *
 */
static int sps_rm_assign(struct sps_pipe *pipe,
			 struct sps_connection *map)
{
	struct sps_connect *cfg = &pipe->connect;

	/* Check ownership and BAM */
	if ((cfg->mode == SPS_MODE_SRC && map->client_src != NULL) ||
	    (cfg->mode != SPS_MODE_SRC && map->client_dest != NULL))
		/* The end point is already connected */
		return SPS_ERROR;

	/* Check whether this end point is a BAM (not memory) */
	if ((cfg->mode == SPS_MODE_SRC && map->src.bam == NULL) ||
	    (cfg->mode != SPS_MODE_SRC && map->dest.bam == NULL))
		return SPS_ERROR;

	/* Record the connection assignment */
	if (cfg->mode == SPS_MODE_SRC) {
		map->client_src = pipe;
		pipe->bam = map->src.bam;
		pipe->pipe_index = map->src.pipe_index;
		if (pipe->connect.event_thresh != SPSRM_CLEAR)
			map->src.event_threshold = pipe->connect.event_thresh;
	} else {
		map->client_dest = pipe;
		pipe->bam = map->dest.bam;
		pipe->pipe_index = map->dest.pipe_index;
		if (pipe->connect.event_thresh != SPSRM_CLEAR)
			map->dest.event_threshold =
			pipe->connect.event_thresh;
	}
	pipe->map = map;

	SPS_DBG("sps:sps_rm_assign.bam 0x%x.pipe_index=%d\n",
			BAM_ID(pipe->bam), pipe->pipe_index);

	/* Copy parameters to client connect state */
	pipe->connect.src_pipe_index = map->src.pipe_index;
	pipe->connect.dest_pipe_index = map->dest.pipe_index;
	pipe->connect.desc = map->desc;
	pipe->connect.data = map->data;

	pipe->client_state = SPS_STATE_ALLOCATE;

	return 0;
}

/**
 * Free connection mapping resources
 *
 * This function frees a connection mapping resources.
 *
 * @pipe - client context for SPS connection end point
 *
 */
static void sps_rm_free_map_rsrc(struct sps_connection *map)
{
	struct sps_bam *bam;

	if (map->client_src != NULL || map->client_dest != NULL)
		return;

	if (map->alloc_src_pipe != SPS_BAM_PIPE_INVALID) {
		bam = map->src.bam;
		sps_bam_pipe_free(bam, map->src.pipe_index);

		/* Is this a BAM-DMA pipe? */
#ifdef CONFIG_SPS_SUPPORT_BAMDMA
		if ((bam->props.options & SPS_BAM_OPT_BAMDMA))
			/* Deallocate and free the BAM-DMA channel */
			sps_dma_pipe_free(bam, map->src.pipe_index);
#endif
		map->alloc_src_pipe = SPS_BAM_PIPE_INVALID;
		map->src.pipe_index = SPS_BAM_PIPE_INVALID;
	}
	if (map->alloc_dest_pipe != SPS_BAM_PIPE_INVALID) {
		bam = map->dest.bam;
		sps_bam_pipe_free(bam, map->dest.pipe_index);

		/* Is this a BAM-DMA pipe? */
#ifdef CONFIG_SPS_SUPPORT_BAMDMA
		if ((bam->props.options & SPS_BAM_OPT_BAMDMA)) {
			/* Deallocate the BAM-DMA channel */
			sps_dma_pipe_free(bam, map->dest.pipe_index);
		}
#endif
		map->alloc_dest_pipe = SPS_BAM_PIPE_INVALID;
		map->dest.pipe_index = SPS_BAM_PIPE_INVALID;
	}
	if (map->alloc_desc_base != SPS_ADDR_INVALID) {
		sps_mem_free_io(map->alloc_desc_base, map->desc.size);

		map->alloc_desc_base = SPS_ADDR_INVALID;
		map->desc.phys_base = SPS_ADDR_INVALID;
	}
	if (map->alloc_data_base != SPS_ADDR_INVALID) {
		sps_mem_free_io(map->alloc_data_base, map->data.size);

		map->alloc_data_base = SPS_ADDR_INVALID;
		map->data.phys_base = SPS_ADDR_INVALID;
	}
}

/**
 * Init connection mapping from client connect
 *
 * This function initializes a connection mapping from the client's
 * connect parameters.
 *
 * @map - connection mapping struct
 *
 * @cfg - client connect parameters
 *
 * @return - pointer to allocated connection mapping, or NULL on error
 *
 */
static void sps_rm_init_map(struct sps_connection *map,
			    const struct sps_connect *cfg)
{
	/* Clear the connection mapping struct */
	memset(map, 0, sizeof(*map));
	map->desc.phys_base = SPS_ADDR_INVALID;
	map->data.phys_base = SPS_ADDR_INVALID;
	map->alloc_desc_base = SPS_ADDR_INVALID;
	map->alloc_data_base = SPS_ADDR_INVALID;
	map->alloc_src_pipe = SPS_BAM_PIPE_INVALID;
	map->alloc_dest_pipe = SPS_BAM_PIPE_INVALID;

	/* Copy client required parameters */
	map->src.dev = cfg->source;
	map->dest.dev = cfg->destination;
	map->desc.size = cfg->desc.size;
	map->data.size = cfg->data.size;
	map->config = cfg->config;

	/* Did client specify descriptor FIFO? */
	if (map->desc.size != SPSRM_CLEAR &&
	    cfg->desc.phys_base != SPSRM_CLEAR &&
	    cfg->desc.base != (void *)SPSRM_CLEAR)
		map->desc = cfg->desc;

	/* Did client specify data FIFO? */
	if (map->data.size != SPSRM_CLEAR &&
	    cfg->data.phys_base != SPSRM_CLEAR &&
	    cfg->data.base != (void *)SPSRM_CLEAR)
		map->data = cfg->data;

	/* Did client specify source pipe? */
	if (cfg->src_pipe_index != SPSRM_CLEAR)
		map->src.pipe_index = cfg->src_pipe_index;
	else
		map->src.pipe_index = SPS_BAM_PIPE_INVALID;


	/* Did client specify destination pipe? */
	if (cfg->dest_pipe_index != SPSRM_CLEAR)
		map->dest.pipe_index = cfg->dest_pipe_index;
	else
		map->dest.pipe_index = SPS_BAM_PIPE_INVALID;
}

/**
 * Create a new connection mapping
 *
 * This function creates a new connection mapping.
 *
 * @pipe - client context for SPS connection end point
 *
 * @return - pointer to allocated connection mapping, or NULL on error
 *
 */
static struct sps_connection *sps_rm_create(struct sps_pipe *pipe)
{
	struct sps_connection *map;
	struct sps_bam *bam;
	u32 desc_size;
	u32 data_size;
	enum sps_mode dir;
	int success = false;

	/* Allocate new connection */
	map = kzalloc(sizeof(*map), GFP_KERNEL);
	if (map == NULL) {
		SPS_ERR("Failed to allocate connection struct");
		return NULL;
	}

	/* Initialize connection struct */
	sps_rm_init_map(map, &pipe->connect);
	dir = pipe->connect.mode;

	/* Use a do/while() loop to avoid a "goto" */
	success = false;
	/* Get BAMs */
	map->src.bam = sps_h2bam(map->src.dev);
	if (map->src.bam == NULL) {
		if (map->src.dev != SPS_DEV_HANDLE_MEM) {
			SPS_ERR("Invalid BAM handle: 0x%x", map->src.dev);
			goto exit_err;
		}
		map->src.pipe_index = SPS_BAM_PIPE_INVALID;
	}
	map->dest.bam = sps_h2bam(map->dest.dev);
	if (map->dest.bam == NULL) {
		if (map->dest.dev != SPS_DEV_HANDLE_MEM) {
			SPS_ERR("Invalid BAM handle: 0x%x", map->dest.dev);
			goto exit_err;
		}
		map->dest.pipe_index = SPS_BAM_PIPE_INVALID;
	}

	/* Check the BAM device for the pipe */
	if ((dir == SPS_MODE_SRC && map->src.bam == NULL) ||
	    (dir != SPS_MODE_SRC && map->dest.bam == NULL)) {
		SPS_ERR("Invalid BAM endpt: dir %d src 0x%x dest 0x%x",
			dir, map->src.dev, map->dest.dev);
		goto exit_err;
	}

	/* Allocate pipes and copy BAM parameters */
	if (map->src.bam != NULL) {
		/* Allocate the pipe */
		bam = map->src.bam;
		map->alloc_src_pipe = sps_bam_pipe_alloc(bam,
							map->src.pipe_index);
		if (map->alloc_src_pipe == SPS_BAM_PIPE_INVALID)
			goto exit_err;
		map->src.pipe_index = map->alloc_src_pipe;

		/* Is this a BAM-DMA pipe? */
#ifdef CONFIG_SPS_SUPPORT_BAMDMA
		if ((bam->props.options & SPS_BAM_OPT_BAMDMA)) {
			int rc;
			/* Allocate the BAM-DMA channel */
			rc = sps_dma_pipe_alloc(bam, map->src.pipe_index,
						 SPS_MODE_SRC);
			if (rc) {
				SPS_ERR("Failed to alloc BAM-DMA pipe: %d",
					map->src.pipe_index);
				goto exit_err;
			}
		}
#endif
		map->src.bam_phys = bam->props.phys_addr;
		map->src.event_threshold = bam->props.event_threshold;
	}
	if (map->dest.bam != NULL) {
		/* Allocate the pipe */
		bam = map->dest.bam;
		map->alloc_dest_pipe = sps_bam_pipe_alloc(bam,
							 map->dest.pipe_index);
		if (map->alloc_dest_pipe == SPS_BAM_PIPE_INVALID)
			goto exit_err;

		map->dest.pipe_index = map->alloc_dest_pipe;

		/* Is this a BAM-DMA pipe? */
#ifdef CONFIG_SPS_SUPPORT_BAMDMA
		if ((bam->props.options & SPS_BAM_OPT_BAMDMA)) {
			int rc;
			/* Allocate the BAM-DMA channel */
			rc = sps_dma_pipe_alloc(bam, map->dest.pipe_index,
					       SPS_MODE_DEST);
			if (rc) {
				SPS_ERR("Failed to alloc BAM-DMA pipe: %d",
					map->dest.pipe_index);
				goto exit_err;
			}
		}
#endif
		map->dest.bam_phys = bam->props.phys_addr;
		map->dest.event_threshold =
		bam->props.event_threshold;
	}

	/* Get default FIFO sizes */
	desc_size = 0;
	data_size = 0;
	if (map->src.bam != NULL) {
		bam = map->src.bam;
		desc_size = bam->props.desc_size;
		data_size = bam->props.data_size;
	}
	if (map->dest.bam != NULL) {
		bam = map->dest.bam;
		if (bam->props.desc_size > desc_size)
			desc_size = bam->props.desc_size;
		if (bam->props.data_size > data_size)
			data_size = bam->props.data_size;
	}

	/* Set FIFO sizes */
	if (map->desc.size == SPSRM_CLEAR)
		map->desc.size = desc_size;
	if (map->src.bam != NULL && map->dest.bam != NULL) {
		/* BAM-to-BAM requires data FIFO */
		if (map->data.size == SPSRM_CLEAR)
			map->data.size = data_size;
	} else {
		map->data.size = 0;
	}
	if (map->desc.size > SPSRM_MAX_DESC_FIFO_SIZE) {
		SPS_ERR("Invalid desc FIFO size: 0x%x",	map->desc.size);
		goto exit_err;
	}
	if (map->src.bam != NULL && map->dest.bam != NULL &&
	    map->data.size > SPSRM_MAX_DATA_FIFO_SIZE) {
		SPS_ERR("Invalid data FIFO size: 0x%x",	map->data.size);
		goto exit_err;
	}

	/* Allocate descriptor FIFO if necessary */
	if (map->desc.size && map->desc.phys_base == SPS_ADDR_INVALID) {
		map->alloc_desc_base = sps_mem_alloc_io(map->desc.size);
		if (map->alloc_desc_base == SPS_ADDR_INVALID) {
			SPS_ERR("I/O memory allocation failure: 0x%x",
				map->desc.size);
			goto exit_err;
		}
		map->desc.phys_base = map->alloc_desc_base;
		map->desc.base = spsi_get_mem_ptr(map->desc.phys_base);
		if (map->desc.base == NULL) {
			SPS_ERR("Cannot get virt addr for I/O buffer: 0x%x",
				map->desc.phys_base);
			goto exit_err;
		}
	}

	/* Allocate data FIFO if necessary */
	if (map->data.size && map->data.phys_base == SPS_ADDR_INVALID) {
		map->alloc_data_base = sps_mem_alloc_io(map->data.size);
		if (map->alloc_data_base == SPS_ADDR_INVALID) {
			SPS_ERR("I/O memory allocation failure: 0x%x",
				map->data.size);
			goto exit_err;
		}
		map->data.phys_base = map->alloc_data_base;
		map->data.base = spsi_get_mem_ptr(map->data.phys_base);
		if (map->data.base == NULL) {
			SPS_ERR("Cannot get virt addr for I/O buffer: 0x%x",
				map->data.phys_base);
			goto exit_err;
		}
	}

	/* Attempt to assign this connection to the client */
	if (sps_rm_assign(pipe, map))
		goto exit_err;

	/* Initialization was successful */
	success = true;
exit_err:

	/* If initialization failed, free resources */
	if (!success) {
		sps_rm_free_map_rsrc(map);
		kfree(map);
		return NULL;
	}

	return map;
}

/**
 * Free connection mapping
 *
 * This function frees a connection mapping.
 *
 * @pipe - client context for SPS connection end point
 *
 * @return 0 on success, negative value on error
 *
 */
static int sps_rm_free(struct sps_pipe *pipe)
{
	struct sps_connection *map = (void *)pipe->map;
	struct sps_connect *cfg = &pipe->connect;

	mutex_lock(&sps_rm->lock);

	/* Free this connection */
	if (cfg->mode == SPS_MODE_SRC)
		map->client_src = NULL;
	else
		map->client_dest = NULL;

	pipe->map = NULL;
	pipe->client_state = SPS_STATE_DISCONNECT;
	sps_rm_free_map_rsrc(map);

	sps_rm_remove_ref(map);

	mutex_unlock(&sps_rm->lock);

	return 0;
}

/**
 * Allocate an SPS connection end point
 *
 * This function allocates resources and initializes a BAM connection.
 *
 * @pipe - client context for SPS connection end point
 *
 * @return 0 on success, negative value on error
 *
 */
static int sps_rm_alloc(struct sps_pipe *pipe)
{
	struct sps_connection *map;
	int result = SPS_ERROR;

	if (pipe->connect.sps_reserved != SPSRM_CLEAR) {
		/*
		 * Client did not call sps_get_config()	to init
		 * struct sps_connect, so only use legacy members.
		 */
		u32 source = pipe->connect.source;
		u32 destination = pipe->connect.destination;
		enum sps_mode mode = pipe->connect.mode;
		u32 config = pipe->connect.config;
		memset(&pipe->connect, SPSRM_CLEAR,
			      sizeof(pipe->connect));
		pipe->connect.source = source;
		pipe->connect.destination = destination;
		pipe->connect.mode = mode;
		pipe->connect.config = config;
	}
	if (pipe->connect.config == SPSRM_CLEAR)
		pipe->connect.config = SPS_CONFIG_DEFAULT;

	/*
	 *  If configuration is not default, then client is specifying a
	 * connection mapping.  Find a matching mapping, or fail.
	 * If a match is found, the client's Connect struct will be updated
	 * with all the mapping's values.
	 */
	if (pipe->connect.config != SPS_CONFIG_DEFAULT) {
		if (sps_map_find(&pipe->connect)) {
			SPS_ERR("Failed to find connection mapping");
			return SPS_ERROR;
		}
	}

	mutex_lock(&sps_rm->lock);
	/* Check client state */
	if (IS_SPS_STATE_OK(pipe)) {
		SPS_ERR("Client connection already allocated");
		goto exit_err;
	}

	/* Are the connection resources already allocated? */
	map = find_unconnected(pipe);
	if (map != NULL) {
		/* Attempt to assign this connection to the client */
		if (sps_rm_assign(pipe, map))
			/* Assignment failed, so must allocate new */
			map = NULL;
	}

	/* Allocate a new connection if necessary */
	if (map == NULL) {
		map = sps_rm_create(pipe);
		if (map == NULL) {
			SPS_ERR("Failed to allocate connection");
			goto exit_err;
		}
		list_add_tail(&map->list, &sps_rm->connections_q);
	}

	/* Add the connection to the allocated queue */
	map->refs++;

	/* Initialization was successful */
	result = 0;
exit_err:
	mutex_unlock(&sps_rm->lock);

	if (result)
		return SPS_ERROR;

	return 0;
}

/**
 * Disconnect an SPS connection end point
 *
 * This function frees resources and de-initializes a BAM connection.
 *
 * @pipe - client context for SPS connection end point
 *
 * @return 0 on success, negative value on error
 *
 */
static int sps_rm_disconnect(struct sps_pipe *pipe)
{
	sps_rm_free(pipe);
	return 0;
}

/**
 * Process connection state change
 *
 * This function processes a connection state change.
 *
 * @pipe - pointer to client context
 *
 * @state - new state for connection
 *
 * @return 0 on success, negative value on error
 *
 */
int sps_rm_state_change(struct sps_pipe *pipe, u32 state)
{
	int auto_enable = false;
	int result;

	/* Allocate the pipe */
	if (pipe->client_state == SPS_STATE_DISCONNECT &&
	    state == SPS_STATE_ALLOCATE) {
		if (sps_rm_alloc(pipe))
			return SPS_ERROR;
	}

	/* Configure the pipe */
	if (pipe->client_state == SPS_STATE_ALLOCATE &&
	    state == SPS_STATE_CONNECT) {
		/* Connect the BAM pipe */
		struct sps_bam_connect_param params;
		memset(&params, 0, sizeof(params));
		params.mode = pipe->connect.mode;
		if (pipe->connect.options != SPSRM_CLEAR) {
			params.options = pipe->connect.options;
			params.irq_gen_addr = pipe->connect.irq_gen_addr;
			params.irq_gen_data = pipe->connect.irq_gen_data;
		}
		result = sps_bam_pipe_connect(pipe, &params);
		if (result) {
			SPS_ERR("Failed to connect BAM 0x%x pipe %d",
				(u32) pipe->bam, pipe->pipe_index);
			return SPS_ERROR;
		}
		pipe->client_state = SPS_STATE_CONNECT;

		/* Set auto-enable for system-mode connections */
		if (pipe->connect.source == SPS_DEV_HANDLE_MEM ||
		    pipe->connect.destination == SPS_DEV_HANDLE_MEM) {
			if (pipe->map->desc.size != 0 &&
			    pipe->map->desc.phys_base != SPS_ADDR_INVALID)
				auto_enable = true;
		}
	}

	/* Enable the pipe data flow */
	if (pipe->client_state == SPS_STATE_CONNECT &&
	    !(state == SPS_STATE_DISABLE
	      || state == SPS_STATE_DISCONNECT)
	    && (state == SPS_STATE_ENABLE || auto_enable
		|| (pipe->connect.options & SPS_O_AUTO_ENABLE))) {
		result = sps_bam_pipe_enable(pipe->bam, pipe->pipe_index);
		if (result) {
			SPS_ERR("Failed to set BAM 0x%x pipe %d flow on",
				pipe->bam->props.phys_addr,
				pipe->pipe_index);
			return SPS_ERROR;
		}

		/* Is this a BAM-DMA pipe? */
#ifdef CONFIG_SPS_SUPPORT_BAMDMA
		if ((pipe->bam->props.options & SPS_BAM_OPT_BAMDMA)) {
			/* Activate the BAM-DMA channel */
			result = sps_dma_pipe_enable(pipe->bam,
						     pipe->pipe_index);
			if (result) {
				SPS_ERR("Failed to activate BAM-DMA pipe: %d",
					pipe->pipe_index);
				return SPS_ERROR;
			}
		}
#endif
		pipe->client_state = SPS_STATE_ENABLE;
	}

	/* Disable the pipe data flow */
	if (pipe->client_state == SPS_STATE_ENABLE &&
	    (state == SPS_STATE_DISABLE	|| state == SPS_STATE_DISCONNECT)) {
		result = sps_bam_pipe_disable(pipe->bam, pipe->pipe_index);
		if (result) {
			SPS_ERR("Failed to set BAM 0x%x pipe %d flow off",
				pipe->bam->props.phys_addr,
				pipe->pipe_index);
			return SPS_ERROR;
		}
		pipe->client_state = SPS_STATE_CONNECT;
	}

	/* Disconnect the BAM pipe */
	if (pipe->client_state == SPS_STATE_CONNECT &&
	    state == SPS_STATE_DISCONNECT) {
		struct sps_connection *map;
		u32 pipe_index;

		if (pipe->connect.mode == SPS_MODE_SRC)
			pipe_index = pipe->map->src.pipe_index;
		else
			pipe_index = pipe->map->dest.pipe_index;


		result = sps_bam_pipe_disconnect(pipe->bam, pipe_index);
		if (result) {
			SPS_ERR("Failed to disconnect BAM 0x%x pipe %d",
				pipe->bam->props.phys_addr,
				pipe->pipe_index);
			return SPS_ERROR;
		}

		/* Clear map state */
		map = (void *)pipe->map;
		if (pipe->connect.mode == SPS_MODE_SRC)
			map->client_src = NULL;
		else if (pipe->connect.mode == SPS_MODE_DEST)
			map->client_dest = NULL;

		sps_rm_disconnect(pipe);

		/* Clear the client state */
		pipe->map = NULL;
		pipe->bam = NULL;
		pipe->client_state = SPS_STATE_DISCONNECT;
	}

	return 0;
}
