Return-Path: MIME-Version: 1.0 Reply-To: pghatwork@gmail.com Date: Fri, 22 Oct 2010 12:39:06 +0200 Message-ID: Subject: [PATCH 6/9] mfd: Add support for the ST-Ericsson CG2900 Audio. From: Par-Gunnar Hjalmdahl To: linus.walleij@stericsson.com, linux-bluetooth@vger.kernel.org, linux-kernel@vger.kernel.org Content-Type: text/plain; charset=ISO-8859-1 Sender: linux-kernel-owner@vger.kernel.org List-ID: This patch adds support for controlling audio paths within the ST-Ericsson CG2900 Connectivity Combo controller. This patch adds API for the Kernel as well as a char device for user space stacks to control the CG2900 audio paths. Signed-off-by: Par-Gunnar Hjalmdahl --- drivers/mfd/Kconfig | 8 + drivers/mfd/cg2900/Makefile | 2 + drivers/mfd/cg2900/cg2900_audio.c | 3026 +++++++++++++++++++++++++++++++++++++ include/linux/mfd/cg2900_audio.h | 460 ++++++ 4 files changed, 3496 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 6fcd8b6..6f92b04 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -288,6 +288,14 @@ config MFD_CG2900_UART help Support for UART as transport for ST-Ericsson CG2900 Connectivity Controller + 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. config PMIC_DA903X bool "Dialog Semiconductor DA9030/DA9034 PMIC Support" diff --git a/drivers/mfd/cg2900/Makefile b/drivers/mfd/cg2900/Makefile index c8dd713..94095c0 100644 --- a/drivers/mfd/cg2900/Makefile +++ b/drivers/mfd/cg2900/Makefile @@ -12,3 +12,5 @@ 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..b9ab504 --- /dev/null +++ b/drivers/mfd/cg2900/cg2900_audio.c @@ -0,0 +1,3026 @@ +/* + * 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 +#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 + +/* Used to select proper API, ignoring subrevisions etc */ +enum chip_revision { + CHIP_REV_PG1, + CHIP_REV_PG2 +}; + +/** + * 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. + * @revision: Chip revision, used to select API. + * @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. + * @stream_ids: Bitmask for in-use stream ids (only used with + * PG2 chip API). + */ +struct audio_info { + enum main_state state; + enum chip_revision revision; + 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; + u32 stream_ids; +}; + +/** + * 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 conversion functions + * + * Since the CG2900 apis uses several different ways to encode the + * same parameter in different cases, we have to use translator + * functions. + */ + +/** + * session_config_sample_rate() - Convert sample rate to format used in VS_Set_SessionConfiguration. + * @rate: Sample rate in API encoding. + */ +static u8 session_config_sample_rate(enum cg2900_endpoint_sample_rate rate) +{ + static const u8 codes[] = { + [ENDPOINT_SAMPLE_RATE_8_KHZ] = CG2900_BT_SESSION_RATE_8K, + [ENDPOINT_SAMPLE_RATE_16_KHZ] = CG2900_BT_SESSION_RATE_16K, + [ENDPOINT_SAMPLE_RATE_44_1_KHZ] = CG2900_BT_SESSION_RATE_44_1K, + [ENDPOINT_SAMPLE_RATE_48_KHZ] = CG2900_BT_SESSION_RATE_48K + }; + + return codes[rate]; +} + +/** + * mc_i2s_sample_rate() - Convert sample rate to format used in VS_Port_Config for I2S. + * @rate: Sample rate in API encoding. + */ +static u8 mc_i2s_sample_rate(enum cg2900_dai_sample_rate rate) +{ + static const u8 codes[] = { + [SAMPLE_RATE_8] = CG2900_MC_I2S_SAMPLE_RATE_8, + [SAMPLE_RATE_16] = CG2900_MC_I2S_SAMPLE_RATE_16, + [SAMPLE_RATE_44_1] = CG2900_MC_I2S_SAMPLE_RATE_44_1, + [SAMPLE_RATE_48] = CG2900_MC_I2S_SAMPLE_RATE_48 + }; + + return codes[rate]; +} + +/** + * mc_pcm_sample_rate() - Convert sample rate to format used in VS_Port_Config for PCM/I2S. + * @rate: Sample rate in API encoding. + */ +static u8 mc_pcm_sample_rate(enum cg2900_dai_sample_rate rate) +{ + static const u8 codes[] = { + [SAMPLE_RATE_8] = CG2900_MC_PCM_SAMPLE_RATE_8, + [SAMPLE_RATE_16] = CG2900_MC_PCM_SAMPLE_RATE_16, + [SAMPLE_RATE_44_1] = CG2900_MC_PCM_SAMPLE_RATE_44_1, + [SAMPLE_RATE_48] = CG2900_MC_PCM_SAMPLE_RATE_48 + }; + + return codes[rate]; +} + +/** + * mc_i2s_channel_select() - Convert channel selection to format used in VS_Port_Config. + * @sel: Channel selection in API encoding. + */ +static u8 mc_i2s_channel_select(enum cg2900_dai_channel_sel sel) +{ + static const u8 codes[] = { + [CHANNEL_SELECTION_RIGHT] = CG2900_MC_I2S_RIGHT_CHANNEL, + [CHANNEL_SELECTION_LEFT] = CG2900_MC_I2S_LEFT_CHANNEL, + [CHANNEL_SELECTION_BOTH] = CG2900_MC_I2S_BOTH_CHANNELS + }; + return codes[sel]; +} + +/** + * get_fs_duration() - Convert framesync-enumeration to real value. + * @duration: Framsync duration (API encoding). + * + * Returns: + * Duration in bits. + */ +static u16 get_fs_duration(enum cg2900_dai_fs_duration duration) +{ + static const u16 values[] = { + [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 + }; + return values[duration]; +} + +/** + * mc_i2s_role() - Convert master/slave encoding to format for I2S-ports. + * @mode: Master/slave in API encoding. + */ +static u8 mc_i2s_role(enum cg2900_dai_mode mode) +{ + if (mode == DAI_MODE_SLAVE) + return CG2900_I2S_MODE_SLAVE; + else + return CG2900_I2S_MODE_MASTER; +} + +/** + * mc_pcm_role() - Convert master/slave encoding to format for PCM/I2S-port. + * @mode: Master/slave in API encoding. + */ +static u8 mc_pcm_role(enum cg2900_dai_mode mode) +{ + if (mode == DAI_MODE_SLAVE) + return CG2900_PCM_MODE_SLAVE; + else + return CG2900_PCM_MODE_MASTER; +} + +/** + * fm_get_conversion() - Convert sample rate to convert up/down used in X_Set_Control FM commands. + * @srate: Sample rate. + */ +static u16 fm_get_conversion(enum cg2900_endpoint_sample_rate srate) +{ + if (srate >= ENDPOINT_SAMPLE_RATE_44_1_KHZ) + return CG2900_FM_CMD_SET_CTRL_CONV_UP; + else + return CG2900_FM_CMD_SET_CTRL_CONV_DOWN; +} + +/* + * 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: CG2900_Core device resetting. + */ +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); +} + +/** + * new_stream_id() - Allocate a new stream id. + * + * Returns: + * 0-127 new valid id. + * -ENOMEM if no id is available. + */ +static s8 new_stream_id(void) +{ + int r; + + mutex_lock(&audio_info->management_mutex); + + r = find_first_zero_bit(&audio_info->stream_ids, + 8 * sizeof(audio_info->stream_ids)); + + if (r >= 8 * sizeof(audio_info->stream_ids)) { + r = -ENOMEM; + goto out; + } + + audio_info->stream_ids |= (0x01u << r); + +out: + mutex_unlock(&audio_info->management_mutex); + return r; +} + +/** + * release_stream_id() - Release a stream id. + * @id: stream to release. + */ +static void release_stream_id(u8 id) +{ + if (id >= 8 * sizeof(audio_info->stream_ids)) + return; + + mutex_lock(&audio_info->management_mutex); + audio_info->stream_ids &= ~(0x01u << id); + mutex_unlock(&audio_info->management_mutex); +} + +/** + * receive_fm_write_response() - Wait for and handle the response to an FM Legacy WriteCommand request. + * @audio_user: Audio user to check for. + * @command: FM command to wait for. + * + * This function first waits (up to 5 seconds) for a response to an FM + * write command 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_write_response(struct audio_user *audio_user, + u16 command) +{ + int err = 0; + struct sk_buff *skb; + struct fm_leg_cmd_cmpl *pkt; + u16 rsp_cmd; + + /* + * 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; + } + + pkt = (struct fm_leg_cmd_cmpl *)skb->data; + + /* Check if we received the correct event */ + if (pkt->opcode != CG2900_FM_GEN_ID_LEGACY) { + 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 */ + rsp_cmd = cg2900_get_fm_cmd_id(le16_to_cpu(pkt->response_head)); + + if (pkt->fm_function != CG2900_FM_CMD_PARAM_WRITECOMMAND || + rsp_cmd != command) { + CG2900_ERR("Received unexpected packet func 0x%X cmd 0x%04X", + pkt->fm_function, rsp_cmd); + err = -EIO; + goto error_handling_free_skb; + } + + if (pkt->cmd_status != CG2900_FM_CMD_STATUS_COMMAND_SUCCEEDED) { + CG2900_ERR("FM Command failed (%d)", pkt->cmd_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: Opcode 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, u16 rsp, + void *data, int data_len) +{ + int err = 0; + struct sk_buff *skb; + struct bt_cmd_cmpl_event *evt; + u16 opcode; + + /* + * 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 = (struct bt_cmd_cmpl_event *)skb->data; + if (evt->eventcode != HCI_BT_EVT_CMD_COMPLETE) { + CG2900_ERR("We did not receive the event we expected (0x%X)", + evt->eventcode); + err = -EIO; + goto error_handling_free_skb; + } + + opcode = le16_to_cpu(evt->opcode); + if (opcode != rsp) { + CG2900_ERR("Received cmd complete for unexpected command: " + "0x%04X", opcode); + err = -EIO; + goto error_handling_free_skb; + } + + if (evt->status != HCI_BT_ERROR_NO_ERROR) { + CG2900_ERR("Received command complete with err %d", + evt->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, evt->data, data_len); + + /* Operation succeeded. We are now done */ + +error_handling_free_skb: + kfree_skb(skb); + return err; +} + +/** + * send_vs_session_ctrl() - Formats an sends a CG2900_BT_VS_SESSION_CTRL command. + * @user: Audio user this command belongs to. + * @stream_handle: Handle to stream. + * @command: Command to execute on stream, should be one of + * CG2900_BT_SESSION_START, CG2900_BT_SESSION_STOP, + * CG2900_BT_SESSION_PAUSE, CG2900_BT_SESSION_RESUME. + * + * Packs and sends a command packet and waits for the response. Must + * be called with the bt_mutex held. + * + * Returns: + * 0 if there is no error. + * -ENOMEM if not possible to allocate packet. + * -ECOMM if no response was received. + * -EIO for other errors. + */ +static int send_vs_session_ctrl(struct audio_user *user, + u8 stream_handle, u8 command) +{ + int err = 0; + struct bt_vs_session_ctrl_cmd *pkt; + struct sk_buff *skb; + + CG2900_INFO("BT: HCI_VS_Session_Control"); + + skb = cg2900_alloc_skb(sizeof(*pkt), GFP_KERNEL); + if (!skb) { + CG2900_ERR("Could not allocate skb"); + return -ENOMEM; + } + + /* Enter data into the skb */ + pkt = (struct bt_vs_session_ctrl_cmd *) skb_put(skb, sizeof(*pkt)); + + pkt->opcode = cpu_to_le16(CG2900_BT_VS_SESSION_CTRL); + pkt->plen = BT_PARAM_LEN(sizeof(*pkt)); + pkt->id = stream_handle; + pkt->control = command; /* Start/stop etc */ + + cb_info_bt.user = user; + SET_RESP_STATE(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); + kfree_skb(skb); + goto finished; + } + + err = receive_bt_cmd_complete(user, CG2900_BT_VS_SESSION_CTRL, + NULL, 0); +finished: + SET_RESP_STATE(user->resp_state, IDLE); + return err; +} + +/** + * send_vs_session_config() - Formats an sends a CG2900_BT_VS_SESSION_CONFIG command. + * @user: Audio user this command belongs to. + * @config_stream: Custom function for configuring the stream. + * @priv_data: Private data passed to @config_stream untouched. + * + * Packs and sends a command packet and waits for the response. Must + * be called with the bt_mutex held. + * + * Space is allocated for one stream and a custom function is used to + * fill in the stream configuration. + * + * Returns: + * 0-255 stream handle if no error. + * -ENOMEM if not possible to allocate packet. + * -ECOMM if no response was received. + * -EIO for other errors. + */ +static int send_vs_session_config(struct audio_user *user, + void(*config_stream)(void *, struct session_config_stream *), + void *priv_data) +{ + int err = 0; + struct sk_buff *skb; + struct bt_vs_session_config_cmd *pkt; + u8 session_id; + + CG2900_INFO("BT: HCI_VS_Set_Session_Configuration"); + + skb = cg2900_alloc_skb(sizeof(*pkt), GFP_KERNEL); + if (!skb) { + CG2900_ERR("Could not allocate skb"); + return -ENOMEM; + } + + pkt = (struct bt_vs_session_config_cmd *)skb_put(skb, sizeof(*pkt)); + /* zero the packet so we don't have to set all reserved fields */ + memset(pkt, 0, sizeof(*pkt)); + + /* Common parameters */ + pkt->opcode = cpu_to_le16(CG2900_BT_VS_SET_SESSION_CONFIG); + pkt->plen = BT_PARAM_LEN(sizeof(*pkt)); + pkt->n_streams = 1; /* 1 stream configuration supplied */ + + /* Let the custom-function fill in the rest */ + config_stream(priv_data, &pkt->stream); + + cb_info_bt.user = user; + SET_RESP_STATE(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); + kfree_skb(skb); + goto finished; + } + + err = receive_bt_cmd_complete(user, + CG2900_BT_VS_SET_SESSION_CONFIG, + &session_id, sizeof(session_id)); + /* Return session id/stream handle if success */ + if (!err) + err = session_id; + +finished: + SET_RESP_STATE(user->resp_state, IDLE); + return err; +} + +/** + * send_fm_write_1_param() - Formats and sends an FM legacy write command with one parameter. + * @user: Audio user this command belongs to. + * @command: Command. + * @param: Parameter for command. + * + * Packs and sends a command packet and waits for the response. Must + * be called with the fm_mutex held. + * + * Returns: + * 0 if there is no error. + * -ENOMEM if not possible to allocate packet. + * -ECOMM if no response was received. + * -EIO for other errors. + */ +static int send_fm_write_1_param(struct audio_user *user, + u16 command, u16 param) +{ + int err = 0; + struct sk_buff *skb; + struct fm_leg_cmd *cmd; + size_t len; + + /* base package + one parameter */ + len = sizeof(*cmd) + sizeof(cmd->fm_cmd.data[0]); + + skb = cg2900_alloc_skb(len, GFP_KERNEL); + if (!skb) { + CG2900_ERR("Could not allocate skb"); + return -ENOMEM; + } + + cmd = (struct fm_leg_cmd *)skb_put(skb, len); + + cmd->length = CG2900_FM_CMD_PARAM_LEN(len); + cmd->opcode = CG2900_FM_GEN_ID_LEGACY; + cmd->read_write = CG2900_FM_CMD_LEG_PARAM_WRITE; + cmd->fm_function = CG2900_FM_CMD_PARAM_WRITECOMMAND; + /* one parameter - builtin assumption for this function */ + cmd->fm_cmd.head = cpu_to_le16(cg2900_make_fm_cmd_id(command, 1)); + cmd->fm_cmd.data[0] = cpu_to_le16(param); + + cb_info_fm.user = user; + SET_RESP_STATE(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); + kfree_skb(skb); + goto finished; + } + + err = receive_fm_write_response(user, command); +finished: + SET_RESP_STATE(user->resp_state, IDLE); + return err; +} + +/** + * send_vs_stream_ctrl() - Formats an sends a CG2900_MC_VS_STREAM_CONTROL command. + * @user: Audio user this command belongs to. + * @stream: Stream id. + * @command: Start/stop etc. + * + * Packs and sends a command packet and waits for the response. Must + * be called with the bt_mutex held. + * + * While the HCI command allows for multiple streams in one command, + * this function only handles one. + * + * Returns: + * 0 if there is no error. + * -ENOMEM if not possible to allocate packet. + * -ECOMM if no response was received. + * -EIO for other errors. + */ +static int send_vs_stream_ctrl(struct audio_user *user, u8 stream, u8 command) +{ + int err = 0; + struct sk_buff *skb; + struct mc_vs_stream_ctrl_cmd *cmd; + size_t len; + u8 vs_err; + + /* basic length + one stream */ + len = sizeof(*cmd) + sizeof(cmd->stream[0]); + + skb = cg2900_alloc_skb(len, GFP_KERNEL); + if (!skb) { + CG2900_ERR("Could not allocate skb"); + return -ENOMEM; + } + + cmd = (struct mc_vs_stream_ctrl_cmd *)skb_put(skb, len); + + cmd->opcode = cpu_to_le16(CG2900_MC_VS_STREAM_CONTROL); + cmd->plen = BT_PARAM_LEN(len); + cmd->command = command; + + /* one stream */ + cmd->n_streams = 1; + cmd->stream[0] = stream; + + cb_info_bt.user = user; + SET_RESP_STATE(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); + kfree_skb(skb); + goto finished; + } + + /* All commands in PG2 API returns one byte with extra status */ + err = receive_bt_cmd_complete(user, + CG2900_MC_VS_STREAM_CONTROL, + &vs_err, sizeof(vs_err)); + if (err) + CG2900_DBG("VS_STREAM_CONTROL - failed with error %02x", + vs_err); + +finished: + SET_RESP_STATE(user->resp_state, IDLE); + return err; +} + +/** + * send_vs_create_stream() - Formats an sends a CG2900_MC_VS_CREATE_STREAM command. + * @user: Audio user this command belongs to. + * @inport: Stream id. + * @outport: Start/stop etc. + * @order: Activation order. + * + * Packs and sends a command packet and waits for the response. Must + * be called with the bt_mutex held. + * + * Returns: + * 0 if there is no error. + * -ENOMEM if not possible to allocate packet. + * -ECOMM if no response was received. + * -EIO for other errors. + */ +static int send_vs_create_stream(struct audio_user *user, u8 inport, + u8 outport, u8 order) +{ + int err = 0; + struct sk_buff *skb; + struct mc_vs_create_stream_cmd *cmd; + s8 id; + u8 vs_err; + + id = new_stream_id(); + if (id < 0) { + CG2900_ERR("No free stream id"); + err = -EIO; + goto finished; + } + + skb = cg2900_alloc_skb(sizeof(*cmd), GFP_KERNEL); + if (!skb) { + CG2900_ERR("Could not allocate skb"); + err = -ENOMEM; + goto finished_release_id; + } + + cmd = (struct mc_vs_create_stream_cmd *)skb_put(skb, sizeof(*cmd)); + + cmd->opcode = cpu_to_le16(CG2900_MC_VS_CREATE_STREAM); + cmd->plen = BT_PARAM_LEN(sizeof(*cmd)); + cmd->id = (u8)id; + cmd->inport = inport; + cmd->outport = outport; + cmd->order = order; + + cb_info_bt.user = user; + SET_RESP_STATE(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); + kfree_skb(skb); + goto finished_release_id; + } + + /* All commands in PG2 API returns one byte with extra status */ + err = receive_bt_cmd_complete(user, + CG2900_MC_VS_CREATE_STREAM, + &vs_err, sizeof(vs_err)); + if (err) { + CG2900_DBG("VS_CREATE_STREAM - failed with error %02x", + vs_err); + goto finished_release_id; + } + + err = id; + goto finished; + +finished_release_id: + release_stream_id(id); +finished: + SET_RESP_STATE(user->resp_state, IDLE); + return err; +} + +/** + * send_vs_port_cfg() - Formats an sends a CG2900_MC_VS_PORT_CONFIG command. + * @user: Audio user this command belongs to. + * @port: Port id to configure. + * @cfg: Pointer to specific configuration. + * @cfglen: Length of configuration. + * + * Packs and sends a command packet and waits for the response. Must + * be called with the bt_mutex held. + * + * Returns: + * 0 if there is no error. + * -ENOMEM if not possible to allocate packet. + * -ECOMM if no response was received. + * -EIO for other errors. + */ +static int send_vs_port_cfg(struct audio_user *user, u8 port, + const void *cfg, size_t cfglen) +{ + int err = 0; + struct sk_buff *skb; + struct mc_vs_port_cfg_cmd *cmd; + void *ptr; + u8 vs_err; + + skb = cg2900_alloc_skb(sizeof(*cmd) + cfglen, GFP_KERNEL); + if (!skb) { + CG2900_ERR("Could not allocate skb"); + return -ENOMEM; + } + + /* Fill in common part */ + cmd = (struct mc_vs_port_cfg_cmd *) skb_put(skb, sizeof(*cmd)); + cmd->opcode = cpu_to_le16(CG2900_MC_VS_PORT_CONFIG); + cmd->plen = BT_PARAM_LEN(sizeof(*cmd) + cfglen); + cmd->type = port; + + /* Copy specific configuration */ + ptr = skb_put(skb, cfglen); + memcpy(ptr, cfg, cfglen); + + /* Send */ + cb_info_bt.user = user; + SET_RESP_STATE(user->resp_state, WAITING); + + err = cg2900_write(audio_info->dev_bt, skb); + if (err) { + CG2900_ERR("Error occurred while transmitting skb (%d)", err); + kfree_skb(skb); + goto finished; + } + + /* All commands in PG2 API returns one byte with extra status */ + err = receive_bt_cmd_complete(user, CG2900_MC_VS_PORT_CONFIG, + &vs_err, sizeof(vs_err)); + if (err) + CG2900_DBG("VS_PORT_CONFIG - failed with error %02x", + vs_err); + +finished: + SET_RESP_STATE(user->resp_state, IDLE); + return err; +} + +/** + * set_dai_config_pg1() - Internal implementation of @cg2900_audio_set_dai_config for PG1 hardware. + * @audio_user: Pointer to audio user struct. + * @config: Pointer to the configuration to set. + * + * Sets the Digital Audio Interface (DAI) configuration for PG1 + * hardware. This is and internal function and basic + * argument-verification should have been done by the caller. + * + * Returns: + * 0 if there is no error. + * -EACCESS if port is not supported. + * -ENOMEM if not possible to allocate packet. + * -ECOMM if no response was received. + * -EIO for other errors. + */ +static int set_dai_config_pg1(struct audio_user *audio_user, + struct cg2900_dai_config *config) +{ + int err = 0; + struct cg2900_dai_conf_i2s_pcm *i2s_pcm; + struct sk_buff *skb = NULL; + struct bt_vs_set_hw_cfg_cmd_i2s *i2s_cmd; + struct bt_vs_set_hw_cfg_cmd_pcm *pcm_cmd; + + /* + * Use mutex to assure that only ONE command is sent at any time on + * each channel. + */ + mutex_lock(&audio_info->bt_mutex); + + /* 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; + } + + /* Fill in hci-command according to received configuration */ + switch (config->port) { + case PORT_0_I2S: + i2s_cmd = (struct bt_vs_set_hw_cfg_cmd_i2s *) + skb_put(skb, sizeof(*i2s_cmd)); + + i2s_cmd->opcode = cpu_to_le16(CG2900_BT_VS_SET_HARDWARE_CONFIG); + i2s_cmd->plen = BT_PARAM_LEN(sizeof(*i2s_cmd)); + + i2s_cmd->vp_type = PORT_PROTOCOL_I2S; + i2s_cmd->port_id = 0x00; /* First/only I2S port */ + i2s_cmd->half_period = config->conf.i2s.half_period; + + i2s_cmd->master_slave = mc_i2s_role(config->conf.i2s.mode); + + /* 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: + pcm_cmd = (struct bt_vs_set_hw_cfg_cmd_pcm *) + skb_put(skb, sizeof(*pcm_cmd)); + + pcm_cmd->opcode = cpu_to_le16(CG2900_BT_VS_SET_HARDWARE_CONFIG); + pcm_cmd->plen = BT_PARAM_LEN(sizeof(*pcm_cmd)); + + i2s_pcm = &config->conf.i2s_pcm; + + /* + * PG1 chips don't support I2S over the PCM/I2S bus, + * and PG2 chips don't use this command + */ + if (i2s_pcm->protocol != PORT_PROTOCOL_PCM) { + CG2900_ERR("I2S not supported over the PCM/I2S bus"); + err = -EACCES; + goto error_handling_free_skb; + } + + pcm_cmd->vp_type = PORT_PROTOCOL_PCM; + pcm_cmd->port_id = 0x00; /* First/only PCM port */ + + HWCONFIG_PCM_SET_MODE(pcm_cmd, mc_pcm_role(i2s_pcm->mode)); + + HWCONFIG_PCM_SET_DIR(pcm_cmd, 0, i2s_pcm->slot_0_dir); + HWCONFIG_PCM_SET_DIR(pcm_cmd, 1, i2s_pcm->slot_1_dir); + HWCONFIG_PCM_SET_DIR(pcm_cmd, 2, i2s_pcm->slot_2_dir); + HWCONFIG_PCM_SET_DIR(pcm_cmd, 3, i2s_pcm->slot_3_dir); + + pcm_cmd->bit_clock = i2s_pcm->clk; + pcm_cmd->frame_len = + cpu_to_le16(get_fs_duration(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, + 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; +} + +/** + * set_dai_config_pg2() - Internal implementation of @cg2900_audio_set_dai_config for PG2 hardware. + * @audio_user: Pointer to audio user struct. + * @config: Pointer to the configuration to set. + * + * Sets the Digital Audio Interface (DAI) configuration for PG2 + * hardware. This is an internal function and basic + * argument-verification should have been done by the caller. + * + * Returns: + * 0 if there is no error. + * -EACCESS if port is not supported. + * -ENOMEM if not possible to allocate packet. + * -ECOMM if no response was received. + * -EIO for other errors. + */ +static int set_dai_config_pg2(struct audio_user *audio_user, + struct cg2900_dai_config *config) +{ + int err = 0; + struct cg2900_dai_conf_i2s *i2s; + struct cg2900_dai_conf_i2s_pcm *i2s_pcm; + + struct mc_vs_port_cfg_i2s i2s_cfg; + struct mc_vs_port_cfg_pcm_i2s pcm_cfg; + + /* + * Use mutex to assure that only ONE command is sent at any time on + * each channel. + */ + mutex_lock(&audio_info->bt_mutex); + + switch (config->port) { + case PORT_0_I2S: + i2s = &config->conf.i2s; + + memset(&i2s_cfg, 0, sizeof(i2s_cfg)); /* just to be safe */ + + /* master/slave */ + PORTCFG_I2S_SET_ROLE(i2s_cfg, mc_i2s_role(i2s->mode)); + + PORTCFG_I2S_SET_HALFPERIOD(i2s_cfg, i2s->half_period); + PORTCFG_I2S_SET_CHANNELS(i2s_cfg, + mc_i2s_channel_select(i2s->channel_sel)); + PORTCFG_I2S_SET_SRATE(i2s_cfg, + mc_i2s_sample_rate(i2s->sample_rate)); + switch (i2s->word_width) { + case WORD_WIDTH_16: + PORTCFG_I2S_SET_WORDLEN(i2s_cfg, CG2900_MC_I2S_WORD_16); + break; + case WORD_WIDTH_32: + PORTCFG_I2S_SET_WORDLEN(i2s_cfg, CG2900_MC_I2S_WORD_32); + break; + } + + /* 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); + + /* Send */ + err = send_vs_port_cfg(audio_user, CG2900_MC_PORT_I2S, + &i2s_cfg, sizeof(i2s_cfg)); + break; + + case PORT_1_I2S_PCM: + i2s_pcm = &config->conf.i2s_pcm; + + memset(&pcm_cfg, 0, sizeof(pcm_cfg)); /* just to be safe */ + + /* master/slave */ + PORTCFG_PCM_SET_ROLE(pcm_cfg, mc_pcm_role(i2s_pcm->mode)); + + /* set direction for all 4 slots */ + PORTCFG_PCM_SET_DIR(pcm_cfg, 0, i2s_pcm->slot_0_dir); + PORTCFG_PCM_SET_DIR(pcm_cfg, 1, i2s_pcm->slot_1_dir); + PORTCFG_PCM_SET_DIR(pcm_cfg, 2, i2s_pcm->slot_2_dir); + PORTCFG_PCM_SET_DIR(pcm_cfg, 3, i2s_pcm->slot_3_dir); + + /* set used SCO slots, other use cases not supported atm */ + PORTCFG_PCM_SET_SCO_USED(pcm_cfg, 0, i2s_pcm->slot_0_used); + PORTCFG_PCM_SET_SCO_USED(pcm_cfg, 1, i2s_pcm->slot_1_used); + PORTCFG_PCM_SET_SCO_USED(pcm_cfg, 2, i2s_pcm->slot_2_used); + PORTCFG_PCM_SET_SCO_USED(pcm_cfg, 3, i2s_pcm->slot_3_used); + + /* slot starts */ + pcm_cfg.slot_start[0] = i2s_pcm->slot_0_start; + pcm_cfg.slot_start[1] = i2s_pcm->slot_1_start; + pcm_cfg.slot_start[2] = i2s_pcm->slot_2_start; + pcm_cfg.slot_start[3] = i2s_pcm->slot_3_start; + + /* audio/voice sample-rate ratio */ + PORTCFG_PCM_SET_RATIO(pcm_cfg, i2s_pcm->ratio); + + /* PCM or I2S mode */ + PORTCFG_PCM_SET_MODE(pcm_cfg, i2s_pcm->protocol); + + pcm_cfg.frame_len = i2s_pcm->duration; + + PORTCFG_PCM_SET_BITCLK(pcm_cfg, i2s_pcm->clk); + PORTCFG_PCM_SET_SRATE(pcm_cfg, + mc_pcm_sample_rate(i2s_pcm->sample_rate)); + + /* 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); + + /* Send */ + err = send_vs_port_cfg(audio_user, CG2900_MC_PORT_PCM_I2S, + &pcm_cfg, sizeof(pcm_cfg)); + break; + + default: + CG2900_ERR("Unknown port configuration %d", config->port); + err = -EACCES; + }; + + mutex_unlock(&audio_info->bt_mutex); + return err; +} + +/** + * struct i2s_fm_stream_config_priv - Helper struct for stream i2s-fm streams. + * @fm_config: FM endpoint configuration. + * @rx: true for FM-RX, false for FM-TX. + */ +struct i2s_fm_stream_config_priv { + struct cg2900_endpoint_config_fm *fm_config; + bool rx; + +}; + +/** + * config_i2s_fm_stream() - Callback for @send_vs_session_config. + * @_priv: Pointer to a @i2s_fm_stream_config_priv struct. + * @cfg: Pointer to stream config block in command packet. + * + * Fills in stream configuration for I2S-FM RX/TX. + */ + +static void config_i2s_fm_stream(void *_priv, + struct session_config_stream *cfg) +{ + struct i2s_fm_stream_config_priv *priv = _priv; + struct session_config_vport *fm; + struct session_config_vport *i2s; + + cfg->media_type = CG2900_BT_SESSION_MEDIA_TYPE_AUDIO; + + if (audio_info->i2s_config.channel_sel == CHANNEL_SELECTION_BOTH) + SESSIONCFG_SET_CHANNELS(cfg, CG2900_BT_MEDIA_CONFIG_STEREO); + else + SESSIONCFG_SET_CHANNELS(cfg, CG2900_BT_MEDIA_CONFIG_MONO); + + SESSIONCFG_I2S_SET_SRATE(cfg, + session_config_sample_rate(priv->fm_config->sample_rate)); + + cfg->codec_type = CG2900_CODEC_TYPE_NONE; + /* codec mode and parameters not used */ + + if (priv->rx) { + fm = &cfg->inport; /* FM is input */ + i2s = &cfg->outport; /* I2S is output */ + } else { + i2s = &cfg->inport; /* I2S is input */ + fm = &cfg->outport; /* FM is output */ + } + + fm->type = CG2900_BT_VP_TYPE_FM; + + i2s->type = CG2900_BT_VP_TYPE_I2S; + i2s->i2s.index = CG2900_BT_SESSION_I2S_INDEX_I2S; + i2s->i2s.channel = audio_info->i2s_config.channel_sel; +} + +/** + * 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. + * -EIO for other errors. + */ +static int conn_start_i2s_to_fm_rx(struct audio_user *audio_user, + unsigned int *stream_handle) +{ + int err = 0; + union cg2900_endpoint_config_union *fm_config; + + 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. + */ + err = send_fm_write_1_param(audio_user, + CG2900_FM_CMD_ID_AUP_EXT_SET_MODE, + CG2900_FM_CMD_AUP_EXT_SET_MODE_PARALLEL); + if (err) + goto finished_unlock_mutex; + + /* + * Now configure the External Sample Rate Converter by sending + * HCI_Write command with AUP_EXT_SetControl. + */ + err = send_fm_write_1_param( + audio_user, CG2900_FM_CMD_ID_AUP_EXT_SET_CTRL, + fm_get_conversion(fm_config->fm.sample_rate)); + if (err) + goto finished_unlock_mutex; + + /* Set up the stream */ + if (audio_info->revision == CHIP_REV_PG1) { + struct i2s_fm_stream_config_priv stream_priv; + + /* Now send HCI_VS_Set_Session_Configuration command */ + stream_priv.fm_config = &fm_config->fm; + stream_priv.rx = true; + err = send_vs_session_config(audio_user, config_i2s_fm_stream, + &stream_priv); + } else { + struct mc_vs_port_cfg_fm fm_cfg; + + memset(&fm_cfg, 0, sizeof(fm_cfg)); + + /* Configure port FM RX */ + /* Expects 0-3 - same as user API - so no conversion needed */ + PORTCFG_FM_SET_SRATE(fm_cfg, (u8)fm_config->fm.sample_rate); + + err = send_vs_port_cfg(audio_user, CG2900_MC_PORT_FM_RX_1, + &fm_cfg, sizeof(fm_cfg)); + if (err) + goto finished_unlock_mutex; + + /* CreateStream */ + err = send_vs_create_stream(audio_user, + CG2900_MC_PORT_FM_RX_1, + CG2900_MC_PORT_I2S, + 0); /* chip doesn't care */ + } + + if (err < 0) + goto finished_unlock_mutex; + + /* Store the stream handle (used for start and stop stream) */ + *stream_handle = (u8)err; + CG2900_DBG("stream_handle set to %d", *stream_handle); + + /* Now start the stream */ + if (audio_info->revision == CHIP_REV_PG1) + err = send_vs_session_ctrl(audio_user, *stream_handle, + CG2900_BT_SESSION_START); + else + err = send_vs_stream_ctrl(audio_user, *stream_handle, + CG2900_MC_STREAM_START); + +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. + * -EIO for other errors. + */ +static int conn_start_i2s_to_fm_tx(struct audio_user *audio_user, + unsigned int *stream_handle) +{ + int err = 0; + union cg2900_endpoint_config_union *fm_config; + + 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"); + err = send_fm_write_1_param(audio_user, CG2900_FM_CMD_ID_AIP_SET_MODE, + CG2900_FM_CMD_AIP_SET_MODE_INPUT_DIG); + if (err) + goto finished_unlock_mutex; + + /* + * Now configure the BT sample rate converter by sending HCI_Write + * command with AIP_BT_SetControl. + */ + CG2900_DBG("FM: AIP_BT_SetControl"); + err = send_fm_write_1_param( + audio_user, CG2900_FM_CMD_ID_AIP_BT_SET_CTRL, + fm_get_conversion(fm_config->fm.sample_rate)); + if (err) + goto finished_unlock_mutex; + + /* + * Now set input of the BT sample rate converter by sending HCI_Write + * command with AIP_BT_SetMode. + */ + CG2900_DBG("FM: AIP_BT_SetMode"); + err = send_fm_write_1_param(audio_user, + CG2900_FM_CMD_ID_AIP_BT_SET_MODE, + CG2900_FM_CMD_AIP_BT_SET_MODE_INPUT_PAR); + if (err) + goto finished_unlock_mutex; + + /* Set up the stream */ + if (audio_info->revision == CHIP_REV_PG1) { + struct i2s_fm_stream_config_priv stream_priv; + + /* Now send HCI_VS_Set_Session_Configuration command */ + stream_priv.fm_config = &fm_config->fm; + stream_priv.rx = false; + err = send_vs_session_config(audio_user, config_i2s_fm_stream, + &stream_priv); + } else { + struct mc_vs_port_cfg_fm fm_cfg; + + memset(&fm_cfg, 0, sizeof(fm_cfg)); + + /* Configure port FM TX */ + /* Expects 0-3 - same as user API - so no conversion needed */ + PORTCFG_FM_SET_SRATE(fm_cfg, (u8)fm_config->fm.sample_rate); + + err = send_vs_port_cfg(audio_user, CG2900_MC_PORT_FM_TX, + &fm_cfg, sizeof(fm_cfg)); + if (err) + goto finished_unlock_mutex; + + /* CreateStream */ + err = send_vs_create_stream(audio_user, + CG2900_MC_PORT_I2S, + CG2900_MC_PORT_FM_TX, + 0); /* chip doesn't care */ + } + + if (err < 0) + goto finished_unlock_mutex; + + /* Store the stream handle (used for start and stop stream) */ + *stream_handle = (u8)err; + CG2900_DBG("stream_handle set to %d", *stream_handle); + + /* Now start the stream */ + if (audio_info->revision == CHIP_REV_PG1) + err = send_vs_session_ctrl(audio_user, *stream_handle, + CG2900_BT_SESSION_START); + else + err = send_vs_stream_ctrl(audio_user, *stream_handle, + CG2900_MC_STREAM_START); + +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; +} + +/** + * config_pcm_sco_stream() - Callback for @send_vs_session_config. + * @_priv: Pointer to a @cg2900_endpoint_config_sco_in_out struct. + * @cfg: Pointer to stream config block in command packet. + * + * Fills in stream configuration for PCM-SCO. + */ +static void config_pcm_sco_stream(void *_priv, + struct session_config_stream *cfg) +{ + struct cg2900_endpoint_config_sco_in_out *sco_ep = _priv; + + cfg->media_type = CG2900_BT_SESSION_MEDIA_TYPE_AUDIO; + + SESSIONCFG_SET_CHANNELS(cfg, CG2900_BT_MEDIA_CONFIG_MONO); + SESSIONCFG_I2S_SET_SRATE(cfg, + session_config_sample_rate(sco_ep->sample_rate)); + + cfg->codec_type = CG2900_CODEC_TYPE_NONE; + /* codec mode and parameters not used */ + + cfg->inport.type = CG2900_BT_VP_TYPE_BT_SCO; + cfg->inport.sco.acl_handle = cpu_to_le16(DEFAULT_SCO_HANDLE); + + cfg->outport.type = CG2900_BT_VP_TYPE_PCM; + cfg->outport.pcm.index = CG2900_BT_SESSION_PCM_INDEX_PCM_I2S; + + SESSIONCFG_PCM_SET_USED(cfg->outport, 0, + audio_info->i2s_pcm_config.slot_0_used); + SESSIONCFG_PCM_SET_USED(cfg->outport, 1, + audio_info->i2s_pcm_config.slot_1_used); + SESSIONCFG_PCM_SET_USED(cfg->outport, 2, + audio_info->i2s_pcm_config.slot_2_used); + SESSIONCFG_PCM_SET_USED(cfg->outport, 3, + audio_info->i2s_pcm_config.slot_3_used); + + cfg->outport.pcm.slot_start[0] = + audio_info->i2s_pcm_config.slot_0_start; + cfg->outport.pcm.slot_start[1] = + audio_info->i2s_pcm_config.slot_1_start; + cfg->outport.pcm.slot_start[2] = + audio_info->i2s_pcm_config.slot_2_start; + cfg->outport.pcm.slot_start[3] = + audio_info->i2s_pcm_config.slot_3_start; +} + +/** + * 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 + * -EIO for other errors. + */ +static int conn_start_pcm_to_sco(struct audio_user *audio_user, + unsigned int *stream_handle) +{ + int err = 0; + union cg2900_endpoint_config_union *bt_config; + + 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); + + /* Set up the stream */ + if (audio_info->revision == CHIP_REV_PG1) { + err = send_vs_session_config(audio_user, config_pcm_sco_stream, + &bt_config->sco); + } else { + struct mc_vs_port_cfg_sco sco_cfg; + + /* zero codec params etc */ + memset(&sco_cfg, 0, sizeof(sco_cfg)); + sco_cfg.acl_id = DEFAULT_SCO_HANDLE; + PORTCFG_SCO_SET_WBS(sco_cfg, 0); /* No WBS yet */ + PORTCFG_SCO_SET_CODEC(sco_cfg, CG2900_CODEC_TYPE_NONE); + + err = send_vs_port_cfg(audio_user, CG2900_MC_PORT_BT_SCO, + &sco_cfg, sizeof(sco_cfg)); + if (err) + goto finished_unlock_mutex; + + /* CreateStream */ + err = send_vs_create_stream(audio_user, + CG2900_MC_PORT_PCM_I2S, + CG2900_MC_PORT_BT_SCO, + 0); /* chip doesn't care */ + } + + if (err < 0) + goto finished_unlock_mutex; + + /* Store the stream handle (used for start and stop stream) */ + *stream_handle = (u8)err; + CG2900_DBG("stream_handle set to %d", *stream_handle); + + /* Now start the stream by sending HCI_VS_Session_Control command */ + err = send_vs_session_ctrl(audio_user, *stream_handle, + CG2900_BT_SESSION_START); + +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 is used to stop an audio stream defined by a stream + * handle. It does this by first stopping the stream and then + * resetting the session/stream. + * + * Returns: + * 0 if there is no error. + * -ECOMM if no response was received. + * -ENOMEM upon allocation errors. + * Errors from @cg2900_write. + * -EIO for other errors. + */ +static int conn_stop_stream(struct audio_user *audio_user, + unsigned int stream_handle) +{ + int err = 0; + struct sk_buff *skb; + u16 opcode; + + /* + * 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 */ + if (audio_info->revision == CHIP_REV_PG1) + err = send_vs_session_ctrl(audio_user, stream_handle, + CG2900_BT_SESSION_STOP); + else + err = send_vs_stream_ctrl(audio_user, stream_handle, + CG2900_MC_STREAM_STOP); + if (err) + goto finished_unlock_mutex; + + /* Now delete the stream - format command... */ + if (audio_info->revision == CHIP_REV_PG1) { + struct bt_vs_reset_session_cfg_cmd *cmd; + + CG2900_DBG("BT: HCI_VS_Reset_Session_Configuration"); + + skb = cg2900_alloc_skb(sizeof(*cmd), GFP_KERNEL); + if (!skb) { + CG2900_ERR("Could not allocate skb"); + err = -ENOMEM; + goto finished_unlock_mutex; + } + + cmd = (struct bt_vs_reset_session_cfg_cmd *) + skb_put(skb, sizeof(*cmd)); + + opcode = CG2900_BT_VS_RESET_SESSION_CONFIG; + cmd->opcode = cpu_to_le16(opcode); + cmd->plen = BT_PARAM_LEN(sizeof(*cmd)); + cmd->id = (u8)stream_handle; + } else { + struct mc_vs_delete_stream_cmd *cmd; + + CG2900_DBG("BT: HCI_VS_Delete_Stream"); + + skb = cg2900_alloc_skb(sizeof(*cmd), GFP_KERNEL); + if (!skb) { + CG2900_ERR("Could not allocate skb"); + err = -ENOMEM; + goto finished_unlock_mutex; + } + + cmd = (struct mc_vs_delete_stream_cmd *) + skb_put(skb, sizeof(*cmd)); + + opcode = CG2900_MC_VS_DELETE_STREAM; + cmd->opcode = cpu_to_le16(opcode); + cmd->plen = BT_PARAM_LEN(sizeof(*cmd)); + cmd->stream = (u8)stream_handle; + } + + /* ...and send it */ + cb_info_bt.user = audio_user; + SET_RESP_STATE(audio_user->resp_state, WAITING); + + err = cg2900_write(audio_info->dev_bt, skb); + if (err) { + CG2900_ERR("Error occurred while transmitting skb (%d)", err); + goto error_handling_free_skb; + } + + /* wait for response */ + if (audio_info->revision == CHIP_REV_PG1) { + err = receive_bt_cmd_complete(audio_user, opcode, NULL, 0); + } else { + u8 vs_err; + + /* All commands in PG2 API returns one byte extra status */ + err = receive_bt_cmd_complete(audio_user, opcode, + &vs_err, sizeof(vs_err)); + + if (err) + CG2900_DBG("VS_DELETE_STREAM - failed with error %02x", + vs_err); + else + release_stream_id(stream_handle); + + } + + 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; +} + +/** + * 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) +{ + 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) { + struct cg2900_rev_data rev_data; + + /* + * 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; + + /* Read chip revision data */ + if (!cg2900_get_local_revision(&rev_data)) { + CG2900_ERR("Couldn't retrieve revision data"); + err = -EIO; + goto error_handling; + } + + /* Decode revision data */ + switch (rev_data.revision) { + case CG2900_PG1_REV: + case CG2900_PG1_SPECIAL_REV: + audio_info->revision = CHIP_REV_PG1; + break; + + case CG2900_PG2_REV: + audio_info->revision = CHIP_REV_PG2; + break; + + default: + CG2900_ERR("Chip rev 0x%04X sub 0x%04X not supported", + rev_data.revision, rev_data.sub_version); + err = -EIO; + goto error_handling; + } + + audio_info->state = OPENED; + } + + goto finished; + +error_handling: + if (audio_info->dev_fm) { + cg2900_deregister_user(audio_info->dev_fm); + audio_info->dev_fm = NULL; + } + 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); + +/** + * 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) +{ + 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); + +/** + * 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) +{ + int err = 0; + struct audio_user *audio_user; + struct cg2900_rev_data rev_data; + + 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; + + if (!cg2900_get_local_revision(&rev_data)) { + CG2900_ERR("Couldn't retrieve revision data"); + return -EIO; + } + + /* Different commands are used for PG1 and PG2 */ + switch (rev_data.revision) { + case CG2900_PG1_REV: + case CG2900_PG1_SPECIAL_REV: + err = set_dai_config_pg1(audio_user, config); + break; + + case CG2900_PG2_REV: + err = set_dai_config_pg2(audio_user, config); + break; + + default: + CG2900_ERR("Chip rev 0x%04X sub 0x%04X not supported", + rev_data.revision, rev_data.sub_version); + err = -EIO; + } + + return err; +} +EXPORT_SYMBOL(cg2900_audio_set_dai_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) +{ + 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); + +/** + * 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) +{ + 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); + +static bool is_dai_port(enum cg2900_audio_endpoint_id ep) +{ + /* These are the only supported ones */ + return (ep == ENDPOINT_PORT_0_I2S) || (ep == ENDPOINT_PORT_1_I2S_PCM); +} + +/** + * 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) +{ + 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; + + /* put digital interface in ep_1 to simplify comparison below */ + if (!is_dai_port(ep_1)) { + /* Swap endpoints */ + enum cg2900_audio_endpoint_id t = ep_1; + ep_1 = ep_2; + ep_2 = t; + } + + if (ep_1 == ENDPOINT_PORT_1_I2S_PCM && ep_2 == ENDPOINT_BT_SCO_INOUT) { + err = conn_start_pcm_to_sco(audio_user, stream_handle); + } else if (ep_1 == ENDPOINT_PORT_0_I2S && ep_2 == ENDPOINT_FM_RX) { + err = conn_start_i2s_to_fm_rx(audio_user, stream_handle); + } else if (ep_1 == ENDPOINT_PORT_0_I2S && ep_2 == ENDPOINT_FM_TX) { + err = conn_start_i2s_to_fm_tx(audio_user, stream_handle); + } else { + CG2900_ERR("Endpoint config not handled: ep1: %d, " + "ep2: %d", ep_1, ep_2); + err = -EINVAL; + } + + return err; +} +EXPORT_SYMBOL(cg2900_audio_start_stream); + +/** + * 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) +{ + 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; + + return conn_stop_stream(audio_user, stream_handle); +} +EXPORT_SYMBOL(cg2900_audio_stop_stream); + +/** + * 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. Saved len: %d, stored_len: %d", + count, dev->stored_data_len); + + 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_probe() - Initialize CG2900 audio resources. + * @pdev: Platform device. + * + * 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 __devinit cg2900_audio_probe(struct platform_device *pdev) +{ + int err; + + CG2900_INFO("cg2900_audio_probe"); + + 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 = &(pdev->dev); + + 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_remove() - Release CG2900 audio resources. + * @pdev: Platform device. + * + * Remove misc device and free resources. + * + * Returns: + * 0 if success. + * Error codes from misc_deregister. + */ +static int __devexit cg2900_audio_remove(struct platform_device *pdev) +{ + int err; + + CG2900_INFO("cg2900_audio_remove"); + + if (!audio_info) + return 0; + + 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; + return err; +} + +static struct platform_driver cg2900_audio_driver = { + .driver = { + .name = "cg2900-audio", + .owner = THIS_MODULE, + }, + .probe = cg2900_audio_probe, + .remove = __devexit_p(cg2900_audio_remove), +}; + +/** + * cg2900_audio_init() - Initialize module. + * + * Registers platform driver. + */ +static int __init cg2900_audio_init(void) +{ + CG2900_INFO("cg2900_audio_init"); + return platform_driver_register(&cg2900_audio_driver); +} + +/** + * cg2900_audio_exit() - Remove module. + * + * Unregisters platform driver. + */ +static void __exit cg2900_audio_exit(void) +{ + CG2900_INFO("cg2900_audio_exit"); + platform_driver_unregister(&cg2900_audio_driver); +} + +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..44ded01 --- /dev/null +++ b/include/linux/mfd/cg2900_audio.h @@ -0,0 +1,460 @@ +/* + * 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 = 0, + SYNC_DURATION_16 = 1, + SYNC_DURATION_24 = 2, + SYNC_DURATION_32 = 3, + SYNC_DURATION_48 = 4, + SYNC_DURATION_50 = 5, + SYNC_DURATION_64 = 6, + SYNC_DURATION_75 = 7, + SYNC_DURATION_96 = 8, + SYNC_DURATION_125 = 9, + SYNC_DURATION_128 = 10, + SYNC_DURATION_150 = 11, + SYNC_DURATION_192 = 12, + SYNC_DURATION_250 = 13, + SYNC_DURATION_256 = 14, + SYNC_DURATION_300 = 15, + SYNC_DURATION_384 = 16, + SYNC_DURATION_500 = 17, + SYNC_DURATION_512 = 18, + SYNC_DURATION_600 = 19, + SYNC_DURATION_768 = 20 +}; + +/** + * 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 = 0, + SAMPLE_RATE_16 = 1, + SAMPLE_RATE_44_1 = 2, + SAMPLE_RATE_48 = 3 +}; + +/** + * 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. + * + * This enum defines the same values as @cg2900_dai_sample_rate, but + * is kept to preserve the API. + * + * @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 = SAMPLE_RATE_8, + ENDPOINT_SAMPLE_RATE_16_KHZ = SAMPLE_RATE_16, + ENDPOINT_SAMPLE_RATE_44_1_KHZ = SAMPLE_RATE_44_1, + ENDPOINT_SAMPLE_RATE_48_KHZ = SAMPLE_RATE_48 +}; + + +/** + * 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; +}; + +int cg2900_audio_open(unsigned int *session); +int cg2900_audio_close(unsigned int *session); +int cg2900_audio_set_dai_config(unsigned int session, + struct cg2900_dai_config *config); +int cg2900_audio_get_dai_config(unsigned int session, + struct cg2900_dai_config *config); +int cg2900_audio_config_endpoint(unsigned int session, + struct cg2900_endpoint_config *config); +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 cg2900_audio_stop_stream(unsigned int session, + unsigned int stream_handle); + +#endif /* _CG2900_AUDIO_H_ */ -- 1.6.3.3