Return-Path: MIME-Version: 1.0 Reply-To: pghatwork@gmail.com Date: Fri, 24 Sep 2010 15:51:19 +0200 Message-ID: Subject: [PATCH 5/6] This patch adds support for controlling the audio paths From: Par-Gunnar Hjalmdahl To: linux-bluetooth@vger.kernel.org, linux-kernel@vger.kernel.org, linus.walleij@stericsson.com, Pavan Savoy Content-Type: text/plain; charset=ISO-8859-1 Sender: linux-kernel-owner@vger.kernel.org List-ID: 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 --- 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@stericsson.com) for ST-Ericsson. + * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Linux Bluetooth Audio Driver for ST-Ericsson CG2900 controller. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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@stericsson.com) for ST-Ericsson. + * Kjell Andersson (kjell.k.andersson@stericsson.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 + +/* + * 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