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 <[email protected]>
---
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 ([email protected]) for
ST-Ericsson.
+ * Kjell Andersson ([email protected]) for ST-Ericsson.
+ * License terms: GNU General Public License (GPL), version 2
+ *
+ * Linux Bluetooth Audio Driver for ST-Ericsson CG2900 controller.
+ */
+
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/poll.h>
+#include <linux/sched.h>
+#include <linux/skbuff.h>
+#include <linux/types.h>
+#include <linux/mfd/cg2900.h>
+#include <linux/mfd/cg2900_audio.h>
+
+#include "cg2900_debug.h"
+#include "hci_defines.h"
+#include "cg2900_chip.h"
+
+/* Char device op codes */
+#define OP_CODE_SET_DAI_CONF 0x01
+#define OP_CODE_GET_DAI_CONF 0x02
+#define OP_CODE_CONFIGURE_ENDPOINT 0x03
+#define OP_CODE_START_STREAM 0x04
+#define OP_CODE_STOP_STREAM 0x05
+
+/* Device names */
+#define DEVICE_NAME "cg2900_audio"
+
+/* Type of channel used */
+#define BT_CHANNEL_USED 0x00
+#define FM_CHANNEL_USED 0x01
+
+#define MAX_NBR_OF_USERS 10
+#define FIRST_USER 1
+
+#define SET_RESP_STATE(__state_var, __new_state) \
+ CG2900_SET_STATE("resp_state", __state_var, __new_state)
+
+#define DEFAULT_SCO_HANDLE 0x0008
+
+/* Use a timeout of 5 seconds when waiting for a command response */
+#define RESP_TIMEOUT 5000
+
+/* 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 ([email protected]) for
ST-Ericsson.
+ * Kjell Andersson ([email protected]) for ST-Ericsson.
+ * License terms: GNU General Public License (GPL), version 2
+ *
+ * Linux Bluetooth Audio Driver for ST-Ericsson controller.
+ */
+
+#ifndef _CG2900_AUDIO_H_
+#define _CG2900_AUDIO_H_
+
+#include <linux/types.h>
+
+/*
+ * Digital Audio Interface configuration types
+ */
+
+/** CG2900_A2DP_MAX_AVDTP_HDR_LEN - Max length of a AVDTP header.
+ * Max length of a AVDTP header for an A2DP packet.
+ */
+#define CG2900_A2DP_MAX_AVDTP_HDR_LEN 25
+
+/**
+ * enum cg2900_dai_dir - Contains the DAI port directions alternatives.
+ * @DAI_DIR_B_RX_A_TX: Port B as Rx and port A as Tx.
+ * @DAI_DIR_B_TX_A_RX: Port B as Tx and port A as Rx.
+ */
+enum cg2900_dai_dir {
+ DAI_DIR_B_RX_A_TX = 0x00,
+ DAI_DIR_B_TX_A_RX = 0x01
+};
+
+/**
+ * enum cg2900_dai_mode - DAI mode alternatives.
+ * @DAI_MODE_SLAVE: Slave.
+ * @DAI_MODE_MASTER: Master.
+ */
+enum cg2900_dai_mode {
+ DAI_MODE_SLAVE = 0x00,
+ DAI_MODE_MASTER = 0x01
+};
+
+/**
+ * enum cg2900_dai_stream_ratio - Voice stream ratio alternatives.
+ * @STREAM_RATIO_FM16_VOICE16: FM 16kHz, Voice 16kHz.
+ * @STREAM_RATIO_FM16_VOICE8: FM 16kHz, Voice 8kHz.
+ * @STREAM_RATIO_FM48_VOICE16: FM 48kHz, Voice 16Khz.
+ * @STREAM_RATIO_FM48_VOICE8: FM 48kHz, Voice 8kHz.
+ *
+ * Contains the alternatives for the voice stream ratio between the
Audio stream
+ * sample rate and the Voice stream sample rate.
+ */
+enum cg2900_dai_stream_ratio {
+ STREAM_RATIO_FM16_VOICE16 = 0x01,
+ STREAM_RATIO_FM16_VOICE8 = 0x02,
+ STREAM_RATIO_FM48_VOICE16 = 0x03,
+ STREAM_RATIO_FM48_VOICE8 = 0x06
+};
+
+/**
+ * enum cg2900_dai_fs_duration - Frame sync duration alternatives.
+ * @SYNC_DURATION_8: 8 frames sync duration.
+ * @SYNC_DURATION_16: 16 frames sync duration.
+ * @SYNC_DURATION_24: 24 frames sync duration.
+ * @SYNC_DURATION_32: 32 frames sync duration.
+ * @SYNC_DURATION_48: 48 frames sync duration.
+ * @SYNC_DURATION_50: 50 frames sync duration.
+ * @SYNC_DURATION_64: 64 frames sync duration.
+ * @SYNC_DURATION_75: 75 frames sync duration.
+ * @SYNC_DURATION_96: 96 frames sync duration.
+ * @SYNC_DURATION_125: 125 frames sync duration.
+ * @SYNC_DURATION_128: 128 frames sync duration.
+ * @SYNC_DURATION_150: 150 frames sync duration.
+ * @SYNC_DURATION_192: 192 frames sync duration.
+ * @SYNC_DURATION_250: 250 frames sync duration.
+ * @SYNC_DURATION_256: 256 frames sync duration.
+ * @SYNC_DURATION_300: 300 frames sync duration.
+ * @SYNC_DURATION_384: 384 frames sync duration.
+ * @SYNC_DURATION_500: 500 frames sync duration.
+ * @SYNC_DURATION_512: 512 frames sync duration.
+ * @SYNC_DURATION_600: 600 frames sync duration.
+ * @SYNC_DURATION_768: 768 frames sync duration.
+ *
+ * This parameter sets the PCM frame sync duration. It is calculated as the
+ * ratio between the bit clock and the frame rate. For example, if the bit
+ * clock is 512 kHz and the stream sample rate is 8 kHz, the PCM frame sync
+ * duration is 512 / 8 = 64.
+ */
+enum cg2900_dai_fs_duration {
+ SYNC_DURATION_8 = 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
Thanks for you comments Alan.
I guess I didn't put enough comments in the code or the commit message
regarding this.
In fact we (ST-Ericsson) are writing drivers that e.g. maps to the V4L
framework. They then
call the API in this driver. This driver has never been intended to
map directly towards ALSA
or V4L. We have other drivers under development that does that.
/P-G
2010/10/22 Alan Cox <[email protected]>:
> On Fri, 22 Oct 2010 12:39:06 +0200
> Par-Gunnar Hjalmdahl <[email protected]> wrote:
>
>> 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.
>
> I don't see any ALSA hooks for audio or any V4L hooks for the FM radio.
> Surely the audio routing wants exposing as a mixer and the audio via
> ALSA, likewise FM radio is video4linux so that it'll have the standard
> interfaces that are expected by the rest of the Linux OS.
>
> Alan
>
On Fri, 22 Oct 2010 12:39:06 +0200
Par-Gunnar Hjalmdahl <[email protected]> wrote:
> 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.
I don't see any ALSA hooks for audio or any V4L hooks for the FM radio.
Surely the audio routing wants exposing as a mixer and the audio via
ALSA, likewise FM radio is video4linux so that it'll have the standard
interfaces that are expected by the rest of the Linux OS.
Alan