lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [day] [month] [year] [list]
Message-ID: <AANLkTi=LDAym0Ef+_ObakQhRXTzLHadw6vmuMRVuhu0-@mail.gmail.com>
Date:	Fri, 24 Sep 2010 15:51:19 +0200
From:	Par-Gunnar Hjalmdahl <par-gunnar.p.hjalmdahl@...ricsson.com>
To:	linux-bluetooth@...r.kernel.org, linux-kernel@...r.kernel.org,
	linus.walleij@...ricsson.com, Pavan Savoy <pavan_savoy@...y.com>
Subject: [PATCH 5/6] This patch adds support for controlling the audio paths

This patch adds support for controlling the audio paths
 within the ST-Ericsson CG2900 connectivity controller.
 This patch registers as a user into the CG2900 framework
 for the bt_audio and fm_audio channels. It contains an API
 for controlling the audio paths that can also be reached from
 User space using a char device.

Signed-off-by: Par-Gunnar Hjalmdahl <par-gunnar.p.hjalmdahl@...ricsson.com>
---
 drivers/mfd/Kconfig               |    7 +
 drivers/mfd/cg2900/Makefile       |    2 +
 drivers/mfd/cg2900/cg2900_audio.c | 2475 +++++++++++++++++++++++++++++++++++++
 include/linux/mfd/cg2900_audio.h  |  583 +++++++++
 4 files changed, 3067 insertions(+), 0 deletions(-)
 create mode 100644 drivers/mfd/cg2900/cg2900_audio.c
 create mode 100644 include/linux/mfd/cg2900_audio.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 2888358..7856fd5 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -603,6 +603,13 @@ config MFD_CG2900_UART
 	  Support for UART as transport for ST-Ericsson CG2900 Connectivity
 	  Controller

+config MFD_CG2900_AUDIO
+	tristate "Support CG2900 audio interface"
+	depends on MFD_CG2900
+	help
+	  Support for ST-Ericsson CG2900 Connectivity audio interface. Gives a
+	  module the ability to setup audio paths within the CG2900 controller.
+
 endif # MFD_SUPPORT

 menu "Multimedia Capabilities Port drivers"
diff --git a/drivers/mfd/cg2900/Makefile b/drivers/mfd/cg2900/Makefile
index 02b7c25..3a6672b 100644
--- a/drivers/mfd/cg2900/Makefile
+++ b/drivers/mfd/cg2900/Makefile
@@ -10,3 +10,5 @@ obj-$(CONFIG_MFD_CG2900_CHIP)       += cg2900_chip.o
 obj-$(CONFIG_MFD_STLC2690_CHIP)     += stlc2690_chip.o

 obj-$(CONFIG_MFD_CG2900_UART)       += cg2900_uart.o
+
+obj-$(CONFIG_MFD_CG2900_AUDIO)      += cg2900_audio.o
diff --git a/drivers/mfd/cg2900/cg2900_audio.c
b/drivers/mfd/cg2900/cg2900_audio.c
new file mode 100644
index 0000000..bcb0680
--- /dev/null
+++ b/drivers/mfd/cg2900/cg2900_audio.c
@@ -0,0 +1,2475 @@
+/*
+ * drivers/mfd/cg2900/cg2900_audio.c
+ *
+ * Copyright (C) ST-Ericsson SA 2010
+ * Authors:
+ * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@...ricsson.com) for
ST-Ericsson.
+ * Kjell Andersson (kjell.k.andersson@...ricsson.com) for ST-Ericsson.
+ * License terms:  GNU General Public License (GPL), version 2
+ *
+ * Linux Bluetooth Audio Driver for ST-Ericsson CG2900 controller.
+ */
+
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/types.h>
+#include <linux/poll.h>
+#include <linux/cdev.h>
+#include <linux/mfd/cg2900.h>
+#include <linux/mfd/cg2900_audio.h>
+#include <linux/miscdevice.h>
+#include <linux/mutex.h>
+#include <linux/device.h>
+#include <linux/skbuff.h>
+#include <linux/list.h>
+#include <linux/sched.h>
+
+#include "cg2900_debug.h"
+#include "hci_defines.h"
+#include "cg2900_chip.h"
+
+/* Char device op codes */
+#define OP_CODE_SET_DAI_CONF			0x01
+#define OP_CODE_GET_DAI_CONF			0x02
+#define OP_CODE_CONFIGURE_ENDPOINT		0x03
+#define OP_CODE_START_STREAM			0x04
+#define OP_CODE_STOP_STREAM			0x05
+
+/* Device names */
+#define DEVICE_NAME				"cg2900_audio"
+
+/* Type of channel used */
+#define BT_CHANNEL_USED				0x00
+#define FM_CHANNEL_USED				0x01
+
+#define MAX_NBR_OF_USERS			10
+#define FIRST_USER				1
+
+#define SET_RESP_STATE(__state_var, __new_state) \
+	CG2900_SET_STATE("resp_state", __state_var, __new_state)
+
+#define DEFAULT_SCO_HANDLE			0x0008
+
+/* Use a timeout of 5 seconds when waiting for a command response */
+#define RESP_TIMEOUT				5000
+
+/**
+ * enum chip_resp_state - State when communicating with the CG2900 controller.
+ * @IDLE:		No outstanding packets to the controller.
+ * @WAITING:		Packet has been sent to the controller. Waiting for
+ *			response.
+ * @RESP_RECEIVED:	Response from controller has been received but not yet
+ *			handled.
+ */
+enum chip_resp_state {
+	IDLE,
+	WAITING,
+	RESP_RECEIVED
+};
+
+/**
+ * enum main_state - Main state for the CG2900 Audio driver.
+ * @OPENED:	Audio driver has registered to CG2900 Core.
+ * @CLOSED:	Audio driver is not registered to CG2900 Core.
+ * @RESET:	A reset of CG2900 Core has occurred and no user has re-opened
+ *		the audio driver.
+ */
+enum main_state {
+	OPENED,
+	CLOSED,
+	RESET
+};
+
+/**
+ * struct char_dev_info - CG2900 character device info structure.
+ * @session:		Stored session for the char device.
+ * @stored_data:	Data returned when executing last command, if any.
+ * @stored_data_len:	Length of @stored_data in bytes.
+ * @management_mutex:	Mutex for handling access to char dev management.
+ * @rw_mutex:		Mutex for handling access to char dev writes and reads.
+ */
+struct char_dev_info {
+	int		session;
+	u8		*stored_data;
+	int		stored_data_len;
+	struct mutex	management_mutex;
+	struct mutex	rw_mutex;
+};
+
+/**
+ * struct audio_user - CG2900 audio user info structure.
+ * @session:	Stored session for the char device.
+ * @resp_state:	State for controller communications.
+ */
+struct audio_user {
+	int			session;
+	enum chip_resp_state	resp_state;
+};
+
+/**
+ * struct endpoint_list - List for storing endpoint configuration nodes.
+ * @ep_list:		Pointer to first node in list.
+ * @management_mutex:	Mutex for handling access to list.
+ */
+struct endpoint_list {
+	struct list_head	ep_list;
+	struct mutex		management_mutex;
+};
+
+/**
+ * struct endpoint_config_node - Node for storing endpoint configuration.
+ * @list:		list_head struct.
+ * @endpoint_id:	Endpoint ID.
+ * @config:		Stored configuration for this endpoint.
+ */
+struct endpoint_config_node {
+	struct list_head			list;
+	enum cg2900_audio_endpoint_id		endpoint_id;
+	union cg2900_endpoint_config_union	config;
+};
+
+/**
+ * struct audio_info - Main CG2900 Audio driver info structure.
+ * @state:			Current state of the CG2900 Audio driver.
+ * @dev:			The misc device created by this driver.
+ * @dev_bt:			CG2900 Core device registered by this driver for
+ *				the BT audio channel.
+ * @dev_fm:			CG2900 Core device registered by this driver for
+ *				the FM audio channel.
+ * @management_mutex:		Mutex for handling access to CG2900 Audio driver
+ *				management.
+ * @bt_mutex:			Mutex for handling access to BT audio channel.
+ * @fm_mutex:			Mutex for handling access to FM audio channel.
+ * @nbr_of_users_active:	Number of sessions open in the CG2900 Audio
+ *				driver.
+ * @bt_queue:			Received BT events.
+ * @fm_queue:			Received FM events.
+ * @audio_sessions:		Pointers to currently opened sessions (maps
+ *				session ID to user info).
+ * @i2s_config:			DAI I2S configuration.
+ * @i2s_pcm_config:		DAI PCM_I2S configuration.
+ * @i2s_config_known:		@true if @i2s_config has been set,
+ *				@false otherwise.
+ * @i2s_pcm_config_known:	@true if @i2s_pcm_config has been set,
+ *				@false otherwise.
+ * @endpoints:			List containing the endpoint configurations.
+ */
+struct audio_info {
+	enum main_state			state;
+	struct miscdevice		dev;
+	struct cg2900_device		*dev_bt;
+	struct cg2900_device		*dev_fm;
+	struct mutex			management_mutex;
+	struct mutex			bt_mutex;
+	struct mutex			fm_mutex;
+	int				nbr_of_users_active;
+	struct sk_buff_head		bt_queue;
+	struct sk_buff_head		fm_queue;
+	struct audio_user		*audio_sessions[MAX_NBR_OF_USERS];
+	struct cg2900_dai_conf_i2s	i2s_config;
+	struct cg2900_dai_conf_i2s_pcm	i2s_pcm_config;
+	bool				i2s_config_known;
+	bool				i2s_pcm_config_known;
+	struct endpoint_list		endpoints;
+};
+
+/**
+ * struct audio_cb_info - Callback info structure registered in @user_data.
+ * @channel:	Stores if this device handles BT or FM events.
+ * @user:	Audio user currently awaiting data on the channel.
+ */
+struct audio_cb_info {
+	int			channel;
+	struct audio_user	*user;
+};
+
+/* cg2900_audio wait queues */
+static DECLARE_WAIT_QUEUE_HEAD(wq_bt);
+static DECLARE_WAIT_QUEUE_HEAD(wq_fm);
+
+static struct audio_info *audio_info;
+
+static struct audio_cb_info cb_info_bt = {
+	.channel = BT_CHANNEL_USED,
+	.user = NULL
+};
+static struct audio_cb_info cb_info_fm = {
+	.channel = FM_CHANNEL_USED,
+	.user = NULL
+};
+
+/*
+ *	Internal helper functions
+ */
+
+/**
+ * read_cb() - Handle data received from STE connectivity driver.
+ * @dev:	Device receiving data.
+ * @skb:	Buffer with data coming form device.
+ */
+static void read_cb(struct cg2900_device *dev, struct sk_buff *skb)
+{
+	struct audio_cb_info *cb_info;
+
+	CG2900_INFO("CG2900 Audio: read_cb");
+
+	if (!dev) {
+		CG2900_ERR("NULL supplied as dev");
+		return;
+	}
+
+	if (!skb) {
+		CG2900_ERR("NULL supplied as skb");
+		return;
+	}
+
+	cb_info = (struct audio_cb_info *)dev->user_data;
+	if (!cb_info) {
+		CG2900_ERR("NULL supplied as cb_info");
+		return;
+	}
+	if (!(cb_info->user)) {
+		CG2900_ERR("NULL supplied as cb_info->user");
+		return;
+	}
+
+	/* Mark that packet has been received */
+	SET_RESP_STATE(cb_info->user->resp_state, RESP_RECEIVED);
+
+	/* Handle packet depending on channel */
+	if (cb_info->channel == BT_CHANNEL_USED) {
+		skb_queue_tail(&(audio_info->bt_queue), skb);
+		wake_up_interruptible(&wq_bt);
+	} else if (cb_info->channel == FM_CHANNEL_USED) {
+		skb_queue_tail(&(audio_info->fm_queue), skb);
+		wake_up_interruptible(&wq_fm);
+	} else {
+		/* Unhandled channel; free the packet */
+		CG2900_ERR("Received callback on bad channel %d",
+			   cb_info->channel);
+		kfree_skb(skb);
+	}
+}
+
+/**
+ * reset_cb() - Reset callback function.
+ * @dev:	CPD device reseting.
+ */
+static void reset_cb(struct cg2900_device *dev)
+{
+	CG2900_INFO("CG2900 Audio: reset_cb");
+	mutex_lock(&audio_info->management_mutex);
+	audio_info->nbr_of_users_active = 0;
+	audio_info->state = RESET;
+	mutex_unlock(&audio_info->management_mutex);
+}
+
+static struct cg2900_callbacks cg2900_cb = {
+	.read_cb = read_cb,
+	.reset_cb = reset_cb
+};
+
+/**
+ * get_session_user() - Check that supplied session is within valid range.
+ * @session:	Session ID.
+ *
+ * Returns:
+ *   Audio_user if there is no error.
+ *   NULL for bad session ID.
+ */
+static struct audio_user *get_session_user(int session)
+{
+	struct audio_user *audio_user;
+
+	if (session < FIRST_USER || session >= MAX_NBR_OF_USERS) {
+		CG2900_ERR("Calling with invalid session %d", session);
+		return NULL;
+	}
+
+	audio_user = audio_info->audio_sessions[session];
+	if (!audio_user)
+		CG2900_ERR("Calling with non-opened session %d", session);
+	return audio_user;
+}
+
+/**
+ * del_endpoint_private() - Deletes an endpoint from @list.
+ * @endpoint_id:	Endpoint ID.
+ * @list:		List of endpoints.
+ *
+ * Deletes an endpoint from the supplied endpoint list.
+ * This function is not protected by any semaphore.
+ */
+static void del_endpoint_private(enum cg2900_audio_endpoint_id endpoint_id,
+				 struct endpoint_list *list)
+{
+	struct list_head *cursor, *next;
+	struct endpoint_config_node *tmp;
+
+	list_for_each_safe(cursor, next, &(list->ep_list)) {
+		tmp = list_entry(cursor, struct endpoint_config_node, list);
+		if (tmp->endpoint_id == endpoint_id) {
+			list_del(cursor);
+			kfree(tmp);
+		}
+	}
+}
+
+/**
+ * add_endpoint() - Add endpoint node to @list.
+ * @ep_config:	Endpoint configuration.
+ * @list:	List of endpoints.
+ *
+ * Add endpoint node to the supplied list and copies supplied config to node.
+ * If a node already exists for the supplied endpoint, the old node is removed
+ * and replaced by the new node.
+ */
+static void add_endpoint(struct cg2900_endpoint_config *ep_config,
+			 struct endpoint_list *list)
+{
+	struct endpoint_config_node *item;
+
+	item = kzalloc(sizeof(*item), GFP_KERNEL);
+	if (!item) {
+		CG2900_ERR("Failed to alloc memory!");
+		return;
+	}
+
+	/* Store values */
+	item->endpoint_id = ep_config->endpoint_id;
+	memcpy(&(item->config), &(ep_config->config), sizeof(item->config));
+
+	mutex_lock(&(list->management_mutex));
+
+	/*
+	 * Check if endpoint ID already exist in list.
+	 * If that is the case, remove it.
+	 */
+	if (!list_empty(&(list->ep_list)))
+		del_endpoint_private(ep_config->endpoint_id, list);
+
+	list_add_tail(&(item->list), &(list->ep_list));
+
+	mutex_unlock(&(list->management_mutex));
+}
+
+/**
+ * find_endpoint() - Finds endpoint identified by @endpoint_id in @list.
+ * @endpoint_id:	Endpoint ID.
+ * @list:		List of endpoints.
+ *
+ * Returns:
+ *   Endpoint configuration if there is no error.
+ *   NULL if no configuration can be found for @endpoint_id.
+ */
+static union cg2900_endpoint_config_union *
+find_endpoint(enum cg2900_audio_endpoint_id endpoint_id,
+	      struct endpoint_list *list)
+{
+	struct list_head *cursor, *next;
+	struct endpoint_config_node *tmp;
+	struct endpoint_config_node *ret_ep = NULL;
+
+	mutex_lock(&list->management_mutex);
+	list_for_each_safe(cursor, next, &(list->ep_list)) {
+		tmp = list_entry(cursor, struct endpoint_config_node, list);
+		if (tmp->endpoint_id == endpoint_id) {
+			ret_ep = tmp;
+			break;
+		}
+	}
+	mutex_unlock(&list->management_mutex);
+
+	if (ret_ep)
+		return &(ret_ep->config);
+	else
+		return NULL;
+}
+
+/**
+ * flush_endpoint_list() - Deletes all stored endpoints in @list.
+ * @list:	List of endpoints.
+ */
+static void flush_endpoint_list(struct endpoint_list *list)
+{
+	struct list_head *cursor, *next;
+	struct endpoint_config_node *tmp;
+
+	mutex_lock(&list->management_mutex);
+	list_for_each_safe(cursor, next, &(list->ep_list)) {
+		tmp = list_entry(cursor, struct endpoint_config_node, list);
+		list_del(cursor);
+		kfree(tmp);
+	}
+	mutex_unlock(&list->management_mutex);
+}
+
+/**
+ * receive_fm_legacy_response() - Wait for and handle an FM Legacy response.
+ * @audio_user:	Audio user to check for.
+ * @rsp_lsb:	LSB of FM command to wait for.
+ * @rsp_msb:	MSB of FM command to wait for.
+ *
+ * This function first waits for FM response (up to 5 seconds) and when one
+ * arrives, it checks that it is the one we are waiting for and also that no
+ * error has occurred.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -ECOMM if no response was received.
+ *   -EIO for other errors.
+ */
+static int receive_fm_legacy_response(struct audio_user *audio_user,
+				      u8 rsp_lsb, u8 rsp_msb)
+{
+	int err = 0;
+	struct sk_buff *skb;
+	u8 status;
+	u8 fm_function;
+	u8 fm_rsp_hdr_lsb;
+	u8 fm_rsp_hdr_msb;
+
+	/*
+	 * Wait for callback to receive command complete and then wake us up
+	 * again.
+	 */
+	if (0 >= wait_event_interruptible_timeout(wq_fm,
+					audio_user->resp_state == RESP_RECEIVED,
+					msecs_to_jiffies(RESP_TIMEOUT))) {
+		/* We timed out or an error occurred */
+		CG2900_ERR("Error occurred while waiting for return packet.");
+		return -ECOMM;
+	}
+
+	/* OK, now we should have received answer. Let's check it. */
+	skb = skb_dequeue_tail(&audio_info->fm_queue);
+	if (!skb) {
+		CG2900_ERR("No skb in queue when it should be there");
+		return -EIO;
+	}
+
+	/* Check if we received the correct event */
+	if (CG2900_FM_GEN_ID_LEGACY !=
+			skb->data[CG2900_FM_USER_GEN_ID_POS_CMD_CMPL]) {
+		CG2900_ERR("Received unknown FM packet. 0x%X %X %X %X %X",
+			   skb->data[0], skb->data[1], skb->data[2],
+			   skb->data[3], skb->data[4]);
+		err = -EIO;
+		goto error_handling_free_skb;
+	}
+
+	/* FM Legacy Command complete event */
+	status = skb->data[CG2900_FM_USER_LEG_STATUS_POS_CMD_CMPL];
+	fm_function = skb->data[CG2900_FM_USER_LEG_FUNC_POS_CMD_CMPL];
+	fm_rsp_hdr_lsb = skb->data[CG2900_FM_USER_LEG_HDR_POS_CMD_CMPL];
+	fm_rsp_hdr_msb = skb->data[CG2900_FM_USER_LEG_HDR_POS_CMD_CMPL + 1];
+
+	if (fm_function != CG2900_FM_CMD_PARAM_WRITECOMMAND ||
+	    fm_rsp_hdr_lsb != rsp_lsb || fm_rsp_hdr_msb != rsp_msb) {
+		CG2900_ERR("Received unexpected packet func 0x%X "
+			   "cmd 0x%02X%02X",
+			   fm_function, fm_rsp_hdr_msb, fm_rsp_hdr_lsb);
+		err = -EIO;
+		goto error_handling_free_skb;
+	}
+
+	if (status != CG2900_FM_CMD_STATUS_COMMAND_SUCCEEDED) {
+		CG2900_ERR("FM Command failed (%d)", status);
+		err = -EIO;
+		goto error_handling_free_skb;
+	}
+	/* Operation succeeded. We are now done */
+
+error_handling_free_skb:
+	kfree_skb(skb);
+	return err;
+}
+
+/**
+ * receive_bt_cmd_complete() - Wait for and handle an BT Command
Complete event.
+ * @audio_user:	Audio user to check for.
+ * @rsp_lsb:	LSB of BT command to wait for.
+ * @rsp_msb:	MSB of BT command to wait for.
+ * @data:	Pointer to buffer if any received data should be stored (except
+ *		status).
+ * @data_len:	Length of @data in bytes.
+ *
+ * This function first waits for BT Command Complete event (up to 5 seconds)
+ * and when one arrives, it checks that it is the one we are waiting for and
+ * also that no error has occurred.
+ * If @data is supplied it also copies received data into @data.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -ECOMM if no response was received.
+ *   -EIO for other errors.
+ */
+static int receive_bt_cmd_complete(struct audio_user *audio_user, u8 rsp_lsb,
+				   u8 rsp_msb, void *data, int data_len)
+{
+	int err = 0;
+	struct sk_buff *skb;
+	u8 evt_id;
+	u8 op_lsb;
+	u8 op_msb;
+	u8 status;
+
+	/*
+	 * Wait for callback to receive command complete and then wake us up
+	 * again.
+	 */
+	if (0 >= wait_event_interruptible_timeout(wq_bt,
+					audio_user->resp_state == RESP_RECEIVED,
+					msecs_to_jiffies(RESP_TIMEOUT))) {
+		/* We timed out or an error occurred */
+		CG2900_ERR("Error occurred while waiting for return packet.");
+		return -ECOMM;
+	}
+
+	/* OK, now we should have received answer. Let's check it. */
+	skb = skb_dequeue_tail(&audio_info->bt_queue);
+	if (!skb) {
+		CG2900_ERR("No skb in queue when it should be there");
+		return -EIO;
+	}
+
+	evt_id = skb->data[HCI_BT_EVT_ID_POS];
+	if (evt_id != HCI_BT_EVT_CMD_COMPLETE) {
+		CG2900_ERR("We did not receive the event we expected (0x%X)",
+			   evt_id);
+		err = -EIO;
+		goto error_handling_free_skb;
+	}
+
+	op_lsb = skb->data[HCI_BT_EVT_CMD_COMPL_ID_POS];
+	op_msb = skb->data[HCI_BT_EVT_CMD_COMPL_ID_POS + 1];
+	status = skb->data[HCI_BT_EVT_CMD_COMPL_STATUS_POS];
+
+	if (rsp_lsb != op_lsb || rsp_msb != op_msb) {
+		CG2900_ERR("Received cmd complete for unexpected command: "
+			   "0x%02X%02X", op_msb, op_lsb);
+		err = -EIO;
+		goto error_handling_free_skb;
+	}
+
+	if (status != HCI_BT_ERROR_NO_ERROR) {
+		CG2900_ERR("Received command complete with err %d", status);
+		err = -EIO;
+		goto error_handling_free_skb;
+	}
+
+	/* Copy the rest of the parameters if a buffer has been supplied.
+	 * The caller must have set the length correctly.
+	 */
+	if (data)
+		memcpy(data, &(skb->data[HCI_BT_EVT_CMD_COMPL_STATUS_POS + 1]),
+		       data_len);
+
+	/* Operation succeeded. We are now done */
+
+error_handling_free_skb:
+	kfree_skb(skb);
+	return err;
+}
+
+/**
+ * conn_start_i2s_to_fm_rx() - Start an audio stream connecting FM RX to I2S.
+ * @audio_user:		Audio user to check for.
+ * @stream_handle:	[out] Pointer where to store the stream handle.
+ *
+ * This function sets up an FM RX to I2S stream.
+ * It does this by first setting the output mode and then the configuration of
+ * the External Sample Rate Converter.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -ECOMM if no response was received.
+ *   -ENOMEM upon allocation errors.
+ *   Errors from @cg2900_write and @receive_fm_legacy_response.
+ *   -EIO for other errors.
+ */
+static int conn_start_i2s_to_fm_rx(struct audio_user *audio_user,
+				   unsigned int *stream_handle)
+{
+	int err = 0;
+	u8 temp_session_id;
+	union cg2900_endpoint_config_union *fm_config;
+	struct sk_buff *skb;
+	u8 *data;
+
+	fm_config = find_endpoint(ENDPOINT_FM_RX,
+				  &(audio_info->endpoints));
+	if (!fm_config) {
+		CG2900_ERR("FM RX not configured before stream start");
+		return -EIO;
+	}
+
+	if (!(audio_info->i2s_config_known)) {
+		CG2900_ERR("I2S DAI not configured before stream start");
+		return -EIO;
+	}
+
+	/*
+	 * Use mutex to assure that only ONE command is sent at any time on each
+	 * channel.
+	 */
+	mutex_lock(&audio_info->fm_mutex);
+	mutex_lock(&audio_info->bt_mutex);
+
+	/*
+	 * Now set the output mode of the External Sample Rate Converter by
+	 * sending HCI_Write command with AUP_EXT_SetMode.
+	 */
+	CG2900_DBG("FM: AUP_EXT_SetMode");
+
+	skb = cg2900_alloc_skb(CG2900_FM_CMD_LEN_AUP_EXT_SET_MODE, GFP_KERNEL);
+	if (!skb) {
+		CG2900_ERR("Could not allocate skb");
+		err = -ENOMEM;
+		goto finished_unlock_mutex;
+	}
+
+	/* Enter data into the skb */
+	data = skb_put(skb, CG2900_FM_CMD_LEN_AUP_EXT_SET_MODE);
+
+	data[0] = CG2900_FM_CMD_PARAM_LEN_AUP_EXT_SET_MODE;
+	data[1] = CG2900_FM_GEN_ID_LEGACY;
+	data[2] = CG2900_FM_CMD_LEG_PARAM_WRITE;
+	data[3] = CG2900_FM_CMD_PARAM_WRITECOMMAND;
+	data[4] = CG2900_FM_CMD_ID_AUP_EXT_SET_MODE_LSB;
+	data[5] = CG2900_FM_CMD_ID_AUP_EXT_SET_MODE_MSB;
+	data[6] = HCI_SET_U16_DATA_LSB(CG2900_FM_CMD_AUP_EXT_SET_MODE_PARALLEL);
+	data[7] = HCI_SET_U16_DATA_MSB(CG2900_FM_CMD_AUP_EXT_SET_MODE_PARALLEL);
+
+	cb_info_fm.user = audio_user;
+	SET_RESP_STATE(audio_user->resp_state, WAITING);
+
+	/* Send packet to controller */
+	err = cg2900_write(audio_info->dev_fm, skb);
+	if (err) {
+		CG2900_ERR("Error occurred while transmitting skb (%d)", err);
+		goto error_handling_free_skb;
+	}
+
+	err = receive_fm_legacy_response(audio_user,
+					 CG2900_FM_RSP_ID_AUP_EXT_SET_MODE_LSB,
+					 CG2900_FM_RSP_ID_AUP_EXT_SET_MODE_MSB);
+	if (err)
+		goto finished_unlock_mutex;
+
+	SET_RESP_STATE(audio_user->resp_state, IDLE);
+
+	/*
+	 * Now configure the External Sample Rate Converter by sending
+	 * HCI_Write command with AUP_EXT_SetControl.
+	 */
+	CG2900_DBG("FM: AUP_EXT_SetControl");
+
+	skb = cg2900_alloc_skb(CG2900_FM_CMD_LEN_AUP_EXT_SET_CTRL, GFP_KERNEL);
+	if (!skb) {
+		CG2900_ERR("Could not allocate skb");
+		err = -ENOMEM;
+		goto finished_unlock_mutex;
+	}
+
+	/* Enter data into the skb */
+	data = skb_put(skb, CG2900_FM_CMD_LEN_AUP_EXT_SET_CTRL);
+
+	data[0] = CG2900_FM_CMD_PARAM_LEN_AUP_EXT_SET_CTRL;
+	data[1] = CG2900_FM_GEN_ID_LEGACY;
+	data[2] = CG2900_FM_CMD_LEG_PARAM_WRITE;
+	data[3] = CG2900_FM_CMD_PARAM_WRITECOMMAND;
+	data[4] = CG2900_FM_CMD_ID_AUP_EXT_SET_CTRL_LSB;
+	data[5] = CG2900_FM_CMD_ID_AUP_EXT_SET_CTRL_MSB;
+	if (fm_config->fm.sample_rate >= ENDPOINT_SAMPLE_RATE_44_1_KHZ) {
+		data[6] = HCI_SET_U16_DATA_LSB(CG2900_FM_CMD_SET_CTRL_CONV_UP);
+		data[7] = HCI_SET_U16_DATA_MSB(CG2900_FM_CMD_SET_CTRL_CONV_UP);
+	} else {
+		data[6] = HCI_SET_U16_DATA_LSB(
+				CG2900_FM_CMD_SET_CTRL_CONV_DOWN);
+		data[7] = HCI_SET_U16_DATA_MSB(
+				CG2900_FM_CMD_SET_CTRL_CONV_DOWN);
+	}
+
+	cb_info_fm.user = audio_user;
+	SET_RESP_STATE(audio_user->resp_state, WAITING);
+
+	/* Send packet to controller */
+	err = cg2900_write(audio_info->dev_fm, skb);
+	if (err) {
+		CG2900_ERR("Error occurred while transmitting skb (%d)", err);
+		goto error_handling_free_skb;
+	}
+
+	err = receive_fm_legacy_response(audio_user,
+					 CG2900_FM_RSP_ID_AUP_EXT_SET_CTRL_LSB,
+					 CG2900_FM_RSP_ID_AUP_EXT_SET_CTRL_MSB);
+	if (err)
+		goto finished_unlock_mutex;
+
+	/*
+	 * Now send HCI_VS_Set_Session_Configuration command
+	 */
+	CG2900_DBG("BT: HCI_VS_Set_Session_Configuration");
+
+	skb = cg2900_alloc_skb(CG2900_BT_LEN_VS_SET_SESSION_CONFIG, GFP_KERNEL);
+	if (!skb) {
+		CG2900_ERR("Could not allocate skb");
+		err = -ENOMEM;
+		goto finished_unlock_mutex;
+	}
+
+	/* Enter data into the skb */
+	data = skb_put(skb, CG2900_BT_LEN_VS_SET_SESSION_CONFIG);
+	/*
+	 * Default all bytes to 0 so we don't have to set reserved bytes below.
+	 */
+	memset(data, 0, CG2900_BT_LEN_VS_SET_SESSION_CONFIG);
+
+	data[0] = CG2900_BT_VS_SET_SESSION_CONFIG_LSB;
+	data[1] = CG2900_BT_VS_SET_SESSION_CONFIG_MSB;
+	data[2] = CG2900_BT_PARAM_LEN_VS_SET_SESSION_CONFIG;
+	data[3] = 1; /* Number of streams */
+	data[4] = CG2900_BT_SESSION_MEDIA_TYPE_AUDIO;
+	/* Media configuration: Set sample rate and audio channel setup */
+	data[5] = CG2900_BT_SESSION_CONF_SET_SAMPLE_RATE(
+			fm_config->fm.sample_rate);
+	if (audio_info->i2s_config.channel_sel == CHANNEL_SELECTION_BOTH)
+		data[5] |= CG2900_BT_MEDIA_CONFIG_STEREO;
+	else
+		data[5] |= CG2900_BT_MEDIA_CONFIG_MONO;
+	/* data[6] - data[10] SBC codec params (not used for FM RX) */
+	/* Input Virtual Port (VP) configuration */
+	data[11] = CG2900_BT_VP_TYPE_FM;
+	/* data[12] - data[23] reserved */
+	/* Output Virtual Port (VP) configuration */
+	data[24] = CG2900_BT_VP_TYPE_I2S;
+	data[25] = CG2900_BT_SESSION_I2S_INDEX_I2S;
+	data[26] = audio_info->i2s_config.channel_sel;
+	/* data[27] - data[36] reserved */
+
+	cb_info_bt.user = audio_user;
+	SET_RESP_STATE(audio_user->resp_state, WAITING);
+
+	/* Send packet to controller */
+	err = cg2900_write(audio_info->dev_bt, skb);
+	if (err) {
+		CG2900_ERR("Error occurred while transmitting skb (%d)", err);
+		goto error_handling_free_skb;
+	}
+
+	err = receive_bt_cmd_complete(audio_user,
+			CG2900_BT_VS_SET_SESSION_CONFIG_LSB,
+			CG2900_BT_VS_SET_SESSION_CONFIG_MSB,
+			&temp_session_id, CG2900_BT_PARAM_LEN_SESSION_ID);
+	if (err)
+		goto finished_unlock_mutex;
+
+	/* Store the stream handle (used for start and stop stream) */
+	*stream_handle = temp_session_id;
+	CG2900_DBG("stream_handle set to %d", *stream_handle);
+
+	SET_RESP_STATE(audio_user->resp_state, IDLE);
+
+	/*
+	 * Now start the stream by sending HCI_VS_Session_Control command
+	 */
+	CG2900_DBG("BT: HCI_VS_Session_Control");
+
+	skb = cg2900_alloc_skb(CG2900_BT_LEN_VS_SESSION_CTRL, GFP_KERNEL);
+	if (!skb) {
+		CG2900_ERR("Could not allocate skb");
+		err = -ENOMEM;
+		goto finished_unlock_mutex;
+	}
+
+	/* Enter data into the skb */
+	data = skb_put(skb, CG2900_BT_LEN_VS_SESSION_CTRL);
+
+	data[0] = CG2900_BT_VS_SESSION_CTRL_LSB;
+	data[1] = CG2900_BT_VS_SESSION_CTRL_MSB;
+	data[2] = CG2900_BT_PARAM_LEN_VS_SESSION_CTRL;
+	data[3] = (u8)(*stream_handle); /* Session ID */
+	data[4] = CG2900_BT_SESSION_START;
+
+	cb_info_bt.user = audio_user;
+	SET_RESP_STATE(audio_user->resp_state, WAITING);
+
+	/* Send packet to controller */
+	err = cg2900_write(audio_info->dev_bt, skb);
+	if (err) {
+		CG2900_ERR("Error occurred while transmitting skb (%d)", err);
+		goto error_handling_free_skb;
+	}
+
+	err = receive_bt_cmd_complete(audio_user,
+				      CG2900_BT_VS_SESSION_CTRL_LSB,
+				      CG2900_BT_VS_SESSION_CTRL_MSB,
+				      NULL, 0);
+
+	goto finished_unlock_mutex;
+
+error_handling_free_skb:
+	kfree_skb(skb);
+finished_unlock_mutex:
+	SET_RESP_STATE(audio_user->resp_state, IDLE);
+	mutex_unlock(&audio_info->bt_mutex);
+	mutex_unlock(&audio_info->fm_mutex);
+	return err;
+}
+
+/**
+ * conn_start_i2s_to_fm_tx() - Start an audio stream connecting FM TX to I2S.
+ * @audio_user:		Audio user to check for.
+ * @stream_handle:	[out] Pointer where to store the stream handle.
+ *
+ * This function sets up an I2S to FM TX stream.
+ * It does this by first setting the Audio Input source and then setting the
+ * configuration and input source of BT sample rate converter.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -ECOMM if no response was received.
+ *   -ENOMEM upon allocation errors.
+ *   Errors from @cg2900_write and @receive_fm_legacy_response.
+ *   -EIO for other errors.
+ */
+static int conn_start_i2s_to_fm_tx(struct audio_user *audio_user,
+				   unsigned int *stream_handle)
+{
+	int err = 0;
+	u8 temp_session_id;
+	union cg2900_endpoint_config_union *fm_config;
+	struct sk_buff *skb;
+	u8 *data;
+
+	fm_config = find_endpoint(ENDPOINT_FM_TX, &(audio_info->endpoints));
+	if (!fm_config) {
+		CG2900_ERR("FM TX not configured before stream start");
+		return -EIO;
+	}
+
+	if (!(audio_info->i2s_config_known)) {
+		CG2900_ERR("I2S DAI not configured before stream start");
+		return -EIO;
+	}
+
+	/*
+	 * Use mutex to assure that only ONE command is sent at any time on each
+	 * channel.
+	 */
+	mutex_lock(&audio_info->fm_mutex);
+	mutex_lock(&audio_info->bt_mutex);
+
+	/*
+	 * Select Audio Input Source by sending HCI_Write command with
+	 * AIP_SetMode.
+	 */
+	CG2900_DBG("FM: AIP_SetMode");
+
+	skb = cg2900_alloc_skb(CG2900_FM_CMD_LEN_AIP_SET_MODE, GFP_KERNEL);
+	if (!skb) {
+		CG2900_ERR("Could not allocate skb");
+		err = -ENOMEM;
+		goto finished_unlock_mutex;
+	}
+
+	/* Enter data into the skb */
+	data = skb_put(skb, CG2900_FM_CMD_LEN_AIP_SET_MODE);
+
+	data[0] = CG2900_FM_CMD_PARAM_LEN_AIP_SET_MODE;
+	data[1] = CG2900_FM_GEN_ID_LEGACY;
+	data[2] = CG2900_FM_CMD_LEG_PARAM_WRITE;
+	data[3] = CG2900_FM_CMD_PARAM_WRITECOMMAND;
+	data[4] = CG2900_FM_CMD_ID_AIP_SET_MODE_LSB;
+	data[5] = CG2900_FM_CMD_ID_AIP_SET_MODE_MSB;
+	data[6] = HCI_SET_U16_DATA_LSB(CG2900_FM_CMD_AIP_SET_MODE_INPUT_DIG);
+	data[7] = HCI_SET_U16_DATA_MSB(CG2900_FM_CMD_AIP_SET_MODE_INPUT_DIG);
+
+	cb_info_fm.user = audio_user;
+	SET_RESP_STATE(audio_user->resp_state, WAITING);
+
+	/* Send packet to controller */
+	err = cg2900_write(audio_info->dev_fm, skb);
+	if (err) {
+		CG2900_ERR("Error occurred while transmitting skb (%d)", err);
+		goto error_handling_free_skb;
+	}
+
+	err = receive_fm_legacy_response(audio_user,
+					 CG2900_FM_RSP_ID_AIP_SET_MODE_LSB,
+					 CG2900_FM_RSP_ID_AIP_SET_MODE_MSB);
+	if (err)
+		goto finished_unlock_mutex;
+
+	SET_RESP_STATE(audio_user->resp_state, IDLE);
+
+	/*
+	 * Now configure the BT sample rate converter by sending HCI_Write
+	 * command with AIP_BT_SetControl.
+	 */
+	CG2900_DBG("FM: AIP_BT_SetControl");
+
+	skb = cg2900_alloc_skb(CG2900_FM_CMD_LEN_AIP_BT_SET_CTRL, GFP_KERNEL);
+	if (!skb) {
+		CG2900_ERR("Could not allocate skb");
+		err = -ENOMEM;
+		goto finished_unlock_mutex;
+	}
+
+	/* Enter data into the skb */
+	data = skb_put(skb, CG2900_FM_CMD_LEN_AIP_BT_SET_CTRL);
+
+	data[0] = CG2900_FM_CMD_PARAM_LEN_AIP_BT_SET_CTRL;
+	data[1] = CG2900_FM_GEN_ID_LEGACY;
+	data[2] = CG2900_FM_CMD_LEG_PARAM_WRITE;
+	data[3] = CG2900_FM_CMD_PARAM_WRITECOMMAND;
+	data[4] = CG2900_FM_CMD_ID_AIP_BT_SET_CTRL_LSB;
+	data[5] = CG2900_FM_CMD_ID_AIP_BT_SET_CTRL_MSB;
+	if (fm_config->fm.sample_rate >= ENDPOINT_SAMPLE_RATE_44_1_KHZ) {
+		data[6] = HCI_SET_U16_DATA_LSB(CG2900_FM_CMD_SET_CTRL_CONV_UP);
+		data[7] = HCI_SET_U16_DATA_MSB(CG2900_FM_CMD_SET_CTRL_CONV_UP);
+	} else {
+		data[6] = HCI_SET_U16_DATA_LSB(
+				CG2900_FM_CMD_SET_CTRL_CONV_DOWN);
+		data[7] = HCI_SET_U16_DATA_MSB(
+				CG2900_FM_CMD_SET_CTRL_CONV_DOWN);
+	}
+
+	cb_info_fm.user = audio_user;
+	SET_RESP_STATE(audio_user->resp_state, WAITING);
+
+	/* Send packet to controller */
+	err = cg2900_write(audio_info->dev_fm, skb);
+	if (err) {
+		CG2900_ERR("Error occurred while transmitting skb (%d)", err);
+		goto error_handling_free_skb;
+	}
+
+	err = receive_fm_legacy_response(audio_user,
+					 CG2900_FM_RSP_ID_AIP_BT_SET_CTRL_LSB,
+					 CG2900_FM_RSP_ID_AIP_BT_SET_CTRL_MSB);
+	if (err)
+		goto finished_unlock_mutex;
+
+	SET_RESP_STATE(audio_user->resp_state, IDLE);
+
+
+	/*
+	 * Now set input of the BT sample rate converter by sending HCI_Write
+	 * command with AIP_BT_SetMode.
+	 */
+	CG2900_DBG("FM: AIP_BT_SetMode");
+
+	skb = cg2900_alloc_skb(CG2900_FM_CMD_LEN_AIP_BT_SET_MODE, GFP_KERNEL);
+	if (!skb) {
+		CG2900_ERR("Could not allocate skb");
+		err = -ENOMEM;
+		goto finished_unlock_mutex;
+	}
+
+	/* Enter data into the skb */
+	data = skb_put(skb, CG2900_FM_CMD_LEN_AIP_BT_SET_MODE);
+
+	data[0] = CG2900_FM_CMD_PARAM_LEN_AIP_BT_SET_MODE;
+	data[1] = CG2900_FM_GEN_ID_LEGACY;
+	data[2] = CG2900_FM_CMD_LEG_PARAM_WRITE;
+	data[3] = CG2900_FM_CMD_PARAM_WRITECOMMAND;
+	data[4] = CG2900_FM_CMD_ID_AIP_BT_SET_MODE_LSB;
+	data[5] = CG2900_FM_CMD_ID_AIP_BT_SET_MODE_MSB;
+	data[6] = HCI_SET_U16_DATA_LSB(CG2900_FM_CMD_AIP_BT_SET_MODE_INPUT_PAR);
+	data[7] = HCI_SET_U16_DATA_MSB(CG2900_FM_CMD_AIP_BT_SET_MODE_INPUT_PAR);
+
+	cb_info_fm.user = audio_user;
+	SET_RESP_STATE(audio_user->resp_state, WAITING);
+
+	/* Send packet to controller */
+	err = cg2900_write(audio_info->dev_fm, skb);
+	if (err) {
+		CG2900_ERR("Error occurred while transmitting skb (%d)", err);
+		goto error_handling_free_skb;
+	}
+
+	err = receive_fm_legacy_response(audio_user,
+					 CG2900_FM_RSP_ID_AIP_BT_SET_MODE_LSB,
+					 CG2900_FM_RSP_ID_AIP_BT_SET_MODE_MSB);
+	if (err)
+		goto finished_unlock_mutex;
+
+	/*
+	 * Now send HCI_VS_Set_Session_Configuration command
+	 */
+	CG2900_DBG("BT: HCI_VS_Set_Session_Configuration");
+
+	skb = cg2900_alloc_skb(CG2900_BT_LEN_VS_SET_SESSION_CONFIG, GFP_KERNEL);
+	if (!skb) {
+		CG2900_ERR("Could not allocate skb");
+		err = -ENOMEM;
+		goto finished_unlock_mutex;
+	}
+
+	/* Enter data into the skb */
+	data = skb_put(skb, CG2900_BT_LEN_VS_SET_SESSION_CONFIG);
+	/*
+	 * Default all bytes to 0 so we don't have to set reserved bytes below.
+	 */
+	memset(data, 0, CG2900_BT_LEN_VS_SET_SESSION_CONFIG);
+
+	data[0] = CG2900_BT_VS_SET_SESSION_CONFIG_LSB;
+	data[1] = CG2900_BT_VS_SET_SESSION_CONFIG_MSB;
+	data[2] = CG2900_BT_PARAM_LEN_VS_SET_SESSION_CONFIG;
+	data[3] = 1; /* Number of streams */
+	data[4] = CG2900_BT_SESSION_MEDIA_TYPE_AUDIO;
+	/* Media configuration: Set sample rate and audio channel setup */
+	data[5] = CG2900_BT_SESSION_CONF_SET_SAMPLE_RATE(
+			fm_config->fm.sample_rate);
+	if (audio_info->i2s_config.channel_sel == CHANNEL_SELECTION_BOTH)
+		data[5] |= CG2900_BT_MEDIA_CONFIG_STEREO;
+	else
+		data[5] |= CG2900_BT_MEDIA_CONFIG_MONO;
+	/* data[6] - data[10] SBC codec params (not used for FM TX) */
+	/* Input Virtual Port (VP) configuration */
+	data[11] = CG2900_BT_VP_TYPE_I2S;
+	data[12] = CG2900_BT_SESSION_I2S_INDEX_I2S;
+	data[13] = audio_info->i2s_config.channel_sel;
+	/* data[14] - data[23] reserved */
+	/* Output Virtual Port (VP) configuration */
+	data[24] = CG2900_BT_VP_TYPE_FM;
+	/* data[25] - data[36] reserved */
+
+	cb_info_bt.user = audio_user;
+	SET_RESP_STATE(audio_user->resp_state, WAITING);
+
+	/* Send packet to controller */
+	err = cg2900_write(audio_info->dev_bt, skb);
+	if (err) {
+		CG2900_ERR("Error occurred while transmitting skb (%d)", err);
+		goto error_handling_free_skb;
+	}
+
+	err = receive_bt_cmd_complete(audio_user,
+				      CG2900_BT_VS_SET_SESSION_CONFIG_LSB,
+				      CG2900_BT_VS_SET_SESSION_CONFIG_MSB,
+				      &temp_session_id,
+				      CG2900_BT_PARAM_LEN_SESSION_ID);
+	if (err)
+		goto finished_unlock_mutex;
+
+	/* Store the stream handle (used for start and stop stream) */
+	*stream_handle = temp_session_id;
+	CG2900_DBG("stream_handle set to %d", *stream_handle);
+
+	SET_RESP_STATE(audio_user->resp_state, IDLE);
+
+	/*
+	 * Now start the stream by sending HCI_VS_Session_Control command
+	 */
+	CG2900_DBG("BT: HCI_VS_Session_Control");
+
+	skb = cg2900_alloc_skb(CG2900_BT_LEN_VS_SESSION_CTRL, GFP_KERNEL);
+	if (!skb) {
+		CG2900_ERR("Could not allocate skb");
+		err = -ENOMEM;
+		goto finished_unlock_mutex;
+	}
+
+	/* Enter data into the skb */
+	data = skb_put(skb, CG2900_BT_LEN_VS_SESSION_CTRL);
+
+	data[0] = CG2900_BT_VS_SESSION_CTRL_LSB;
+	data[1] = CG2900_BT_VS_SESSION_CTRL_MSB;
+	data[2] = CG2900_BT_PARAM_LEN_VS_SESSION_CTRL;
+	data[3] = (u8)(*stream_handle); /* Session ID */
+	data[4] = CG2900_BT_SESSION_START;
+
+	cb_info_bt.user = audio_user;
+	SET_RESP_STATE(audio_user->resp_state, WAITING);
+
+	/* Send packet to controller */
+	err = cg2900_write(audio_info->dev_bt, skb);
+	if (err) {
+		CG2900_ERR("Error occurred while transmitting skb (%d)", err);
+		goto error_handling_free_skb;
+	}
+
+	err = receive_bt_cmd_complete(audio_user,
+				      CG2900_BT_VS_SESSION_CTRL_LSB,
+				      CG2900_BT_VS_SESSION_CTRL_MSB,
+				      NULL, 0);
+
+	goto finished_unlock_mutex;
+
+error_handling_free_skb:
+	kfree_skb(skb);
+finished_unlock_mutex:
+	SET_RESP_STATE(audio_user->resp_state, IDLE);
+	mutex_unlock(&audio_info->bt_mutex);
+	mutex_unlock(&audio_info->fm_mutex);
+	return err;
+}
+
+/**
+ * conn_start_pcm_to_sco() - Start an audio stream connecting
Bluetooth (e)SCO to PCM_I2S.
+ * @audio_user:		Audio user to check for.
+ * @stream_handle:	[out] Pointer where to store the stream handle.
+ *
+ * This function sets up a BT to_from PCM_I2S stream.
+ * It does this by first setting the Session configuration and then starting
+ * the Audio Stream.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -ECOMM if no response was received.
+ *   -ENOMEM upon allocation errors.
+ *   Errors from @cg2900_write and @receive_bt_cmd_complete.
+ *   -EIO for other errors.
+ */
+static int conn_start_pcm_to_sco(struct audio_user *audio_user,
+				 unsigned int *stream_handle)
+{
+	int err = 0;
+	u8 temp_session_id;
+	union cg2900_endpoint_config_union *bt_config;
+	struct sk_buff *skb;
+	u8 *data;
+
+	bt_config = find_endpoint(ENDPOINT_BT_SCO_INOUT,
+				  &(audio_info->endpoints));
+	if (!bt_config) {
+		CG2900_ERR("BT not configured before stream start");
+		return -EIO;
+	}
+
+	if (!(audio_info->i2s_pcm_config_known)) {
+		CG2900_ERR("I2S_PCM DAI not configured before stream start");
+		return -EIO;
+	}
+
+	/*
+	 * Use mutex to assure that only ONE command is sent at any time on each
+	 * channel.
+	 */
+	mutex_lock(&audio_info->bt_mutex);
+
+	/*
+	 * First send HCI_VS_Set_Session_Configuration command
+	 */
+	CG2900_DBG("BT: HCI_VS_Set_Session_Configuration");
+
+	skb = cg2900_alloc_skb(CG2900_BT_LEN_VS_SET_SESSION_CONFIG, GFP_KERNEL);
+	if (!skb) {
+		CG2900_ERR("Could not allocate skb");
+		err = -ENOMEM;
+		goto finished_unlock_mutex;
+	}
+
+	/* Enter data into the skb */
+	data = skb_put(skb, CG2900_BT_LEN_VS_SET_SESSION_CONFIG);
+	/*
+	 * Default all bytes to 0 so we don't have to set reserved bytes below.
+	 */
+	memset(data, 0, CG2900_BT_LEN_VS_SET_SESSION_CONFIG);
+
+	data[0] = CG2900_BT_VS_SET_SESSION_CONFIG_LSB;
+	data[1] = CG2900_BT_VS_SET_SESSION_CONFIG_MSB;
+	data[2] = CG2900_BT_PARAM_LEN_VS_SET_SESSION_CONFIG;
+	data[3] = 1; /* Number of streams */
+	data[4] = CG2900_BT_SESSION_MEDIA_TYPE_AUDIO;
+	/* Media configuration: Set sample rate and audio channel setup */
+	data[5] = CG2900_BT_SESSION_CONF_SET_SAMPLE_RATE(
+			bt_config->sco.sample_rate);
+	data[5] |= CG2900_BT_MEDIA_CONFIG_MONO;
+	/* data[6] - data[10] SBC codec params (not used for SCO) */
+	/* Input Virtual Port (VP) configuration */
+	data[11] = CG2900_BT_VP_TYPE_BT_SCO;
+	/*
+	 * As SCO handle we just use a default value.
+	 * The chip will automatically connect the right SCO handle
+	 */
+	data[12] = HCI_SET_U16_DATA_LSB(DEFAULT_SCO_HANDLE);
+	data[13] = HCI_SET_U16_DATA_MSB(DEFAULT_SCO_HANDLE);
+	/* data[14] - data[23] reserved */
+	/* Output Virtual Port (VP) configuration */
+	data[24] = CG2900_BT_VP_TYPE_PCM;
+	data[25] = CG2900_BT_SESSION_PCM_INDEX_PCM_I2S;
+	if (audio_info->i2s_pcm_config.slot_0_used)
+		data[26] |= CG2900_BT_SESSION_CONF_SET_PCM_SLOT_USE(0);
+	if (audio_info->i2s_pcm_config.slot_1_used)
+		data[26] |= CG2900_BT_SESSION_CONF_SET_PCM_SLOT_USE(1);
+	if (audio_info->i2s_pcm_config.slot_2_used)
+		data[26] |= CG2900_BT_SESSION_CONF_SET_PCM_SLOT_USE(2);
+	if (audio_info->i2s_pcm_config.slot_3_used)
+		data[26] |= CG2900_BT_SESSION_CONF_SET_PCM_SLOT_USE(3);
+	data[27] = audio_info->i2s_pcm_config.slot_0_start;
+	data[28] = audio_info->i2s_pcm_config.slot_1_start;
+	data[29] = audio_info->i2s_pcm_config.slot_2_start;
+	data[30] = audio_info->i2s_pcm_config.slot_3_start;
+	/* data[31] - data[36] reserved */
+
+	cb_info_bt.user = audio_user;
+	SET_RESP_STATE(audio_user->resp_state, WAITING);
+
+	/* Send packet to controller */
+	err = cg2900_write(audio_info->dev_bt, skb);
+	if (err) {
+		CG2900_ERR("Error occurred while transmitting skb (%d)", err);
+		goto error_handling_free_skb;
+	}
+
+	err = receive_bt_cmd_complete(audio_user,
+				      CG2900_BT_VS_SET_SESSION_CONFIG_LSB,
+				      CG2900_BT_VS_SET_SESSION_CONFIG_MSB,
+				      &temp_session_id,
+				      CG2900_BT_PARAM_LEN_SESSION_ID);
+	if (err)
+		goto finished_unlock_mutex;
+
+	*stream_handle = temp_session_id;
+	CG2900_DBG("stream_handle set to %d", *stream_handle);
+
+	SET_RESP_STATE(audio_user->resp_state, IDLE);
+
+	/*
+	 * Now start the stream by sending HCI_VS_Session_Control command
+	 */
+	CG2900_DBG("BT: HCI_VS_Session_Control");
+
+	skb = cg2900_alloc_skb(CG2900_BT_LEN_VS_SESSION_CTRL, GFP_KERNEL);
+	if (!skb) {
+		CG2900_ERR("Could not allocate skb");
+		err = -ENOMEM;
+		goto finished_unlock_mutex;
+	}
+
+	/* Enter data into the skb */
+	data = skb_put(skb, CG2900_BT_LEN_VS_SESSION_CTRL);
+
+	data[0] = CG2900_BT_VS_SESSION_CTRL_LSB;
+	data[1] = CG2900_BT_VS_SESSION_CTRL_MSB;
+	data[2] = CG2900_BT_PARAM_LEN_VS_SESSION_CTRL;
+	data[3] = (u8)(*stream_handle); /* Session ID */
+	data[4] = CG2900_BT_SESSION_START;
+
+	cb_info_bt.user = audio_user;
+	SET_RESP_STATE(audio_user->resp_state, WAITING);
+
+	/* Send packet to controller */
+	err = cg2900_write(audio_info->dev_bt, skb);
+	if (err) {
+		CG2900_ERR("Error occurred while transmitting skb (%d)", err);
+		goto error_handling_free_skb;
+	}
+
+	err = receive_bt_cmd_complete(audio_user,
+				      CG2900_BT_VS_SESSION_CTRL_LSB,
+				      CG2900_BT_VS_SESSION_CTRL_MSB,
+				      NULL, 0);
+
+	goto finished_unlock_mutex;
+
+error_handling_free_skb:
+	kfree_skb(skb);
+finished_unlock_mutex:
+	SET_RESP_STATE(audio_user->resp_state, IDLE);
+	mutex_unlock(&audio_info->bt_mutex);
+	return err;
+}
+
+
+/**
+ * conn_stop_stream() - Stops an audio stream defined by @stream_handle.
+ * @audio_user:		Audio user to check for.
+ * @stream_handle:	Handle of the audio stream.
+ *
+ * This function stops an audio stream defined by a stream handle.
+ * It does this by first stopping the Audio Stream and then resetting the
+ * Session configuration.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -ECOMM if no response was received.
+ *   -ENOMEM upon allocation errors.
+ *   Errors from @cg2900_write and @receive_bt_cmd_complete.
+ *   -EIO for other errors.
+ */
+static int conn_stop_stream(struct audio_user *audio_user,
+			    unsigned int stream_handle)
+{
+	int err = 0;
+	union cg2900_endpoint_config_union *bt_config;
+	struct sk_buff *skb;
+	u8 *data;
+
+	bt_config = find_endpoint(ENDPOINT_BT_SCO_INOUT,
+				  &(audio_info->endpoints));
+	if (!bt_config) {
+		CG2900_ERR("BT not configured before stream start");
+		return -EIO;
+	}
+
+	/*
+	 * Use mutex to assure that only ONE command is sent at any time on each
+	 * channel.
+	 */
+	mutex_lock(&audio_info->bt_mutex);
+
+	/*
+	 * Now stop the stream by sending HCI_VS_Session_Control command
+	 */
+	CG2900_DBG("BT: HCI_VS_Session_Control");
+
+	skb = cg2900_alloc_skb(CG2900_BT_LEN_VS_SESSION_CTRL, GFP_KERNEL);
+	if (!skb) {
+		CG2900_ERR("Could not allocate skb");
+		err = -ENOMEM;
+		goto finished_unlock_mutex;
+	}
+
+	/* Enter data into the skb */
+	data = skb_put(skb, CG2900_BT_LEN_VS_SESSION_CTRL);
+
+	data[0] = CG2900_BT_VS_SESSION_CTRL_LSB;
+	data[1] = CG2900_BT_VS_SESSION_CTRL_MSB;
+	data[2] = CG2900_BT_PARAM_LEN_VS_SESSION_CTRL;
+	data[3] = (u8)stream_handle; /* Session ID */
+	data[4] = CG2900_BT_SESSION_STOP;
+
+	cb_info_bt.user = audio_user;
+	SET_RESP_STATE(audio_user->resp_state, WAITING);
+
+	/* Send packet to controller */
+	err = cg2900_write(audio_info->dev_bt, skb);
+	if (err) {
+		CG2900_ERR("Error occurred while transmitting skb (%d)", err);
+		goto error_handling_free_skb;
+	}
+
+	err = receive_bt_cmd_complete(audio_user,
+				      CG2900_BT_VS_SESSION_CTRL_LSB,
+				      CG2900_BT_VS_SESSION_CTRL_MSB,
+				      NULL, 0);
+	if (err)
+		goto finished_unlock_mutex;
+
+	SET_RESP_STATE(audio_user->resp_state, IDLE);
+
+	/*
+	 * Now delete the stream by sending HCI_VS_Reset_Session_Configuration
+	 * command
+	 */
+	CG2900_DBG("BT: HCI_VS_Reset_Session_Configuration");
+
+	skb = cg2900_alloc_skb(CG2900_BT_LEN_VS_RESET_SESSION_CONFIG,
+				 GFP_KERNEL);
+	if (!skb) {
+		CG2900_ERR("Could not allocate skb");
+		err = -ENOMEM;
+		goto finished_unlock_mutex;
+	}
+
+	/* Enter data into the skb */
+	data = skb_put(skb, CG2900_BT_LEN_VS_RESET_SESSION_CONFIG);
+
+	data[0] = CG2900_BT_VS_RESET_SESSION_CONFIG_LSB;
+	data[1] = CG2900_BT_VS_RESET_SESSION_CONFIG_MSB;
+	data[2] = CG2900_BT_PARAM_LEN_VS_SET_SESSION_CONFIG;
+	data[3] = (u8)stream_handle; /* Session ID */
+
+	cb_info_bt.user = audio_user;
+	SET_RESP_STATE(audio_user->resp_state, WAITING);
+
+	/* Send packet to controller */
+	err = cg2900_write(audio_info->dev_bt, skb);
+	if (err) {
+		CG2900_ERR("Error occurred while transmitting skb (%d)", err);
+		goto error_handling_free_skb;
+	}
+
+	err = receive_bt_cmd_complete(audio_user,
+				CG2900_BT_VS_RESET_SESSION_CONFIG_LSB,
+				CG2900_BT_VS_RESET_SESSION_CONFIG_MSB,
+				NULL, 0);
+
+	goto finished_unlock_mutex;
+
+error_handling_free_skb:
+	kfree_skb(skb);
+finished_unlock_mutex:
+	SET_RESP_STATE(audio_user->resp_state, IDLE);
+	mutex_unlock(&audio_info->bt_mutex);
+	return err;
+}
+
+/*
+ *	External methods
+ */
+int cg2900_audio_open(unsigned int *session)
+{
+	int err = 0;
+	int i;
+
+	CG2900_INFO("cg2900_audio_open");
+
+	if (!session) {
+		CG2900_ERR("NULL supplied as session.");
+		return -EINVAL;
+	}
+
+	mutex_lock(&audio_info->management_mutex);
+
+	*session = 0;
+
+	/*
+	 * First find a free session to use and allocate the session structure.
+	 */
+	for (i = FIRST_USER;
+	     i < MAX_NBR_OF_USERS && audio_info->audio_sessions[i];
+	     i++)
+		; /* Just loop until found or end reached */
+
+	if (i >= MAX_NBR_OF_USERS) {
+		CG2900_ERR("Couldn't find free user");
+		err = -EMFILE;
+		goto finished;
+	}
+
+	audio_info->audio_sessions[i] =
+			kzalloc(sizeof(*(audio_info->audio_sessions[0])),
+				GFP_KERNEL);
+	if (!audio_info->audio_sessions[i]) {
+		CG2900_ERR("Could not allocate user");
+		err = -ENOMEM;
+		goto finished;
+	}
+	CG2900_DBG("Found free session %d", i);
+	*session = i;
+	audio_info->nbr_of_users_active++;
+
+	SET_RESP_STATE(audio_info->audio_sessions[*session]->resp_state, IDLE);
+	audio_info->audio_sessions[*session]->session = *session;
+
+	if (audio_info->nbr_of_users_active == 1) {
+		/*
+		 * First user so register to CG2900 Core.
+		 * First the BT audio device.
+		 */
+		audio_info->dev_bt = cg2900_register_user(CG2900_BT_AUDIO,
+							  &cg2900_cb);
+		if (!audio_info->dev_bt) {
+			CG2900_ERR("Failed to register BT audio channel");
+			err = -EIO;
+			goto error_handling;
+		}
+
+		/* Store the callback info structure */
+		audio_info->dev_bt->user_data = &cb_info_bt;
+
+		/* Then the FM audio device */
+		audio_info->dev_fm = cg2900_register_user(CG2900_FM_RADIO_AUDIO,
+							  &cg2900_cb);
+		if (!audio_info->dev_fm) {
+			CG2900_ERR("Failed to register FM audio channel");
+			err = -EIO;
+			goto error_handling;
+		}
+
+		/* Store the callback info structure */
+		audio_info->dev_fm->user_data = &cb_info_fm;
+
+		audio_info->state = OPENED;
+	}
+
+	goto finished;
+
+error_handling:
+	if (audio_info->dev_bt) {
+		cg2900_deregister_user(audio_info->dev_bt);
+		audio_info->dev_bt = NULL;
+	}
+	audio_info->nbr_of_users_active--;
+	kfree(audio_info->audio_sessions[*session]);
+	audio_info->audio_sessions[*session] = NULL;
+finished:
+	mutex_unlock(&audio_info->management_mutex);
+	return err;
+}
+EXPORT_SYMBOL(cg2900_audio_open);
+
+int cg2900_audio_close(unsigned int *session)
+{
+	int err = 0;
+	struct audio_user *audio_user;
+
+	CG2900_INFO("cg2900_audio_close");
+
+	if (audio_info->state != OPENED) {
+		CG2900_ERR("Audio driver not open");
+		return -EIO;
+	}
+
+	if (!session) {
+		CG2900_ERR("NULL pointer supplied");
+		return -EINVAL;
+	}
+
+	audio_user = get_session_user(*session);
+	if (!audio_user) {
+		CG2900_ERR("Invalid session ID");
+		return -EINVAL;
+	}
+
+	mutex_lock(&audio_info->management_mutex);
+
+	if (!(audio_info->audio_sessions[*session])) {
+		CG2900_ERR("Session %d not opened", *session);
+		err = -EACCES;
+		goto err_unlock_mutex;
+	}
+
+	kfree(audio_info->audio_sessions[*session]);
+	audio_info->audio_sessions[*session] = NULL;
+	audio_info->nbr_of_users_active--;
+
+	if (audio_info->nbr_of_users_active == 0) {
+		/* No more sessions open. Deregister from CG2900 Core */
+		cg2900_deregister_user(audio_info->dev_fm);
+		cg2900_deregister_user(audio_info->dev_bt);
+		audio_info->state = CLOSED;
+	}
+
+	*session = 0;
+
+err_unlock_mutex:
+	mutex_unlock(&audio_info->management_mutex);
+	return err;
+}
+EXPORT_SYMBOL(cg2900_audio_close);
+
+int cg2900_audio_set_dai_config(unsigned int session,
+				struct cg2900_dai_config *config)
+{
+	int err = 0;
+	struct audio_user *audio_user;
+	struct cg2900_dai_conf_i2s *i2s = NULL;
+	struct cg2900_dai_conf_i2s_pcm *i2s_pcm = NULL;
+	struct sk_buff *skb = NULL;
+	u8 *data = NULL;
+
+	CG2900_INFO("cg2900_audio_set_dai_config");
+
+	if (audio_info->state != OPENED) {
+		CG2900_ERR("Audio driver not open");
+		return -EIO;
+	}
+
+	audio_user = get_session_user(session);
+	if (!audio_user)
+		return -EINVAL;
+
+	/*
+	 * Use mutex to assure that only ONE command is sent at any time on each
+	 * channel.
+	 */
+	mutex_lock(&audio_info->bt_mutex);
+
+	/*
+	 * Send following commands for the supported chips.
+	 * For CG2900 PG1 = HCI_Cmd_VS_Set_Hardware_Configuration
+	 */
+
+	/* Allocate the sk_buffer. The length is actually a max length since
+	 * length varies depending on logical transport.
+	 */
+	skb = cg2900_alloc_skb(CG2900_BT_LEN_VS_SET_HARDWARE_CONFIG,
+			       GFP_KERNEL);
+	if (!skb) {
+		CG2900_ERR("Could not allocate skb");
+		err = -ENOMEM;
+		goto finished_unlock_mutex;
+	}
+
+	/* Format HCI command
+	 *
+	 * [vp][ltype][direction + mode][Bitclk][PCM duration]
+	 * Start with the 2 byte op code.
+	 */
+	data = skb_put(skb, 2);
+	data[0] = CG2900_BT_VS_SET_HARDWARE_CONFIG_LSB;
+	data[1] = CG2900_BT_VS_SET_HARDWARE_CONFIG_MSB;
+
+	/* Now create each command depending on received configuration */
+	switch (config->port) {
+	case PORT_0_I2S:
+		i2s = (struct cg2900_dai_conf_i2s *) &config->conf;
+
+		/* We will now have 5 bytes of data (including length field) */
+		data = skb_put(skb, 5);
+
+		/* Parameters begin -----*/
+		data[0] = 0x04; /* Parameter length */
+		data[1] = PORT_PROTOCOL_I2S;
+		/* 0 = Virtual port if multiple on vp, PCM /I2S index */
+		data[2] = 0x00;
+		data[3] = i2s->half_period; /* WS Half period size */
+		if (i2s->mode == DAI_MODE_MASTER)
+			data[4] = CG2900_BT_HW_CONFIG_I2S_WS_SEL_MASTER;
+		else
+			data[4] = CG2900_BT_HW_CONFIG_I2S_WS_SEL_SLAVE;
+
+		/* Store the new configuration */
+		mutex_lock(&audio_info->management_mutex);
+		memcpy(&(audio_info->i2s_config), &(config->conf.i2s),
+		       sizeof(config->conf.i2s));
+		audio_info->i2s_config_known = true;
+		mutex_unlock(&audio_info->management_mutex);
+		break;
+
+	case PORT_1_I2S_PCM:
+		i2s_pcm = (struct cg2900_dai_conf_i2s_pcm *) &config->conf;
+
+		/* We will now have 7 bytes of data (including length field) */
+		data = skb_put(skb, 7);
+
+		/* Parameters begin -----*/
+		data[0] = 0x06; /* Parameter total length */
+		data[1] = PORT_PROTOCOL_PCM;
+
+		if (i2s_pcm->protocol != PORT_PROTOCOL_PCM) {
+			/*
+			 * Short solution for PG1 chip, don't support I2S over
+			 * the PCM/I2S bus...
+			 */
+			CG2900_ERR("I2S not supported over the PCM/I2S bus");
+			err = -EACCES;
+			goto error_handling_free_skb;
+		}
+
+		/*
+		 * PCM (logical)
+		 * 0 = Virtual port if multiple on vp, PCM /I2S index
+		 */
+		data[2] = 0x00;
+
+		/*
+		 * Set PCM direction and mode. They are a bit field in one byte.
+		 */
+		data[3] = CG2900_BT_HW_CONFIG_PCM_SET_DIR(0,
+							  i2s_pcm->slot_0_dir);
+		data[3] |= CG2900_BT_HW_CONFIG_PCM_SET_DIR(1,
+							   i2s_pcm->slot_1_dir);
+		data[3] |= CG2900_BT_HW_CONFIG_PCM_SET_DIR(2,
+							   i2s_pcm->slot_2_dir);
+		data[3] |= CG2900_BT_HW_CONFIG_PCM_SET_DIR(3,
+							   i2s_pcm->slot_3_dir);
+		if (i2s_pcm->mode == DAI_MODE_MASTER)
+			data[3] |= CG2900_BT_HW_CONFIG_PCM_SET_MODE(
+					CG2900_BT_HW_CONFIG_PCM_MODE_MASTER);
+		else
+			data[3] |= CG2900_BT_HW_CONFIG_PCM_SET_MODE(
+					CG2900_BT_HW_CONFIG_PCM_MODE_SLAVE);
+		data[4] = i2s_pcm->clk;
+		data[5] = HCI_SET_U16_DATA_LSB(i2s_pcm->duration);
+		data[6] = HCI_SET_U16_DATA_MSB(i2s_pcm->duration);
+
+		/* Store the new configuration */
+		mutex_lock(&audio_info->management_mutex);
+		memcpy(&(audio_info->i2s_pcm_config), &(config->conf.i2s_pcm),
+		       sizeof(config->conf.i2s_pcm));
+		audio_info->i2s_pcm_config_known = true;
+		mutex_unlock(&audio_info->management_mutex);
+		break;
+
+	default:
+		CG2900_ERR("Unknown port configuration %d", config->port);
+		err = -EACCES;
+		goto error_handling_free_skb;
+	};
+
+	cb_info_bt.user = audio_user;
+	SET_RESP_STATE(audio_user->resp_state, WAITING);
+
+	/* Send packet to controller */
+	err = cg2900_write(audio_info->dev_bt, skb);
+	if (err) {
+		CG2900_ERR("Error occurred while transmitting skb (%d)", err);
+		goto error_handling_free_skb;
+	}
+
+	err = receive_bt_cmd_complete(audio_user,
+				      CG2900_BT_VS_SET_HARDWARE_CONFIG_LSB,
+				      CG2900_BT_VS_SET_HARDWARE_CONFIG_MSB,
+				      NULL, 0);
+
+	goto finished_unlock_mutex;
+
+error_handling_free_skb:
+	kfree_skb(skb);
+finished_unlock_mutex:
+	SET_RESP_STATE(audio_user->resp_state, IDLE);
+	mutex_unlock(&audio_info->bt_mutex);
+	return err;
+}
+EXPORT_SYMBOL(cg2900_audio_set_dai_config);
+
+int cg2900_audio_get_dai_config(unsigned int session,
+				struct cg2900_dai_config *config)
+{
+	int err = 0;
+	struct audio_user *audio_user;
+
+	CG2900_INFO("cg2900_audio_get_dai_config");
+
+	if (audio_info->state != OPENED) {
+		CG2900_ERR("Audio driver not open");
+		return -EIO;
+	}
+
+	if (!config) {
+		CG2900_ERR("NULL supplied as config structure");
+		return -EINVAL;
+	}
+
+	audio_user = get_session_user(session);
+	if (!audio_user)
+		return -EINVAL;
+
+	/*
+	 * Return DAI configuration based on the received port.
+	 * If port has not been configured return error.
+	 */
+	switch (config->port) {
+	case PORT_0_I2S:
+		mutex_lock(&audio_info->management_mutex);
+		if (audio_info->i2s_config_known)
+			memcpy(&(config->conf.i2s),
+			       &(audio_info->i2s_config),
+			       sizeof(config->conf.i2s));
+		else
+			err = -EIO;
+		mutex_unlock(&audio_info->management_mutex);
+		break;
+
+	case PORT_1_I2S_PCM:
+		mutex_lock(&audio_info->management_mutex);
+		if (audio_info->i2s_pcm_config_known)
+			memcpy(&(config->conf.i2s_pcm),
+			       &(audio_info->i2s_pcm_config),
+			       sizeof(config->conf.i2s_pcm));
+		else
+			err = -EIO;
+		mutex_unlock(&audio_info->management_mutex);
+		break;
+
+	default:
+		CG2900_ERR("Unknown port configuration %d", config->port);
+		err = -EIO;
+		break;
+	};
+
+	return err;
+}
+EXPORT_SYMBOL(cg2900_audio_get_dai_config);
+
+int cg2900_audio_config_endpoint(unsigned int session,
+				 struct cg2900_endpoint_config *config)
+{
+	struct audio_user *audio_user;
+
+	CG2900_INFO("cg2900_audio_config_endpoint");
+
+	if (audio_info->state != OPENED) {
+		CG2900_ERR("Audio driver not open");
+		return -EIO;
+	}
+
+	if (!config) {
+		CG2900_ERR("NULL supplied as configuration structure");
+		return -EINVAL;
+	}
+
+	audio_user = get_session_user(session);
+	if (!audio_user)
+		return -EINVAL;
+
+	switch (config->endpoint_id) {
+	case ENDPOINT_BT_SCO_INOUT:
+	case ENDPOINT_BT_A2DP_SRC:
+	case ENDPOINT_FM_RX:
+	case ENDPOINT_FM_TX:
+		add_endpoint(config, &(audio_info->endpoints));
+		break;
+
+	case ENDPOINT_PORT_0_I2S:
+	case ENDPOINT_PORT_1_I2S_PCM:
+	case ENDPOINT_SLIMBUS_VOICE:
+	case ENDPOINT_SLIMBUS_AUDIO:
+	case ENDPOINT_BT_A2DP_SNK:
+	case ENDPOINT_ANALOG_OUT:
+	case ENDPOINT_DSP_AUDIO_IN:
+	case ENDPOINT_DSP_AUDIO_OUT:
+	case ENDPOINT_DSP_VOICE_IN:
+	case ENDPOINT_DSP_VOICE_OUT:
+	case ENDPOINT_DSP_TONE_IN:
+	case ENDPOINT_BURST_BUFFER_IN:
+	case ENDPOINT_BURST_BUFFER_OUT:
+	case ENDPOINT_MUSIC_DECODER:
+	case ENDPOINT_HCI_AUDIO_IN:
+	default:
+		CG2900_ERR("Unknown endpoint_id %d", config->endpoint_id);
+		return -EACCES;
+	}
+
+
+	return 0;
+}
+EXPORT_SYMBOL(cg2900_audio_config_endpoint);
+
+int cg2900_audio_start_stream(unsigned int session,
+			      enum cg2900_audio_endpoint_id ep_1,
+			      enum cg2900_audio_endpoint_id ep_2,
+			      unsigned int *stream_handle)
+{
+	int err;
+	struct audio_user *audio_user;
+
+	CG2900_INFO("cg2900_audio_start_stream");
+
+	if (audio_info->state != OPENED) {
+		CG2900_ERR("Audio driver not open");
+		return -EIO;
+	}
+
+	audio_user = get_session_user(session);
+	if (!audio_user)
+		return -EINVAL;
+
+	/* First handle the endpoints */
+	switch (ep_1) {
+	case ENDPOINT_PORT_0_I2S:
+		switch (ep_2) {
+		case ENDPOINT_FM_RX:
+			err = conn_start_i2s_to_fm_rx(audio_user,
+						      stream_handle);
+			break;
+
+		case ENDPOINT_FM_TX:
+			err = conn_start_i2s_to_fm_tx(audio_user,
+						      stream_handle);
+			break;
+
+		default:
+			CG2900_ERR("Endpoint config not handled: ep1: %d, "
+				   "ep2: %d", ep_1, ep_2);
+			return -EINVAL;
+		}
+		break;
+
+	case ENDPOINT_PORT_1_I2S_PCM:
+		switch (ep_2) {
+		case ENDPOINT_BT_SCO_INOUT:
+			err = conn_start_pcm_to_sco(audio_user, stream_handle);
+			break;
+
+		default:
+			CG2900_ERR("Endpoint config not handled: ep1: %d, "
+				   "ep2: %d", ep_1, ep_2);
+			return -EINVAL;
+		}
+		break;
+
+	case ENDPOINT_BT_SCO_INOUT:
+		switch (ep_2) {
+		case ENDPOINT_PORT_1_I2S_PCM:
+			err = conn_start_pcm_to_sco(audio_user, stream_handle);
+			break;
+
+		default:
+			CG2900_ERR("Endpoint config not handled: ep1: %d, "
+				   "ep2: %d", ep_1, ep_2);
+			return -EINVAL;
+		}
+		break;
+
+	case ENDPOINT_FM_RX:
+		switch (ep_2) {
+		case ENDPOINT_PORT_0_I2S:
+			err = conn_start_i2s_to_fm_rx(audio_user,
+						      stream_handle);
+			break;
+
+		default:
+			CG2900_ERR("Endpoint config not handled: ep1: %d, "
+				   "ep2: %d", ep_1, ep_2);
+			return -EINVAL;
+		}
+		break;
+
+	case ENDPOINT_FM_TX:
+		switch (ep_2) {
+		case ENDPOINT_PORT_0_I2S:
+			err = conn_start_i2s_to_fm_tx(audio_user,
+						      stream_handle);
+			break;
+
+		default:
+			CG2900_ERR("Endpoint config not handled: ep1: %d, "
+				   "ep2: %d", ep_1, ep_2);
+			return -EINVAL;
+		}
+		break;
+
+	default:
+		CG2900_ERR("Endpoint config not handled: ep1: %d, ep2: %d",
+			   ep_1, ep_2);
+		return -EINVAL;
+	}
+
+	return err;
+}
+EXPORT_SYMBOL(cg2900_audio_start_stream);
+
+int cg2900_audio_stop_stream(unsigned int session, unsigned int stream_handle)
+{
+	int err = 0;
+	struct audio_user *audio_user;
+
+	CG2900_INFO("cg2900_audio_stop_stream");
+
+	if (audio_info->state != OPENED) {
+		CG2900_ERR("Audio driver not open");
+		return -EIO;
+	}
+
+	audio_user = get_session_user(session);
+	if (!audio_user)
+		return -EINVAL;
+
+	err = conn_stop_stream(audio_user, stream_handle);
+
+	return err;
+}
+EXPORT_SYMBOL(cg2900_audio_stop_stream);
+
+/*
+ *	Character devices for userspace module test
+ */
+
+/**
+ * audio_dev_open() - Open char device.
+ * @inode:	Device driver information.
+ * @filp:	Pointer to the file struct.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -ENOMEM if allocation failed.
+ *   Errors from @cg2900_audio_open.
+ */
+static int audio_dev_open(struct inode *inode, struct file *filp)
+{
+	int err;
+	struct char_dev_info *char_dev_info;
+
+	CG2900_INFO("CG2900 Audio: audio_dev_open");
+
+	/*
+	 * Allocate the char dev info structure. It will be stored inside
+	 * the file pointer and supplied when file_ops are called.
+	 * It's free'd in audio_dev_release.
+	 */
+	char_dev_info = kzalloc(sizeof(*char_dev_info), GFP_KERNEL);
+	if (!char_dev_info) {
+		CG2900_ERR("Couldn't allocate char_dev_info");
+		return -ENOMEM;
+	}
+	filp->private_data = char_dev_info;
+
+	mutex_init(&char_dev_info->management_mutex);
+	mutex_init(&char_dev_info->rw_mutex);
+
+	mutex_lock(&char_dev_info->management_mutex);
+	err = cg2900_audio_open(&char_dev_info->session);
+	mutex_unlock(&char_dev_info->management_mutex);
+	if (err) {
+		CG2900_ERR("Failed to open CG2900 Audio driver (%d)", err);
+		goto error_handling_free_mem;
+	}
+
+	return 0;
+
+error_handling_free_mem:
+	kfree(char_dev_info);
+	filp->private_data = NULL;
+	return err;
+}
+
+/**
+ * audio_dev_release() - Release char device.
+ * @inode:	Device driver information.
+ * @filp:	Pointer to the file struct.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -EBADF if NULL pointer was supplied in private data.
+ *   Errors from @cg2900_audio_close.
+ */
+static int audio_dev_release(struct inode *inode, struct file *filp)
+{
+	int err = 0;
+	struct char_dev_info *dev = (struct char_dev_info *)filp->private_data;
+
+	CG2900_INFO("CG2900 Audio: audio_dev_release");
+
+	if (!dev) {
+		CG2900_ERR("No dev supplied in private data");
+		return -EBADF;
+	}
+
+	mutex_lock(&dev->management_mutex);
+	err = cg2900_audio_close(&dev->session);
+	if (err)
+		/*
+		 * Just print the error. Still free the char_dev_info since we
+		 * don't know the filp structure is valid after this call
+		 */
+		CG2900_ERR("Error when closing CG2900 audio driver (%d)", err);
+
+	mutex_unlock(&dev->management_mutex);
+
+	kfree(dev);
+	filp->private_data = NULL;
+
+	return err;
+}
+
+/**
+ * audio_dev_read() - Return information to the user from last @write call.
+ * @filp:	Pointer to the file struct.
+ * @buf:	Received buffer.
+ * @count:	Size of buffer.
+ * @f_pos:	Position in buffer.
+ *
+ * The audio_dev_read() function returns information from
+ * the last @write call to same char device.
+ * The data is in the following format:
+ *   * OpCode of command for this data
+ *   * Data content (Length of data is determined by the command OpCode, i.e.
+ *     fixed for each command)
+ *
+ * Returns:
+ *   Bytes successfully read (could be 0).
+ *   -EBADF if NULL pointer was supplied in private data.
+ *   -EFAULT if copy_to_user fails.
+ *   -ENOMEM upon allocation failure.
+ */
+static ssize_t audio_dev_read(struct file *filp, char __user *buf,
size_t count,
+			      loff_t *f_pos)
+{
+	struct char_dev_info *dev = (struct char_dev_info *)filp->private_data;
+	unsigned int bytes_to_copy = 0;
+	int err = 0;
+
+	CG2900_INFO("CG2900 Audio: audio_dev_read");
+
+	if (!dev) {
+		CG2900_ERR("No dev supplied in private data");
+		return -EBADF;
+	}
+	mutex_lock(&dev->rw_mutex);
+
+	if (dev->stored_data_len == 0) {
+		/* No data to read */
+		bytes_to_copy = 0;
+		goto finished;
+	}
+
+	bytes_to_copy = min(count, (unsigned int)(dev->stored_data_len));
+	if (bytes_to_copy < dev->stored_data_len)
+		CG2900_ERR("Not enough buffer to store all data. Throwing away "
+			   "rest of data.");
+
+	err = copy_to_user(buf, dev->stored_data, bytes_to_copy);
+	/*
+	 * Throw away all data, even though not all was copied.
+	 * This char device is primarily for testing purposes so we can keep
+	 * such a limitation.
+	 */
+	kfree(dev->stored_data);
+	dev->stored_data = NULL;
+	dev->stored_data_len = 0;
+
+	if (err) {
+		CG2900_ERR("copy_to_user error %d", err);
+		err = -EFAULT;
+		goto error_handling;
+	}
+
+	goto finished;
+
+error_handling:
+	mutex_unlock(&dev->rw_mutex);
+	return (ssize_t)err;
+finished:
+	mutex_unlock(&dev->rw_mutex);
+	return bytes_to_copy;
+}
+
+/**
+ * audio_dev_write() - Call CG2900 Audio API function.
+ * @filp:	Pointer to the file struct.
+ * @buf:	Write buffer.
+ * @count:	Size of the buffer write.
+ * @f_pos:	Position of buffer.
+ *
+ * audio_dev_write() function executes supplied data and
+ * interprets it as if it was a function call to the CG2900 Audio API.
+ * The data is according to:
+ *   * OpCode (4 bytes)
+ *   * Data according to OpCode (see API). No padding between parameters
+ *
+ * OpCodes are:
+ *   * OP_CODE_SET_DAI_CONF 0x00000001
+ *   * OP_CODE_GET_DAI_CONF 0x00000002
+ *   * OP_CODE_CONFIGURE_ENDPOINT 0x00000003
+ *   * OP_CODE_START_STREAM 0x00000004
+ *   * OP_CODE_STOP_STREAM 0x00000005
+ *
+ * Returns:
+ *   Bytes successfully written (could be 0). Equals input @count if
successful.
+ *   -EBADF if NULL pointer was supplied in private data.
+ *   -EFAULT if copy_from_user fails.
+ *   Error codes from all CG2900 Audio API functions.
+ */
+static ssize_t audio_dev_write(struct file *filp, const char __user *buf,
+			       size_t count, loff_t *f_pos)
+{
+	u8 *rec_data;
+	struct char_dev_info *dev = (struct char_dev_info *)filp->private_data;
+	int err = 0;
+	int op_code = 0;
+	u8 *curr_data;
+	unsigned int stream_handle;
+	struct cg2900_dai_config dai_config;
+	struct cg2900_endpoint_config ep_config;
+	enum cg2900_audio_endpoint_id ep_1;
+	enum cg2900_audio_endpoint_id ep_2;
+	int bytes_left = count;
+
+	CG2900_INFO("CG2900 Audio: audio_dev_write count %d", count);
+
+	if (!dev) {
+		CG2900_ERR("No dev supplied in private data");
+		return -EBADF;
+	}
+
+	rec_data = kmalloc(count, GFP_KERNEL);
+	if (!rec_data) {
+		CG2900_ERR("kmalloc failed");
+		return -ENOMEM;
+	}
+
+	mutex_lock(&dev->rw_mutex);
+
+	err = copy_from_user(rec_data, buf, count);
+	if (err) {
+		CG2900_ERR("copy_from_user failed (%d)", err);
+		err = -EFAULT;
+		goto finished_mutex_unlock;
+	}
+
+	/* Initialize temporary data pointer used to traverse the packet */
+	curr_data = rec_data;
+
+	op_code = curr_data[0];
+	CG2900_DBG("op_code %d", op_code);
+	/* OpCode is int size to keep data int aligned */
+	curr_data += sizeof(unsigned int);
+	bytes_left -= sizeof(unsigned int);
+
+	switch (op_code) {
+	case OP_CODE_SET_DAI_CONF:
+		CG2900_DBG("OP_CODE_SET_DAI_CONF %d", sizeof(dai_config));
+		if (bytes_left < sizeof(dai_config)) {
+			CG2900_ERR("Not enough data supplied for "
+				   "OP_CODE_SET_DAI_CONF");
+			err = -EINVAL;
+			goto finished_mutex_unlock;
+		}
+		memcpy(&dai_config, curr_data, sizeof(dai_config));
+		CG2900_DBG("dai_config.port %d", dai_config.port);
+		err = cg2900_audio_set_dai_config(dev->session, &dai_config);
+		break;
+
+	case OP_CODE_GET_DAI_CONF:
+		CG2900_DBG("OP_CODE_GET_DAI_CONF %d", sizeof(dai_config));
+		if (bytes_left < sizeof(dai_config)) {
+			CG2900_ERR("Not enough data supplied for "
+				   "OP_CODE_GET_DAI_CONF");
+			err = -EINVAL;
+			goto finished_mutex_unlock;
+		}
+		/*
+		 * Only need to copy the port really, but let's copy
+		 * like this for simplicity. It's only test functionality
+		 * after all.
+		 */
+		memcpy(&dai_config, curr_data, sizeof(dai_config));
+		CG2900_DBG("dai_config.port %d", dai_config.port);
+		err = cg2900_audio_get_dai_config(dev->session, &dai_config);
+		if (!err) {
+			/*
+			 * Command succeeded. Store data so it can be returned
+			 * when calling read.
+			 */
+			if (dev->stored_data) {
+				CG2900_ERR("Data already allocated (%d bytes). "
+					   "Throwing it away.",
+					   dev->stored_data_len);
+				kfree(dev->stored_data);
+			}
+			dev->stored_data_len = sizeof(op_code) +
+					       sizeof(dai_config);
+			dev->stored_data = kmalloc(dev->stored_data_len,
+						   GFP_KERNEL);
+			if (dev->stored_data) {
+				memcpy(dev->stored_data, &op_code,
+				       sizeof(op_code));
+				memcpy(&(dev->stored_data[sizeof(op_code)]),
+				       &dai_config, sizeof(dai_config));
+			}
+		}
+		break;
+
+	case OP_CODE_CONFIGURE_ENDPOINT:
+		CG2900_DBG("OP_CODE_CONFIGURE_ENDPOINT %d", sizeof(ep_config));
+		if (bytes_left < sizeof(ep_config)) {
+			CG2900_ERR("Not enough data supplied for "
+				   "OP_CODE_CONFIGURE_ENDPOINT");
+			err = -EINVAL;
+			goto finished_mutex_unlock;
+		}
+		memcpy(&ep_config, curr_data, sizeof(ep_config));
+		CG2900_DBG("ep_config.endpoint_id %d", ep_config.endpoint_id);
+		err = cg2900_audio_config_endpoint(dev->session, &ep_config);
+		break;
+
+	case OP_CODE_START_STREAM:
+		CG2900_DBG("OP_CODE_START_STREAM %d",
+			   (sizeof(ep_1) + sizeof(ep_2)));
+		if (bytes_left < (sizeof(ep_1) + sizeof(ep_2))) {
+			CG2900_ERR("Not enough data supplied for "
+				   "OP_CODE_START_STREAM");
+			err = -EINVAL;
+			goto finished_mutex_unlock;
+		}
+		memcpy(&ep_1, curr_data, sizeof(ep_1));
+		curr_data += sizeof(ep_1);
+		memcpy(&ep_2, curr_data, sizeof(ep_2));
+		CG2900_DBG("ep_1 %d ep_2 %d", ep_1,
+			   ep_2);
+
+		err = cg2900_audio_start_stream(dev->session,
+			ep_1, ep_2, &stream_handle);
+		if (!err) {
+			/*
+			 * Command succeeded. Store data so it can be returned
+			 * when calling read.
+			 */
+			if (dev->stored_data) {
+				CG2900_ERR("Data already allocated (%d bytes). "
+					   "Throwing it away.",
+					   dev->stored_data_len);
+				kfree(dev->stored_data);
+			}
+			dev->stored_data_len = sizeof(op_code) +
+					       sizeof(stream_handle);
+			dev->stored_data = kmalloc(dev->stored_data_len,
+						   GFP_KERNEL);
+			if (dev->stored_data) {
+				memcpy(dev->stored_data, &op_code,
+				       sizeof(op_code));
+				memcpy(&(dev->stored_data[sizeof(op_code)]),
+				       &stream_handle, sizeof(stream_handle));
+			}
+			CG2900_DBG("stream_handle %d", stream_handle);
+		}
+		break;
+
+	case OP_CODE_STOP_STREAM:
+		if (bytes_left < sizeof(stream_handle)) {
+			CG2900_ERR("Not enough data supplied for "
+				   "OP_CODE_STOP_STREAM");
+			err = -EINVAL;
+			goto finished_mutex_unlock;
+		}
+		CG2900_DBG("OP_CODE_STOP_STREAM %d", sizeof(stream_handle));
+		memcpy(&stream_handle, curr_data, sizeof(stream_handle));
+		CG2900_DBG("stream_handle %d", stream_handle);
+		err = cg2900_audio_stop_stream(dev->session, stream_handle);
+		break;
+
+	default:
+		CG2900_ERR("Received bad op_code %d", op_code);
+		break;
+	};
+
+finished_mutex_unlock:
+	kfree(rec_data);
+	mutex_unlock(&dev->rw_mutex);
+
+	if (err)
+		return err;
+	else
+		return count;
+}
+
+/**
+ * audio_dev_poll() - Handle POLL call to the interface.
+ * @filp:	Pointer to the file struct.
+ * @wait:	Poll table supplied to caller.
+ *
+ * This function is used by the User Space application to see if the device is
+ * still open and if there is any data available for reading.
+ *
+ * Returns:
+ *   Mask of current set POLL values
+ */
+static unsigned int audio_dev_poll(struct file *filp, poll_table *wait)
+{
+	struct char_dev_info *dev = (struct char_dev_info *)filp->private_data;
+	unsigned int mask = 0;
+
+	if (!dev) {
+		CG2900_ERR("No dev supplied in private data");
+		return POLLERR | POLLRDHUP;
+	}
+
+	if (RESET == audio_info->state)
+		mask |= POLLERR | POLLRDHUP | POLLPRI;
+	else
+		/* Unless RESET we can transmit */
+		mask |= POLLOUT;
+
+	if (dev->stored_data)
+		mask |= POLLIN | POLLRDNORM;
+
+	return mask;
+}
+
+static const struct file_operations char_dev_fops = {
+	.open = audio_dev_open,
+	.release = audio_dev_release,
+	.read = audio_dev_read,
+	.write = audio_dev_write,
+	.poll = audio_dev_poll
+};
+
+/*
+ *	Module related methods
+ */
+
+/**
+ * cg2900_audio_init() - Initialize module.
+ *
+ * Initialize the module and register misc device.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -ENOMEM if allocation fails.
+ *   -EEXIST if device has already been started.
+ *   Error codes from misc_register.
+ */
+static int __init cg2900_audio_init(void)
+{
+	int err;
+
+	CG2900_INFO("cg2900_audio_init");
+
+	if (audio_info) {
+		CG2900_ERR("ST-Ericsson CG2900 Audio driver already initiated");
+		return -EEXIST;
+	}
+
+	/* Initialize private data. */
+	audio_info = kzalloc(sizeof(*audio_info), GFP_KERNEL);
+	if (!audio_info) {
+		CG2900_ERR("Could not alloc audio_info struct.");
+		return -ENOMEM;
+	}
+
+	/* Initiate the mutexes */
+	mutex_init(&(audio_info->management_mutex));
+	mutex_init(&(audio_info->bt_mutex));
+	mutex_init(&(audio_info->fm_mutex));
+	mutex_init(&(audio_info->endpoints.management_mutex));
+
+	/* Initiate the SKB queues */
+	skb_queue_head_init(&(audio_info->bt_queue));
+	skb_queue_head_init(&(audio_info->fm_queue));
+
+	/* Initiate the endpoint list */
+	INIT_LIST_HEAD(&(audio_info->endpoints.ep_list));
+
+	/* Prepare and register MISC device */
+	audio_info->dev.minor = MISC_DYNAMIC_MINOR;
+	audio_info->dev.name = DEVICE_NAME;
+	audio_info->dev.fops = &char_dev_fops;
+	audio_info->dev.parent = NULL;
+
+	err = misc_register(&(audio_info->dev));
+	if (err) {
+		CG2900_ERR("Error %d registering misc dev!", err);
+		goto error_handling;
+	}
+
+	return 0;
+
+error_handling:
+	mutex_destroy(&audio_info->management_mutex);
+	mutex_destroy(&audio_info->bt_mutex);
+	mutex_destroy(&audio_info->fm_mutex);
+	mutex_destroy(&audio_info->endpoints.management_mutex);
+	kfree(audio_info);
+	audio_info = NULL;
+	return err;
+}
+
+/**
+ * cg2900_audio_exit() - Remove module.
+ *
+ * Remove misc device and free resources.
+ */
+static void __exit cg2900_audio_exit(void)
+{
+	int err;
+
+	CG2900_INFO("cg2900_audio_exit");
+
+	if (!audio_info)
+		return;
+
+	err = misc_deregister(&audio_info->dev);
+	if (err)
+		CG2900_ERR("Error deregistering misc dev (%d)!", err);
+
+	mutex_destroy(&audio_info->management_mutex);
+	mutex_destroy(&audio_info->bt_mutex);
+	mutex_destroy(&audio_info->fm_mutex);
+
+	flush_endpoint_list(&(audio_info->endpoints));
+
+	skb_queue_purge(&(audio_info->bt_queue));
+	skb_queue_purge(&(audio_info->fm_queue));
+
+	mutex_destroy(&audio_info->endpoints.management_mutex);
+
+	kfree(audio_info);
+	audio_info = NULL;
+}
+
+module_init(cg2900_audio_init);
+module_exit(cg2900_audio_exit);
+
+MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson");
+MODULE_AUTHOR("Kjell Andersson ST-Ericsson");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Linux Bluetooth Audio ST-Ericsson controller");
diff --git a/include/linux/mfd/cg2900_audio.h b/include/linux/mfd/cg2900_audio.h
new file mode 100644
index 0000000..a423285
--- /dev/null
+++ b/include/linux/mfd/cg2900_audio.h
@@ -0,0 +1,583 @@
+/*
+ * include/linux/mfd/cg2900_audio.h
+ *
+ * Copyright (C) ST-Ericsson SA 2010
+ * Authors:
+ * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@...ricsson.com) for
ST-Ericsson.
+ * Kjell Andersson (kjell.k.andersson@...ricsson.com) for ST-Ericsson.
+ * License terms:  GNU General Public License (GPL), version 2
+ *
+ * Linux Bluetooth Audio Driver for ST-Ericsson controller.
+ */
+
+#ifndef _CG2900_AUDIO_H_
+#define _CG2900_AUDIO_H_
+
+#include <linux/types.h>
+
+/*
+ * Digital Audio Interface configuration types
+ */
+
+/** CG2900_A2DP_MAX_AVDTP_HDR_LEN - Max length of a AVDTP header.
+ * Max length of a AVDTP header for an A2DP packet.
+ */
+#define CG2900_A2DP_MAX_AVDTP_HDR_LEN	25
+
+/**
+ * enum cg2900_dai_dir - Contains the DAI port directions alternatives.
+ * @DAI_DIR_B_RX_A_TX: Port B as Rx and port A as Tx.
+ * @DAI_DIR_B_TX_A_RX: Port B as Tx and port A as Rx.
+ */
+enum cg2900_dai_dir {
+	DAI_DIR_B_RX_A_TX = 0x00,
+	DAI_DIR_B_TX_A_RX = 0x01
+};
+
+/**
+ * enum cg2900_dai_mode - DAI mode alternatives.
+ * @DAI_MODE_SLAVE: Slave.
+ * @DAI_MODE_MASTER: Master.
+ */
+enum cg2900_dai_mode {
+	DAI_MODE_SLAVE = 0x00,
+	DAI_MODE_MASTER = 0x01
+};
+
+/**
+ * enum cg2900_dai_stream_ratio - Voice stream ratio alternatives.
+ * @STREAM_RATIO_FM16_VOICE16:	FM 16kHz, Voice 16kHz.
+ * @STREAM_RATIO_FM16_VOICE8:	FM 16kHz, Voice 8kHz.
+ * @STREAM_RATIO_FM48_VOICE16:	FM 48kHz, Voice 16Khz.
+ * @STREAM_RATIO_FM48_VOICE8:	FM 48kHz, Voice 8kHz.
+ *
+ * Contains the alternatives for the voice stream ratio between the
Audio stream
+ * sample rate and the Voice stream sample rate.
+ */
+enum cg2900_dai_stream_ratio {
+	STREAM_RATIO_FM16_VOICE16 = 0x01,
+	STREAM_RATIO_FM16_VOICE8 = 0x02,
+	STREAM_RATIO_FM48_VOICE16 = 0x03,
+	STREAM_RATIO_FM48_VOICE8 = 0x06
+};
+
+/**
+ * enum cg2900_dai_fs_duration - Frame sync duration alternatives.
+ * @SYNC_DURATION_8: 8 frames sync duration.
+ * @SYNC_DURATION_16: 16 frames sync duration.
+ * @SYNC_DURATION_24: 24 frames sync duration.
+ * @SYNC_DURATION_32: 32 frames sync duration.
+ * @SYNC_DURATION_48: 48 frames sync duration.
+ * @SYNC_DURATION_50: 50 frames sync duration.
+ * @SYNC_DURATION_64: 64 frames sync duration.
+ * @SYNC_DURATION_75: 75 frames sync duration.
+ * @SYNC_DURATION_96: 96 frames sync duration.
+ * @SYNC_DURATION_125: 125 frames sync duration.
+ * @SYNC_DURATION_128: 128 frames sync duration.
+ * @SYNC_DURATION_150: 150 frames sync duration.
+ * @SYNC_DURATION_192: 192 frames sync duration.
+ * @SYNC_DURATION_250: 250 frames sync duration.
+ * @SYNC_DURATION_256: 256 frames sync duration.
+ * @SYNC_DURATION_300: 300 frames sync duration.
+ * @SYNC_DURATION_384: 384 frames sync duration.
+ * @SYNC_DURATION_500: 500 frames sync duration.
+ * @SYNC_DURATION_512: 512 frames sync duration.
+ * @SYNC_DURATION_600: 600 frames sync duration.
+ * @SYNC_DURATION_768: 768 frames sync duration.
+ *
+ * This parameter sets the PCM frame sync duration. It is calculated as the
+ * ratio between the bit clock and the frame rate. For example, if the bit
+ * clock is 512 kHz and the stream sample rate is 8 kHz, the PCM frame sync
+ * duration is 512 / 8 = 64.
+ */
+enum cg2900_dai_fs_duration {
+	SYNC_DURATION_8 = 8,
+	SYNC_DURATION_16 = 16,
+	SYNC_DURATION_24 = 24,
+	SYNC_DURATION_32 = 32,
+	SYNC_DURATION_48 = 48,
+	SYNC_DURATION_50 = 50,
+	SYNC_DURATION_64 = 64,
+	SYNC_DURATION_75 = 75,
+	SYNC_DURATION_96 = 96,
+	SYNC_DURATION_125 = 125,
+	SYNC_DURATION_128 = 128,
+	SYNC_DURATION_150 = 150,
+	SYNC_DURATION_192 = 192,
+	SYNC_DURATION_250 = 250,
+	SYNC_DURATION_256 = 256,
+	SYNC_DURATION_300 = 300,
+	SYNC_DURATION_384 = 384,
+	SYNC_DURATION_500 = 500,
+	SYNC_DURATION_512 = 512,
+	SYNC_DURATION_600 = 600,
+	SYNC_DURATION_768 = 768
+};
+
+/**
+ * enum cg2900_dai_bit_clk - Bit Clock alternatives.
+ * @BIT_CLK_128:	128 Kbits clock.
+ * @BIT_CLK_256:	256 Kbits clock.
+ * @BIT_CLK_512:	512 Kbits clock.
+ * @BIT_CLK_768:	768 Kbits clock.
+ * @BIT_CLK_1024:	1024 Kbits clock.
+ * @BIT_CLK_1411_76:	1411.76 Kbits clock.
+ * @BIT_CLK_1536:	1536 Kbits clock.
+ * @BIT_CLK_2000:	2000 Kbits clock.
+ * @BIT_CLK_2048:	2048 Kbits clock.
+ * @BIT_CLK_2400:	2400 Kbits clock.
+ * @BIT_CLK_2823_52:	2823.52 Kbits clock.
+ * @BIT_CLK_3072:	3072 Kbits clock.
+ *
+ *  This parameter sets the bit clock speed. This is the clocking of the actual
+ *  data. A usual parameter for eSCO voice is 512 kHz.
+ */
+enum cg2900_dai_bit_clk {
+	BIT_CLK_128 = 0x00,
+	BIT_CLK_256 = 0x01,
+	BIT_CLK_512 = 0x02,
+	BIT_CLK_768 = 0x03,
+	BIT_CLK_1024 = 0x04,
+	BIT_CLK_1411_76 = 0x05,
+	BIT_CLK_1536 = 0x06,
+	BIT_CLK_2000 = 0x07,
+	BIT_CLK_2048 = 0x08,
+	BIT_CLK_2400 = 0x09,
+	BIT_CLK_2823_52 = 0x0A,
+	BIT_CLK_3072 = 0x0B
+};
+
+/**
+ * enum cg2900_dai_sample_rate - Sample rates alternatives.
+ * @SAMPLE_RATE_8:	8 kHz sample rate.
+ * @SAMPLE_RATE_16:	16 kHz sample rate.
+ * @SAMPLE_RATE_44_1:	44.1 kHz sample rate.
+ * @SAMPLE_RATE_48:	48 kHz sample rate.
+ */
+enum cg2900_dai_sample_rate {
+	SAMPLE_RATE_8 = 0x00,
+	SAMPLE_RATE_16 = 0x01,
+	SAMPLE_RATE_44_1 = 0x02,
+	SAMPLE_RATE_48 = 0x04
+};
+
+/**
+ * enum cg2900_dai_port_protocol - Port protocol alternatives.
+ * @PORT_PROTOCOL_PCM: Protocol PCM.
+ * @PORT_PROTOCOL_I2S: Protocol I2S.
+ */
+enum cg2900_dai_port_protocol {
+	PORT_PROTOCOL_PCM = 0x00,
+	PORT_PROTOCOL_I2S = 0x01
+};
+
+/**
+ * enum cg2900_dai_channel_sel - The channel selection alternatives.
+ * @CHANNEL_SELECTION_RIGHT: Right channel used.
+ * @CHANNEL_SELECTION_LEFT: Left channel used.
+ * @CHANNEL_SELECTION_BOTH: Both channels used.
+ */
+enum cg2900_dai_channel_sel {
+	CHANNEL_SELECTION_RIGHT = 0x00,
+	CHANNEL_SELECTION_LEFT = 0x01,
+	CHANNEL_SELECTION_BOTH = 0x02
+};
+
+/**
+ * struct cg2900_dai_conf_i2s_pcm - Port configuration structure.
+ * @mode:		Operational mode of the port configured.
+ * @i2s_channel_sel:	I2S channels used. Only valid if used in I2S mode.
+ * @slot_0_used:	True if SCO slot 0 is used.
+ * @slot_1_used:	True if SCO slot 1 is used.
+ * @slot_2_used:	True if SCO slot 2 is used.
+ * @slot_3_used:	True if SCO slot 3 is used.
+ * @slot_0_dir:		Direction of slot 0.
+ * @slot_1_dir:		Direction of slot 1.
+ * @slot_2_dir:		Direction of slot 2.
+ * @slot_3_dir:		Direction of slot 3.
+ * @slot_0_start:	Slot 0 start (relative to the PCM frame sync).
+ * @slot_1_start:	Slot 1 start (relative to the PCM frame sync)
+ * @slot_2_start:	Slot 2 start (relative to the PCM frame sync)
+ * @slot_3_start:	Slot 3 start (relative to the PCM frame sync)
+ * @ratio:		Voice stream ratio between the Audio stream sample rate
+ *			and the Voice stream sample rate.
+ * @protocol:		Protocol used on port.
+ * @duration:		Frame sync duration.
+ * @clk:		Bit clock.
+ * @sample_rate:	Sample rate.
+ */
+struct cg2900_dai_conf_i2s_pcm {
+	enum cg2900_dai_mode mode;
+	enum cg2900_dai_channel_sel i2s_channel_sel;
+	bool slot_0_used;
+	bool slot_1_used;
+	bool slot_2_used;
+	bool slot_3_used;
+	enum cg2900_dai_dir slot_0_dir;
+	enum cg2900_dai_dir slot_1_dir;
+	enum cg2900_dai_dir slot_2_dir;
+	enum cg2900_dai_dir slot_3_dir;
+	__u8 slot_0_start;
+	__u8 slot_1_start;
+	__u8 slot_2_start;
+	__u8 slot_3_start;
+	enum cg2900_dai_stream_ratio ratio;
+	enum cg2900_dai_port_protocol protocol;
+	enum cg2900_dai_fs_duration duration;
+	enum cg2900_dai_bit_clk clk;
+	enum cg2900_dai_sample_rate sample_rate;
+};
+
+/**
+ * enum cg2900_dai_half_period - Half period duration alternatives.
+ * @HALF_PER_DUR_8:	8 Bits.
+ * @HALF_PER_DUR_16:	16 Bits.
+ * @HALF_PER_DUR_24:	24 Bits.
+ * @HALF_PER_DUR_25:	25 Bits.
+ * @HALF_PER_DUR_32:	32 Bits.
+ * @HALF_PER_DUR_48:	48 Bits.
+ * @HALF_PER_DUR_64:	64 Bits.
+ * @HALF_PER_DUR_75:	75 Bits.
+ * @HALF_PER_DUR_96:	96 Bits.
+ * @HALF_PER_DUR_128:	128 Bits.
+ * @HALF_PER_DUR_150:	150 Bits.
+ * @HALF_PER_DUR_192:	192 Bits.
+ *
+ * This parameter sets the number of bits contained in each I2S half period,
+ * i.e. each channel slot. A usual value is 16 bits.
+ */
+enum cg2900_dai_half_period {
+	HALF_PER_DUR_8 = 0x00,
+	HALF_PER_DUR_16 = 0x01,
+	HALF_PER_DUR_24 = 0x02,
+	HALF_PER_DUR_25 = 0x03,
+	HALF_PER_DUR_32 = 0x04,
+	HALF_PER_DUR_48 = 0x05,
+	HALF_PER_DUR_64 = 0x06,
+	HALF_PER_DUR_75 = 0x07,
+	HALF_PER_DUR_96 = 0x08,
+	HALF_PER_DUR_128 = 0x09,
+	HALF_PER_DUR_150 = 0x0A,
+	HALF_PER_DUR_192 = 0x0B
+};
+
+/**
+ * enum cg2900_dai_word_width - Word width alternatives.
+ * @WORD_WIDTH_16: 16 bits words.
+ * @WORD_WIDTH_32: 32 bits words.
+ */
+enum cg2900_dai_word_width {
+	WORD_WIDTH_16 = 0x00,
+	WORD_WIDTH_32 = 0x01
+};
+
+/**
+ * struct cg2900_dai_conf_i2s - Port configuration struct for I2S.
+ * @mode:		Operational mode of the port.
+ * @half_period:	Half period duration.
+ * @channel_sel:	Channel selection.
+ * @sample_rate:	Sample rate.
+ * @word_width:		Word width.
+ */
+struct cg2900_dai_conf_i2s {
+	enum cg2900_dai_mode			mode;
+	enum cg2900_dai_half_period		half_period;
+	enum cg2900_dai_channel_sel		channel_sel;
+	enum cg2900_dai_sample_rate		sample_rate;
+	enum cg2900_dai_word_width		word_width;
+};
+
+/**
+ * union cg2900_dai_port_conf - DAI port configuration union.
+ * @i2s: The configuration struct for a port supporting only I2S.
+ * @i2s_pcm: The configuration struct for a port supporting both PCM and I2S.
+ */
+union cg2900_dai_port_conf {
+	struct cg2900_dai_conf_i2s i2s;
+	struct cg2900_dai_conf_i2s_pcm i2s_pcm;
+};
+
+/**
+ * enum cg2900_dai_ext_port_id - DAI external port id alternatives.
+ * @PORT_0_I2S: Port id is 0 and it supports only I2S.
+ * @PORT_1_I2S_PCM: Port id is 1 and it supports both I2S and PCM.
+ */
+enum cg2900_dai_ext_port_id {
+	PORT_0_I2S,
+	PORT_1_I2S_PCM
+};
+
+/**
+ * enum cg2900_audio_endpoint_id - Audio endpoint id alternatives.
+ * @ENDPOINT_PORT_0_I2S:	Internal audio endpoint of the external I2S
+ *				interface.
+ * @ENDPOINT_PORT_1_I2S_PCM:	Internal audio endpoint of the external I2S/PCM
+ *				interface.
+ * @ENDPOINT_SLIMBUS_VOICE:	Internal audio endpoint of the external Slimbus
+ *				voice interface. (Currently not supported)
+ * @ENDPOINT_SLIMBUS_AUDIO:	Internal audio endpoint of the external Slimbus
+ *				audio interface. (Currently not supported)
+ * @ENDPOINT_BT_SCO_INOUT:	Bluetooth SCO bidirectional.
+ * @ENDPOINT_BT_A2DP_SRC:	Bluetooth A2DP source.
+ * @ENDPOINT_BT_A2DP_SNK:	Bluetooth A2DP sink.
+ * @ENDPOINT_FM_RX:		FM receive.
+ * @ENDPOINT_FM_TX:		FM transmit.
+ * @ENDPOINT_ANALOG_OUT:	Analog out.
+ * @ENDPOINT_DSP_AUDIO_IN:	DSP audio in.
+ * @ENDPOINT_DSP_AUDIO_OUT:	DSP audio out.
+ * @ENDPOINT_DSP_VOICE_IN:	DSP voice in.
+ * @ENDPOINT_DSP_VOICE_OUT:	DSP voice out.
+ * @ENDPOINT_DSP_TONE_IN:	DSP tone in.
+ * @ENDPOINT_BURST_BUFFER_IN:	Burst buffer in.
+ * @ENDPOINT_BURST_BUFFER_OUT:	Burst buffer out.
+ * @ENDPOINT_MUSIC_DECODER:	Music decoder.
+ * @ENDPOINT_HCI_AUDIO_IN:	HCI audio in.
+ */
+enum cg2900_audio_endpoint_id {
+	ENDPOINT_PORT_0_I2S,
+	ENDPOINT_PORT_1_I2S_PCM,
+	ENDPOINT_SLIMBUS_VOICE,
+	ENDPOINT_SLIMBUS_AUDIO,
+	ENDPOINT_BT_SCO_INOUT,
+	ENDPOINT_BT_A2DP_SRC,
+	ENDPOINT_BT_A2DP_SNK,
+	ENDPOINT_FM_RX,
+	ENDPOINT_FM_TX,
+	ENDPOINT_ANALOG_OUT,
+	ENDPOINT_DSP_AUDIO_IN,
+	ENDPOINT_DSP_AUDIO_OUT,
+	ENDPOINT_DSP_VOICE_IN,
+	ENDPOINT_DSP_VOICE_OUT,
+	ENDPOINT_DSP_TONE_IN,
+	ENDPOINT_BURST_BUFFER_IN,
+	ENDPOINT_BURST_BUFFER_OUT,
+	ENDPOINT_MUSIC_DECODER,
+	ENDPOINT_HCI_AUDIO_IN
+};
+
+/**
+ * struct cg2900_dai_config - Configuration struct for Digital Audio Interface.
+ * @port: The port id to configure. Acts as a discriminator for @conf parameter
+ *	  which is a union.
+ * @conf: The configuration union that contains the parameters for the port.
+ */
+struct cg2900_dai_config {
+	enum cg2900_dai_ext_port_id	port;
+	union cg2900_dai_port_conf	conf;
+};
+
+/*
+ * Endpoint configuration types
+ */
+
+/**
+ * enum cg2900_endpoint_sample_rate - Audio endpoint configuration
sample rate alternatives.
+ * @ENDPOINT_SAMPLE_RATE_8_KHZ: 8 kHz sample rate.
+ * @ENDPOINT_SAMPLE_RATE_16_KHZ: 16 kHz sample rate.
+ * @ENDPOINT_SAMPLE_RATE_44_1_KHZ: 44.1 kHz sample rate.
+ * @ENDPOINT_SAMPLE_RATE_48_KHZ: 48 kHz sample rate.
+ */
+enum cg2900_endpoint_sample_rate {
+	ENDPOINT_SAMPLE_RATE_8_KHZ = 0x01,
+	ENDPOINT_SAMPLE_RATE_16_KHZ = 0x02,
+	ENDPOINT_SAMPLE_RATE_44_1_KHZ = 0x04,
+	ENDPOINT_SAMPLE_RATE_48_KHZ = 0x05
+};
+
+
+/**
+ * struct cg2900_endpoint_config_a2dp_src - A2DP source audio
endpoint configurations.
+ * @sample_rate: Sample rate.
+ * @channel_count: Number of channels.
+ */
+struct cg2900_endpoint_config_a2dp_src {
+	enum cg2900_endpoint_sample_rate	sample_rate;
+	unsigned int				channel_count;
+};
+
+/**
+ * struct cg2900_endpoint_config_fm - Configuration parameters for an
FM endpoint.
+ * @sample_rate: The sample rate alternatives for the FM audio endpoints.
+ */
+struct cg2900_endpoint_config_fm {
+	enum cg2900_endpoint_sample_rate	sample_rate;
+};
+
+
+/**
+ * struct cg2900_endpoint_config_sco_in_out - SCO audio endpoint
configuration structure.
+ * @sample_rate: Sample rate, valid values are
+ *		 * ENDPOINT_SAMPLE_RATE_8_KHZ
+ *		 * ENDPOINT_SAMPLE_RATE_16_KHZ.
+ */
+struct cg2900_endpoint_config_sco_in_out {
+	enum cg2900_endpoint_sample_rate	sample_rate;
+};
+
+/**
+ * union cg2900_endpoint_config - Different audio endpoint configurations.
+ * @sco:	SCO audio endpoint configuration structure.
+ * @a2dp_src:	A2DP source audio endpoint configuration structure.
+ * @fm:		FM audio endpoint configuration structure.
+ */
+union cg2900_endpoint_config_union {
+	struct cg2900_endpoint_config_sco_in_out	sco;
+	struct cg2900_endpoint_config_a2dp_src		a2dp_src;
+	struct cg2900_endpoint_config_fm		fm;
+};
+
+/**
+ * struct cg2900_endpoint_config - Audio endpoint configuration.
+ * @endpoint_id:	Identifies the audio endpoint. Works as a discriminator
+ *			for the config union.
+ * @config:		Union holding the configuration parameters for
+ *			the endpoint.
+ */
+struct cg2900_endpoint_config {
+	enum cg2900_audio_endpoint_id		endpoint_id;
+	union cg2900_endpoint_config_union	config;
+};
+
+/*
+ * ST-Ericsson CG2900 audio control driver interfaces methods
+ */
+
+/**
+ * cg2900_audio_open() - Opens a session to the ST-Ericsson CG2900
Audio control interface.
+ * @session:	[out] Address where to store the session identifier.
+ *		Allocated by caller, must not be NULL.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -EINVAL upon bad input parameter.
+ *   -ENOMEM upon allocation failure.
+ *   -EMFILE if no more user session could be opened.
+ *   -EIO upon failure to register to CG2900.
+ */
+int cg2900_audio_open(unsigned int *session);
+
+/**
+ * cg2900_audio_close() - Closes an opened session to the ST-Ericsson
CG2900 audio control interface.
+ * @session:	[in_out] Pointer to session identifier to close.
+ *		Will be 0 after this call.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -EINVAL upon bad input parameter.
+ *   -EIO if driver has not been opened.
+ *   -EACCES if session has not opened.
+ */
+int cg2900_audio_close(unsigned int *session);
+
+/**
+ * cg2900_audio_set_dai_config() -  Sets the Digital Audio Interface
configuration.
+ * @session:	Session identifier this call is related to.
+ * @config:	Pointer to the configuration to set.
+ *		Allocated by caller, must not be NULL.
+ *
+ * Sets the Digital Audio Interface (DAI) configuration. The DAI is
the external
+ * interface between the combo chip and the platform.
+ * For example the PCM or I2S interface.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -EINVAL upon bad input parameter.
+ *   -EIO if driver has not been opened.
+ *   -ENOMEM upon allocation failure.
+ *   -EACCES if trying to set unsupported configuration.
+ *   Errors from @receive_bt_cmd_complete.
+ */
+int cg2900_audio_set_dai_config(unsigned int session,
+				struct cg2900_dai_config *config);
+
+/**
+ * cg2900_audio_get_dai_config() - Gets the current Digital Audio
Interface configuration.
+ * @session:	Session identifier this call is related to.
+ * @config:	[out] Pointer to the configuration to get.
+ *		Allocated by caller, must not be NULL.
+ *
+ * Gets the current Digital Audio Interface configuration. Currently
this method
+ * can only be called after some one has called
+ * cg2900_audio_set_dai_config(), there is today no way of getting
+ * the static settings file parameters from this method.
+ * Note that the @port parameter within @config must be set when calling this
+ * function so that the ST-Ericsson CG2900 Audio driver will know which
+ * configuration to return.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -EINVAL upon bad input parameter.
+ *   -EIO if driver has not been opened or configuration has not been set.
+ */
+int cg2900_audio_get_dai_config(unsigned int session,
+				struct cg2900_dai_config *config);
+
+/**
+ * cg2900_audio_config_endpoint() - Configures one endpoint in the
combo chip's audio system.
+ * @session:	Session identifier this call is related to.
+ * @config:	Pointer to the endpoint's configuration structure.
+ *
+ * Configures one endpoint in the combo chip's audio system.
+ * Supported @endpoint_id values are:
+ *  * ENDPOINT_BT_SCO_INOUT
+ *  * ENDPOINT_BT_A2DP_SRC
+ *  * ENDPOINT_FM_RX
+ *  * ENDPOINT_FM_TX
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -EINVAL upon bad input parameter.
+ *   -EIO if driver has not been opened.
+ *   -EACCES if supplied cg2900_dai_config struct contains not supported
+ *   endpoint_id.
+ */
+int cg2900_audio_config_endpoint(unsigned int session,
+				 struct cg2900_endpoint_config *config);
+
+/**
+ * cg2900_audio_start_stream() - Connects two endpoints and starts
the audio stream.
+ * @session:		Session identifier this call is related to.
+ * @ep_1:		One of the endpoints, no relation to direction or role.
+ * @ep_2:		The other endpoint, no relation to direction or role.
+ * @stream_handle:	Pointer where to store the stream handle.
+ *			Allocated by caller, must not be NULL.
+ *
+ * Connects two endpoints and starts the audio stream.
+ * Note that the endpoints need to be configured before the stream is started;
+ * DAI endpoints, such as ENDPOINT_PORT_0_I2S, are
+ * configured through @cg2900_audio_set_dai_config() while other
+ * endpoints are configured through @cg2900_audio_config_endpoint().
+ *
+ * Supported @endpoint_id values are:
+ *  * ENDPOINT_PORT_0_I2S
+ *  * ENDPOINT_PORT_1_I2S_PCM
+ *  * ENDPOINT_BT_SCO_INOUT
+ *  * ENDPOINT_FM_RX
+ *  * ENDPOINT_FM_TX
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -EINVAL upon bad input parameter or unsupported configuration.
+ *   -EIO if driver has not been opened.
+ *   Errors from @conn_start_i2s_to_fm_rx, @conn_start_i2s_to_fm_tx, and
+ *   @conn_start_pcm_to_sco.
+ */
+int cg2900_audio_start_stream(unsigned int session,
+			      enum cg2900_audio_endpoint_id ep_1,
+			      enum cg2900_audio_endpoint_id ep_2,
+			      unsigned int *stream_handle);
+
+/**
+ * cg2900_audio_stop_stream() - Stops a stream and disconnects the endpoints.
+ * @session:		Session identifier this call is related to.
+ * @stream_handle:	Handle to the stream to stop.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -EINVAL upon bad input parameter.
+ *   -EIO if driver has not been opened.
+ */
+int cg2900_audio_stop_stream(unsigned int session,
+			     unsigned int stream_handle);
+
+#endif /* _CG2900_AUDIO_H_ */
-- 
1.6.3.3
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ