diff options
Diffstat (limited to 'drivers/staging/cg2900/mfd')
-rw-r--r-- | drivers/staging/cg2900/mfd/Makefile | 18 | ||||
-rw-r--r-- | drivers/staging/cg2900/mfd/cg2900_audio.c | 3530 | ||||
-rw-r--r-- | drivers/staging/cg2900/mfd/cg2900_char_devices.c | 719 | ||||
-rw-r--r-- | drivers/staging/cg2900/mfd/cg2900_chip.c | 3791 | ||||
-rw-r--r-- | drivers/staging/cg2900/mfd/cg2900_chip.h | 619 | ||||
-rw-r--r-- | drivers/staging/cg2900/mfd/cg2900_core.c | 719 | ||||
-rw-r--r-- | drivers/staging/cg2900/mfd/cg2900_core.h | 52 | ||||
-rw-r--r-- | drivers/staging/cg2900/mfd/cg2900_lib.c | 284 | ||||
-rw-r--r-- | drivers/staging/cg2900/mfd/cg2900_lib.h | 61 | ||||
-rw-r--r-- | drivers/staging/cg2900/mfd/cg2900_test.c | 402 | ||||
-rw-r--r-- | drivers/staging/cg2900/mfd/stlc2690_chip.c | 1671 | ||||
-rw-r--r-- | drivers/staging/cg2900/mfd/stlc2690_chip.h | 47 |
12 files changed, 11913 insertions, 0 deletions
diff --git a/drivers/staging/cg2900/mfd/Makefile b/drivers/staging/cg2900/mfd/Makefile new file mode 100644 index 00000000000..bdbd8de90ee --- /dev/null +++ b/drivers/staging/cg2900/mfd/Makefile @@ -0,0 +1,18 @@ +# +# Makefile for ST-Ericsson CG2900 connectivity combo controller +# + +ccflags-y := \ + -Idrivers/staging/cg2900/include + +obj-$(CONFIG_CG2900) += cg2900_core.o cg2900_lib.o +export-objs := cg2900_core.o cg2900_lib.o + +obj-$(CONFIG_CG2900) += cg2900_char_devices.o + +obj-$(CONFIG_CG2900_TEST) += cg2900_test.o + +obj-$(CONFIG_CG2900_CHIP) += cg2900_chip.o +obj-$(CONFIG_STLC2690_CHIP) += stlc2690_chip.o + +obj-$(CONFIG_CG2900_AUDIO) += cg2900_audio.o diff --git a/drivers/staging/cg2900/mfd/cg2900_audio.c b/drivers/staging/cg2900/mfd/cg2900_audio.c new file mode 100644 index 00000000000..2b21cc30962 --- /dev/null +++ b/drivers/staging/cg2900/mfd/cg2900_audio.c @@ -0,0 +1,3530 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson. + * Hemant Gupta (hemant.gupta@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Linux Bluetooth Audio Driver for ST-Ericsson CG2900 controller. + */ +#define NAME "cg2900_audio" +#define pr_fmt(fmt) NAME ": " fmt "\n" + +#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 <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci.h> + +#include "cg2900.h" +#include "cg2900_audio.h" +#include "cg2900_chip.h" + +#define MAX_NBR_OF_USERS 10 +#define FIRST_USER 1 + +/* + * This is a default ACL handle. It is necessary to provide to the chip, but + * does not actually do anything. + */ +#define DEFAULT_ACL_HANDLE 0x0001 + +/* Use a timeout of 5 seconds when waiting for a command response */ +#define RESP_TIMEOUT 5000 + +#define BT_DEV (info->dev_bt) +#define FM_DEV (info->dev_fm) + +/* FM Set control conversion macros for CG2905/10 */ +#define CG2910_FM_CMD_SET_CTRL_48000_HEX 0x12C0 +#define CG2910_FM_CMD_SET_CTRL_44100_HEX 0x113A + +/* Bluetooth error codes */ +#define HCI_BT_ERROR_NO_ERROR 0x00 + +/* Used to select proper API, ignoring subrevisions etc */ +enum chip_revision { + CG2900_CHIP_REV_PG1, + CG2900_CHIP_REV_PG2, + CG2905_CHIP_REV_PG1_1, + CG2910_CHIP_REV_PG1, + CG2910_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 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. + * @list: list_head struct. + * @state: Current state of the CG2900 Audio driver. + * @revision: Chip revision, used to select API. + * @misc_dev: The misc device created by this driver. + * @misc_registered: True if misc device is registered. + * @parent: Parent device. + * @dev_bt: Device registered by this driver for the BT + * audio channel. + * @dev_fm: Device registered by this driver for the FM + * audio channel. + * @filp: Current char device file pointer. + * @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. + * @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 + * CG2900 PG2 onwards chip API). + */ +struct audio_info { + struct list_head list; + enum main_state state; + enum chip_revision revision; + struct miscdevice misc_dev; + bool misc_registered; + struct device *parent; + struct device *dev_bt; + struct device *dev_fm; + struct file *filp; + struct mutex management_mutex; + struct mutex bt_mutex; + struct mutex fm_mutex; + int nbr_of_users_active; + 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; + u8 stream_ids[16]; +}; + +/** + * struct audio_user - CG2900 audio user info structure. + * @session: Stored session for the char device. + * @resp_state: State for controller communications. + * @info: CG2900 audio info structure. + */ +struct audio_user { + int session; + enum chip_resp_state resp_state; + struct audio_info *info; +}; + +/** + * struct audio_cb_info - Callback info structure registered in @user_data. + * @user: Audio user currently awaiting data on the channel. + * @wq: Wait queue for this channel. + * @skb_queue: Sk buffer queue. + */ +struct audio_cb_info { + struct audio_user *user; + wait_queue_head_t wq; + struct sk_buff_head skb_queue; +}; + +/** + * 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. + * @info: CG2900 audio info struct. + * @rx_queue: Data queue. + */ +struct char_dev_info { + int session; + u8 *stored_data; + int stored_data_len; + struct mutex management_mutex; + struct mutex rw_mutex; + struct audio_info *info; + struct sk_buff_head rx_queue; +}; + +/* + * cg2900_audio_devices - List of active CG2900 audio devices. + */ +LIST_HEAD(cg2900_audio_devices); + +/* + * cg2900_audio_sessions - Pointers to currently opened sessions (maps + * session ID to user info). + */ +static struct audio_user *cg2900_audio_sessions[MAX_NBR_OF_USERS]; + +/* + * 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(struct audio_info *info, + enum cg2900_endpoint_sample_rate srate) +{ + /* + * For CG2910, Set the external sample rate (host side) + * of the digital output in units of [10Hz] + */ + if (info->revision == CG2910_CHIP_REV_PG1 || + info->revision == CG2910_CHIP_REV_PG2 || + info->revision == CG2905_CHIP_REV_PG1_1) { + if (srate > ENDPOINT_SAMPLE_RATE_44_1_KHZ) + return CG2910_FM_CMD_SET_CTRL_48000_HEX; + else + return CG2910_FM_CMD_SET_CTRL_44100_HEX; + + } else { + 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; + } +} + +/** + * get_info() - Return info structure for this device. + * @dev: Current device. + * + * This function returns the info structure on the following basis: + * * If dev is NULL return first info struct found. If none is found return + * NULL. + * * If dev is valid we will return corresponding info struct if dev is the + * parent of the info struct or if dev's parent is the parent of the info + * struct. + * * If dev is valid and no info structure is found, a new info struct is + * allocated, initialized, and returned. + * + * Returns: + * Pointer to info struct if there is no error. + * NULL if NULL was supplied and no info structure exist. + * ERR_PTR(-ENOMEM) if allocation fails. + */ +static struct audio_info *get_info(struct device *dev) +{ + struct list_head *cursor; + struct audio_info *tmp; + struct audio_info *info = NULL; + + /* + * Find the info structure for dev. If NULL is supplied for dev + * just return first device found. + */ + list_for_each(cursor, &cg2900_audio_devices) { + tmp = list_entry(cursor, struct audio_info, list); + if (!dev || tmp->parent == dev->parent || tmp->parent == dev) { + info = tmp; + break; + } + } + + if (!dev || info) + return info; + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + dev_err(dev, "Could not allocate info struct\n"); + return ERR_PTR(-ENOMEM); + } + info->parent = dev->parent; + + /* Initiate the mutexes */ + mutex_init(&(info->management_mutex)); + mutex_init(&(info->bt_mutex)); + mutex_init(&(info->fm_mutex)); + mutex_init(&(info->endpoints.management_mutex)); + + /* Initiate the endpoint list */ + INIT_LIST_HEAD(&info->endpoints.ep_list); + + list_add_tail(&info->list, &cg2900_audio_devices); + + dev_info(dev, "CG2900 device added\n"); + return info; +} + +/** + * 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); +} + +/** + * device_removed() - Remove device from list if there are no channels left. + * @info: CG2900 audio info structure. + */ +static void device_removed(struct audio_info *info) +{ + struct list_head *cursor; + struct audio_info *tmp; + + if (info->dev_bt || info->dev_fm) + /* There are still devices active */ + return; + + /* Find the stored info structure */ + list_for_each(cursor, &cg2900_audio_devices) { + tmp = list_entry(cursor, struct audio_info, list); + if (tmp == info) { + list_del(cursor); + break; + } + } + + flush_endpoint_list(&info->endpoints); + + mutex_destroy(&info->management_mutex); + mutex_destroy(&info->bt_mutex); + mutex_destroy(&info->fm_mutex); + mutex_destroy(&info->endpoints.management_mutex); + + kfree(info); + pr_info("CG2900 Audio device removed"); +} + +/** + * 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_user_data *dev, struct sk_buff *skb) +{ + struct audio_cb_info *cb_info; + + cb_info = cg2900_get_usr(dev); + + if (!(cb_info->user)) { + dev_err(dev->dev, "NULL supplied as cb_info->user\n"); + return; + } + + /* Mark that packet has been received */ + dev_dbg(dev->dev, "New resp_state: RESP_RECEIVED"); + cb_info->user->resp_state = RESP_RECEIVED; + skb_queue_tail(&cb_info->skb_queue, skb); + wake_up_all(&cb_info->wq); +} + +/** + * reset_cb() - Reset callback function. + * @dev: CG2900_Core device resetting. + */ +static void reset_cb(struct cg2900_user_data *dev) +{ + struct audio_info *info; + + dev_dbg(dev->dev, "reset_cb\n"); + + info = dev_get_drvdata(dev->dev); + mutex_lock(&info->management_mutex); + info->nbr_of_users_active = 0; + info->state = RESET; + mutex_unlock(&info->management_mutex); +} + +/** + * 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) { + pr_err("Calling with invalid session %d", session); + return NULL; + } + + audio_user = cg2900_audio_sessions[session]; + if (!audio_user) + pr_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) { + pr_err("add_endpoint: 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; +} + +/** + * new_stream_id() - Allocate a new stream id. + * @info: Current audio info struct. + * + * Returns: + * 0-127 new valid id. + * -ENOMEM if no id is available. + */ +static s8 new_stream_id(struct audio_info *info) +{ + int r; + + mutex_lock(&info->management_mutex); + + r = find_first_zero_bit(info->stream_ids, + 8 * sizeof(info->stream_ids)); + + if (r >= 8 * sizeof(info->stream_ids)) { + r = -ENOMEM; + goto out; + } + + set_bit(r, (unsigned long int *)info->stream_ids); + +out: + mutex_unlock(&info->management_mutex); + return r; +} + +/** + * release_stream_id() - Release a stream id. + * @info: Current audio info struct. + * @id: Stream to release. + */ +static void release_stream_id(struct audio_info *info, u8 id) +{ + if (id >= 8 * sizeof(info->stream_ids)) + return; + + mutex_lock(&info->management_mutex); + clear_bit(id, (unsigned long int *)info->stream_ids); + mutex_unlock(&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; + int res; + struct sk_buff *skb; + struct fm_leg_cmd_cmpl *pkt; + u16 rsp_cmd; + struct audio_cb_info *cb_info; + struct audio_info *info; + struct cg2900_user_data *pf_data; + + info = audio_user->info; + pf_data = dev_get_platdata(info->dev_fm); + cb_info = cg2900_get_usr(pf_data); + + /* + * Wait for callback to receive command complete and then wake us up + * again. + */ + res = wait_event_timeout(cb_info->wq, + audio_user->resp_state == RESP_RECEIVED, + msecs_to_jiffies(RESP_TIMEOUT)); + if (!res) { + dev_err(FM_DEV, "Timeout while waiting for return packet\n"); + return -ECOMM; + } else if (res < 0) { + dev_err(FM_DEV, + "Error %d occurred while waiting for return packet\n", + res); + return -ECOMM; + } + + /* OK, now we should have received answer. Let's check it. */ + skb = skb_dequeue_tail(&cb_info->skb_queue); + if (!skb) { + dev_err(FM_DEV, "No skb in queue when it should be there\n"); + 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) { + dev_err(FM_DEV, + "Received unknown FM packet. 0x%X %X %X %X %X\n", + 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) { + dev_err(FM_DEV, + "Received unexpected packet func 0x%X cmd 0x%04X\n", + pkt->fm_function, rsp_cmd); + err = -EIO; + goto error_handling_free_skb; + } + + if (pkt->cmd_status != CG2900_FM_CMD_STATUS_COMMAND_SUCCEEDED) { + dev_err(FM_DEV, "FM Command failed (%d)\n", 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; + int res; + struct sk_buff *skb; + struct bt_cmd_cmpl_event *evt; + u16 opcode; + struct audio_cb_info *cb_info; + struct audio_info *info; + struct cg2900_user_data *pf_data; + + info = audio_user->info; + pf_data = dev_get_platdata(info->dev_bt); + cb_info = cg2900_get_usr(pf_data); + + /* + * Wait for callback to receive command complete and then wake us up + * again. + */ + res = wait_event_timeout(cb_info->wq, + audio_user->resp_state == RESP_RECEIVED, + msecs_to_jiffies(RESP_TIMEOUT)); + if (!res) { + dev_err(BT_DEV, "Timeout while waiting for return packet\n"); + return -ECOMM; + } else if (res < 0) { + /* We timed out or an error occurred */ + dev_err(BT_DEV, + "Error %d occurred while waiting for return packet\n", + res); + return -ECOMM; + } + + /* OK, now we should have received answer. Let's check it. */ + skb = skb_dequeue_tail(&cb_info->skb_queue); + if (!skb) { + dev_err(BT_DEV, "No skb in queue when it should be there\n"); + return -EIO; + } + + evt = (struct bt_cmd_cmpl_event *)skb->data; + if (evt->eventcode != HCI_EV_CMD_COMPLETE) { + dev_err(BT_DEV, + "We did not receive the event we expected (0x%X)\n", + evt->eventcode); + err = -EIO; + goto error_handling_free_skb; + } + + opcode = le16_to_cpu(evt->opcode); + if (opcode != rsp) { + dev_err(BT_DEV, + "Received cmd complete for unexpected command: " + "0x%04X\n", opcode); + err = -EIO; + goto error_handling_free_skb; + } + + if (evt->status != HCI_BT_ERROR_NO_ERROR) { + dev_err(BT_DEV, "Received command complete with err %d\n", + evt->status); + err = -EIO; + /* + * In data there might be more detailed error code. + * Let's copy it. + */ + } + + /* + * 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_delete_stream() - Delete 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 delete an audio stream defined by a stream + * handle. + * + * 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 send_vs_delete_stream(struct audio_user *audio_user, + unsigned int stream_handle) +{ + int err = 0; + struct sk_buff *skb; + u16 opcode; + struct audio_info *info = audio_user->info; + struct cg2900_user_data *pf_data = dev_get_platdata(info->dev_bt); + struct audio_cb_info *cb_info = cg2900_get_usr(pf_data); + + /* Now delete the stream - format command... */ + if (info->revision == CG2900_CHIP_REV_PG1) { + struct bt_vs_reset_session_cfg_cmd *cmd; + + dev_dbg(BT_DEV, "BT: HCI_VS_Reset_Session_Configuration\n"); + + skb = pf_data->alloc_skb(sizeof(*cmd), GFP_KERNEL); + if (!skb) { + dev_err(BT_DEV, "Could not allocate skb\n"); + err = -ENOMEM; + return err; + } + + 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; + + dev_dbg(BT_DEV, "BT: HCI_VS_Delete_Stream\n"); + + skb = pf_data->alloc_skb(sizeof(*cmd), GFP_KERNEL); + if (!skb) { + dev_err(BT_DEV, "Could not allocate skb\n"); + err = -ENOMEM; + return err; + } + + 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->user = audio_user; + dev_dbg(BT_DEV, "New resp_state: WAITING\n"); + audio_user->resp_state = WAITING; + + err = pf_data->write(pf_data, skb); + if (err) { + dev_err(BT_DEV, "Error %d occurred while transmitting skb\n", + err); + goto error_handling_free_skb; + } + + /* wait for response */ + if (info->revision == CG2900_CHIP_REV_PG1) { + err = receive_bt_cmd_complete(audio_user, opcode, NULL, 0); + } else { + u8 vs_err; + + /* All commands on CG2900 PG2 onwards + * API returns one byte extra status */ + err = receive_bt_cmd_complete(audio_user, opcode, + &vs_err, sizeof(vs_err)); + + if (err) + dev_err(BT_DEV, + "VS_DELETE_STREAM - failed with error 0x%02X\n", + vs_err); + else + release_stream_id(info, stream_handle); + } + + return err; + +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; + struct audio_cb_info *cb_info; + struct audio_info *info; + struct cg2900_user_data *pf_data; + + info = user->info; + pf_data = dev_get_platdata(info->dev_bt); + cb_info = cg2900_get_usr(pf_data); + + dev_dbg(BT_DEV, "BT: HCI_VS_Session_Control handle: %d cmd: %d\n", + stream_handle, command); + + skb = pf_data->alloc_skb(sizeof(*pkt), GFP_KERNEL); + if (!skb) { + dev_err(BT_DEV, + "send_vs_session_ctrl: Could not allocate skb\n"); + 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->user = user; + dev_dbg(BT_DEV, "New resp_state: WAITING\n"); + user->resp_state = WAITING; + + /* Send packet to controller */ + err = pf_data->write(pf_data, skb); + if (err) { + dev_err(BT_DEV, "Error %d occurred while transmitting skb\n", + err); + kfree_skb(skb); + goto finished; + } + + err = receive_bt_cmd_complete(user, CG2900_BT_VS_SESSION_CTRL, + NULL, 0); +finished: + dev_dbg(BT_DEV, "New resp_state: IDLE\n"); + 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)(struct audio_info *, 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; + struct audio_cb_info *cb_info; + struct audio_info *info; + struct cg2900_user_data *pf_data; + + info = user->info; + pf_data = dev_get_platdata(info->dev_bt); + cb_info = cg2900_get_usr(pf_data); + + dev_dbg(BT_DEV, "BT: HCI_VS_Set_Session_Configuration\n"); + + skb = pf_data->alloc_skb(sizeof(*pkt), GFP_KERNEL); + if (!skb) { + dev_err(BT_DEV, + "send_vs_session_config: Could not allocate skb\n"); + 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(info, priv_data, &pkt->stream); + + cb_info->user = user; + dev_dbg(BT_DEV, "New resp_state: WAITING\n"); + user->resp_state = WAITING; + + /* Send packet to controller */ + err = pf_data->write(pf_data, skb); + if (err) { + dev_err(BT_DEV, "Error %d occurred while transmitting skb\n", + 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: + dev_dbg(BT_DEV, "New resp_state: IDLE\n"); + 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; + struct audio_cb_info *cb_info; + struct audio_info *info; + struct cg2900_user_data *pf_data; + + info = user->info; + pf_data = dev_get_platdata(info->dev_fm); + cb_info = cg2900_get_usr(pf_data); + + dev_dbg(FM_DEV, "send_fm_write_1_param cmd 0x%X param 0x%X\n", + command, param); + + /* base package + one parameter */ + len = sizeof(*cmd) + sizeof(cmd->fm_cmd.data[0]); + + skb = pf_data->alloc_skb(len, GFP_KERNEL); + if (!skb) { + dev_err(FM_DEV, + "send_fm_write_1_param: Could not allocate skb\n"); + 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->user = user; + dev_dbg(FM_DEV, "New resp_state: WAITING\n"); + user->resp_state = WAITING; + + /* Send packet to controller */ + err = pf_data->write(pf_data, skb); + if (err) { + dev_err(FM_DEV, "Error %d occurred while transmitting skb\n", + err); + kfree_skb(skb); + goto finished; + } + + err = receive_fm_write_response(user, command); +finished: + dev_dbg(FM_DEV, "New resp_state: IDLE\n"); + 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; + struct audio_cb_info *cb_info; + struct audio_info *info; + struct cg2900_user_data *pf_data; + + info = user->info; + pf_data = dev_get_platdata(info->dev_bt); + cb_info = cg2900_get_usr(pf_data); + + dev_dbg(BT_DEV, "send_vs_stream_ctrl stream %d command %d\n", stream, + command); + + /* basic length + one stream */ + len = sizeof(*cmd) + sizeof(cmd->stream[0]); + + skb = pf_data->alloc_skb(len, GFP_KERNEL); + if (!skb) { + dev_err(BT_DEV, "send_vs_stream_ctrl:Could not allocate skb\n"); + 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->user = user; + dev_dbg(BT_DEV, "New resp_state: WAITING\n"); + user->resp_state = WAITING; + + /* Send packet to controller */ + err = pf_data->write(pf_data, skb); + if (err) { + dev_err(BT_DEV, "Error %d occurred while transmitting skb\n", + err); + kfree_skb(skb); + goto finished; + } + + /* All commands on CG2900 PG2 onwards + * API returns one byte extra status */ + err = receive_bt_cmd_complete(user, + CG2900_MC_VS_STREAM_CONTROL, + &vs_err, sizeof(vs_err)); + if (err) + dev_err(BT_DEV, + "VS_STREAM_CONTROL - failed with error 0x%02x\n", + vs_err); + +finished: + dev_dbg(BT_DEV, "New resp_state: IDLE\n"); + 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; + struct audio_cb_info *cb_info; + struct audio_info *info; + struct cg2900_user_data *pf_data; + + info = user->info; + pf_data = dev_get_platdata(info->dev_bt); + cb_info = cg2900_get_usr(pf_data); + + dev_dbg(BT_DEV, + "send_vs_create_stream inport %d outport %d order %d\n", + inport, outport, order); + + id = new_stream_id(info); + if (id < 0) { + dev_err(BT_DEV, "No free stream id\n"); + err = -EIO; + goto finished; + } + + skb = pf_data->alloc_skb(sizeof(*cmd), GFP_KERNEL); + if (!skb) { + dev_err(BT_DEV, + "send_vs_create_stream: Could not allocate skb\n"); + 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->user = user; + dev_dbg(BT_DEV, "New resp_state: WAITING\n"); + user->resp_state = WAITING; + + /* Send packet to controller */ + err = pf_data->write(pf_data, skb); + if (err) { + dev_err(BT_DEV, "Error %d occurred while transmitting skb\n", + err); + kfree_skb(skb); + goto finished_release_id; + } + + /* All commands on CG2900 PG2 onwards + * API returns one byte extra status */ + err = receive_bt_cmd_complete(user, + CG2900_MC_VS_CREATE_STREAM, + &vs_err, sizeof(vs_err)); + if (err) { + dev_err(BT_DEV, + "VS_CREATE_STREAM - failed with error 0x%02x\n", + vs_err); + goto finished_release_id; + } + + err = id; + goto finished; + +finished_release_id: + release_stream_id(info, id); +finished: + dev_dbg(BT_DEV, "New resp_state: IDLE\n"); + 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; + struct audio_cb_info *cb_info; + struct audio_info *info; + struct cg2900_user_data *pf_data; + + info = user->info; + pf_data = dev_get_platdata(info->dev_bt); + cb_info = cg2900_get_usr(pf_data); + + dev_dbg(BT_DEV, "send_vs_port_cfg len %d\n", cfglen); + + skb = pf_data->alloc_skb(sizeof(*cmd) + cfglen, GFP_KERNEL); + if (!skb) { + dev_err(BT_DEV, "send_vs_port_cfg: Could not allocate skb\n"); + 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->user = user; + dev_dbg(BT_DEV, "New resp_state: WAITING\n"); + user->resp_state = WAITING; + + err = pf_data->write(pf_data, skb); + if (err) { + dev_err(BT_DEV, "Error %d occurred while transmitting skb\n", + err); + kfree_skb(skb); + goto finished; + } + + /* All commands on CG2900 PG2 onwards + * API returns one byte extra status */ + err = receive_bt_cmd_complete(user, CG2900_MC_VS_PORT_CONFIG, + &vs_err, sizeof(vs_err)); + if (err) + dev_err(BT_DEV, "VS_PORT_CONFIG - failed with error 0x%02x\n", + vs_err); + +finished: + dev_dbg(BT_DEV, "New resp_state: IDLE\n"); + 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; + struct audio_info *info = audio_user->info; + struct cg2900_user_data *pf_data = dev_get_platdata(info->dev_bt); + struct audio_cb_info *cb_info = cg2900_get_usr(pf_data); + + dev_dbg(BT_DEV, "set_dai_config_pg1 port %d\n", config->port); + + /* + * Use mutex to assure that only ONE command is sent at any time on + * each channel. + */ + mutex_lock(&info->bt_mutex); + + /* Allocate the sk_buffer. The length is actually a max length since + * length varies depending on logical transport. + */ + skb = pf_data->alloc_skb(CG2900_BT_LEN_VS_SET_HARDWARE_CONFIG, + GFP_KERNEL); + if (!skb) { + dev_err(BT_DEV, "set_dai_config_pg1: Could not allocate skb\n"); + 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(&info->management_mutex); + memcpy(&info->i2s_config, &config->conf.i2s, + sizeof(config->conf.i2s)); + info->i2s_config_known = true; + mutex_unlock(&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; + + /* + * CG2900 PG1 chips don't support I2S over the PCM/I2S bus, + * and CG2900 PG2 onwards chips don't use this command + */ + if (i2s_pcm->protocol != PORT_PROTOCOL_PCM) { + dev_err(BT_DEV, + "I2S not supported over the PCM/I2S bus\n"); + 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(&info->management_mutex); + memcpy(&info->i2s_pcm_config, &config->conf.i2s_pcm, + sizeof(config->conf.i2s_pcm)); + info->i2s_pcm_config_known = true; + mutex_unlock(&info->management_mutex); + break; + + default: + dev_err(BT_DEV, "Unknown port configuration %d\n", + config->port); + err = -EACCES; + goto error_handling_free_skb; + }; + + cb_info->user = audio_user; + dev_dbg(BT_DEV, "New resp_state: WAITING\n"); + audio_user->resp_state = WAITING; + + /* Send packet to controller */ + err = pf_data->write(pf_data, skb); + if (err) { + dev_err(BT_DEV, "Error %d occurred while transmitting skb\n", + 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: + dev_dbg(BT_DEV, "New resp_state: IDLE\n"); + audio_user->resp_state = IDLE; + mutex_unlock(&info->bt_mutex); + return err; +} + +/** + * set_dai_config_pg2() - Internal implementation of + * cg2900_audio_set_dai_config for CG2900 PG2 onwards hardware. + * @audio_user: Pointer to audio user struct. + * @config: Pointer to the configuration to set. + * + * Sets the Digital Audio Interface (DAI) configuration for + * CG2900 PG2 onwards 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; + struct audio_info *info = audio_user->info; + + dev_dbg(BT_DEV, "set_dai_config_pg2 port %d\n", config->port); + + /* + * Use mutex to assure that only ONE command is sent at any time on + * each channel. + */ + mutex_lock(&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(&info->management_mutex); + memcpy(&(info->i2s_config), &(config->conf.i2s), + sizeof(config->conf.i2s)); + info->i2s_config_known = true; + mutex_unlock(&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(&info->management_mutex); + memcpy(&(info->i2s_pcm_config), &(config->conf.i2s_pcm), + sizeof(config->conf.i2s_pcm)); + info->i2s_pcm_config_known = true; + mutex_unlock(&info->management_mutex); + + /* Send */ + err = send_vs_port_cfg(audio_user, CG2900_MC_PORT_PCM_I2S, + &pcm_cfg, sizeof(pcm_cfg)); + break; + + default: + dev_err(BT_DEV, "Unknown port configuration %d\n", + config->port); + err = -EACCES; + }; + + mutex_unlock(&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. + * @info: Audio info structure. + * @_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(struct audio_info *info, 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 (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 = 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; + struct audio_info *info = audio_user->info; + + dev_dbg(FM_DEV, "conn_start_i2s_to_fm_rx\n"); + + fm_config = find_endpoint(ENDPOINT_FM_RX, &info->endpoints); + if (!fm_config) { + dev_err(FM_DEV, "FM RX not configured before stream start\n"); + return -EIO; + } + + if (!(info->i2s_config_known)) { + dev_err(FM_DEV, + "I2S DAI not configured before stream start\n"); + return -EIO; + } + + /* + * Use mutex to assure that only ONE command is sent at any + * time on each channel. + */ + mutex_lock(&info->fm_mutex); + mutex_lock(&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(info, fm_config->fm.sample_rate)); + if (err) + goto finished_unlock_mutex; + + /* Set up the stream */ + if (info->revision == CG2900_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; + dev_dbg(FM_DEV, "stream_handle set to %d\n", *stream_handle); + + /* Now start the stream */ + if (info->revision == CG2900_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); + /*Let's delete a stream.*/ + if (err < 0) { + dev_dbg(BT_DEV, "Could not start a stream."); + (void)send_vs_delete_stream(audio_user, *stream_handle); + } + +finished_unlock_mutex: + dev_dbg(FM_DEV, "New resp_state: IDLE\n"); + audio_user->resp_state = IDLE; + mutex_unlock(&info->bt_mutex); + mutex_unlock(&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; + struct audio_info *info = audio_user->info; + + dev_dbg(FM_DEV, "conn_start_i2s_to_fm_tx\n"); + + fm_config = find_endpoint(ENDPOINT_FM_TX, &info->endpoints); + if (!fm_config) { + dev_err(FM_DEV, "FM TX not configured before stream start\n"); + return -EIO; + } + + if (!(info->i2s_config_known)) { + dev_err(FM_DEV, + "I2S DAI not configured before stream start\n"); + return -EIO; + } + + /* + * Use mutex to assure that only ONE command is sent at any time + * on each channel. + */ + mutex_lock(&info->fm_mutex); + mutex_lock(&info->bt_mutex); + + /* + * Select Audio Input Source by sending HCI_Write command with + * AIP_SetMode. + */ + if (info->revision == CG2900_CHIP_REV_PG1 || + info->revision == CG2900_CHIP_REV_PG2) { + dev_dbg(FM_DEV, "FM: AIP_SetMode\n"); + 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. + */ + dev_dbg(FM_DEV, "FM: AIP_BT_SetControl\n"); + err = send_fm_write_1_param( + audio_user, CG2900_FM_CMD_ID_AIP_BT_SET_CTRL, + fm_get_conversion(info, 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. + */ + dev_dbg(FM_DEV, "FM: AIP_BT_SetMode\n"); + 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 (info->revision == CG2900_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; + dev_dbg(FM_DEV, "stream_handle set to %d\n", *stream_handle); + + /* Now start the stream */ + if (info->revision == CG2900_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); + /* Let's delete and release stream.*/ + if (err < 0) { + dev_dbg(BT_DEV, "Could not start a stream."); + (void)send_vs_delete_stream(audio_user, *stream_handle); + } + +finished_unlock_mutex: + dev_dbg(FM_DEV, "New resp_state: IDLE\n"); + audio_user->resp_state = IDLE; + mutex_unlock(&info->bt_mutex); + mutex_unlock(&info->fm_mutex); + return err; +} + +/** + * config_pcm_sco_stream() - Callback for @send_vs_session_config. + * @info: Audio info structure. + * @_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(struct audio_info *info, 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_ACL_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, + info->i2s_pcm_config.slot_0_used); + SESSIONCFG_PCM_SET_USED(cfg->outport, 1, + info->i2s_pcm_config.slot_1_used); + SESSIONCFG_PCM_SET_USED(cfg->outport, 2, + info->i2s_pcm_config.slot_2_used); + SESSIONCFG_PCM_SET_USED(cfg->outport, 3, + info->i2s_pcm_config.slot_3_used); + + cfg->outport.pcm.slot_start[0] = + info->i2s_pcm_config.slot_0_start; + cfg->outport.pcm.slot_start[1] = + info->i2s_pcm_config.slot_1_start; + cfg->outport.pcm.slot_start[2] = + info->i2s_pcm_config.slot_2_start; + cfg->outport.pcm.slot_start[3] = + 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; + struct audio_info *info = audio_user->info; + + dev_dbg(BT_DEV, "conn_start_pcm_to_sco\n"); + + bt_config = find_endpoint(ENDPOINT_BT_SCO_INOUT, &info->endpoints); + if (!bt_config) { + dev_err(BT_DEV, "BT not configured before stream start\n"); + return -EIO; + } + + if (!(info->i2s_pcm_config_known)) { + dev_err(BT_DEV, + "I2S_PCM DAI not configured before stream start\n"); + return -EIO; + } + + /* + * Use mutex to assure that only ONE command is sent at any time on each + * channel. + */ + mutex_lock(&info->bt_mutex); + + /* Set up the stream */ + if (info->revision == CG2900_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_ACL_HANDLE; + 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; + dev_dbg(BT_DEV, "stream_handle set to %d\n", *stream_handle); + + /* Now start the stream */ + if (info->revision == CG2900_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); + /* Let's delete and release stream.*/ + if (err < 0) { + dev_dbg(BT_DEV, "Could not start a stream."); + (void)send_vs_delete_stream(audio_user, *stream_handle); + } + +finished_unlock_mutex: + dev_dbg(BT_DEV, "New resp_state: IDLE\n"); + audio_user->resp_state = IDLE; + mutex_unlock(&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 audio_info *info = audio_user->info; + + dev_dbg(BT_DEV, "conn_stop_stream handle %d\n", stream_handle); + + /* + * Use mutex to assure that only ONE command is sent at any + * time on each channel. + */ + mutex_lock(&info->bt_mutex); + + /* Now stop the stream */ + if (info->revision == CG2900_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; + + err = send_vs_delete_stream(audio_user, stream_handle); + +finished_unlock_mutex: + dev_dbg(BT_DEV, "New resp_state: IDLE\n"); + audio_user->resp_state = IDLE; + mutex_unlock(&info->bt_mutex); + return err; +} + +/** + * cg2900_audio_get_devices() - Returns connected CG2900 Audio devices. + * @devices: Array of CG2900 Audio devices. + * @size: Max number of devices in array. + * + * Returns: + * 0 if no devices exist. + * > 0 is the number of devices inserted in the list. + * -EINVAL upon bad input parameter. + */ +int cg2900_audio_get_devices(struct device *devices[], __u8 size) +{ + struct list_head *cursor; + struct audio_info *tmp; + int i = 0; + + if (!size) { + pr_err("No space to insert devices into list\n"); + return 0; + } + + if (!devices) { + pr_err("NULL submitted as devices array\n"); + return -EINVAL; + } + + /* + * Go through and store the devices. If NULL is supplied for dev + * just return first device found. + */ + list_for_each(cursor, &cg2900_audio_devices) { + tmp = list_entry(cursor, struct audio_info, list); + devices[i] = tmp->parent; + i++; + if (i == size) + break; + } + return i; +} +EXPORT_SYMBOL_GPL(cg2900_audio_get_devices); + +/** + * 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. + * @parent: Parent device representing the CG2900 controller connected. + * If NULL is supplied the first available device is used. + * + * Returns: + * 0 if there is no error. + * -EACCES if no info structure can be found. + * -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. + * Error codes from get_info. + */ +int cg2900_audio_open(unsigned int *session, struct device *parent) +{ + int err = 0; + int i; + struct audio_info *info; + struct cg2900_user_data *pf_data_bt; + struct cg2900_user_data *pf_data_fm; + + pr_debug("cg2900_audio_open"); + + info = get_info(parent); + if (!info) { + pr_err("No audio info exist"); + return -EACCES; + } else if (IS_ERR(info)) + return PTR_ERR(info); + + if (!session) { + pr_err("NULL supplied as session"); + return -EINVAL; + } + + mutex_lock(&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 && cg2900_audio_sessions[i]; + i++) + ; /* Just loop until found or end reached */ + + if (i >= MAX_NBR_OF_USERS) { + pr_err("Couldn't find free user"); + err = -EMFILE; + goto finished; + } + + cg2900_audio_sessions[i] = + kzalloc(sizeof(*(cg2900_audio_sessions[0])), GFP_KERNEL); + if (!cg2900_audio_sessions[i]) { + pr_err("Could not allocate user"); + err = -ENOMEM; + goto finished; + } + pr_debug("Found free session %d", i); + *session = i; + info->nbr_of_users_active++; + + cg2900_audio_sessions[*session]->resp_state = IDLE; + cg2900_audio_sessions[*session]->session = *session; + cg2900_audio_sessions[*session]->info = info; + + pf_data_bt = dev_get_platdata(info->dev_bt); + pf_data_fm = dev_get_platdata(info->dev_fm); + + if (info->nbr_of_users_active == 1) { + struct cg2900_rev_data rev_data; + + /* + * First user so register to CG2900 Core. + * First the BT audio device. + */ + err = pf_data_bt->open(pf_data_bt); + if (err) { + dev_err(BT_DEV, "Failed to open BT audio channel\n"); + goto error_handling; + } + + /* Then the FM audio device */ + err = pf_data_fm->open(pf_data_fm); + if (err) { + dev_err(FM_DEV, "Failed to open FM audio channel\n"); + goto error_handling; + } + + /* Read chip revision data */ + if (!pf_data_bt->get_local_revision(pf_data_bt, &rev_data)) { + pr_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: + info->revision = CG2900_CHIP_REV_PG1; + break; + + case CG2900_PG2_REV: + info->revision = CG2900_CHIP_REV_PG2; + break; + + case CG2905_PG1_1_REV: + info->revision = CG2905_CHIP_REV_PG1_1; + break; + + case CG2910_PG1_REV: + info->revision = CG2910_CHIP_REV_PG1; + break; + + case CG2910_PG2_REV: + info->revision = CG2910_CHIP_REV_PG2; + break; + + default: + pr_err("Chip rev 0x%04X sub 0x%04X not supported", + rev_data.revision, rev_data.sub_version); + err = -EIO; + goto error_handling; + } + + info->state = OPENED; + } + + pr_info("Session %d opened", *session); + + goto finished; + +error_handling: + if (pf_data_fm->opened) + pf_data_fm->close(pf_data_fm); + if (pf_data_bt->opened) + pf_data_bt->close(pf_data_bt); + info->nbr_of_users_active--; + kfree(cg2900_audio_sessions[*session]); + cg2900_audio_sessions[*session] = NULL; +finished: + mutex_unlock(&info->management_mutex); + return err; +} +EXPORT_SYMBOL_GPL(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; + struct audio_info *info; + struct cg2900_user_data *pf_data_bt; + struct cg2900_user_data *pf_data_fm; + + pr_debug("cg2900_audio_close"); + + if (!session) { + pr_err("NULL pointer supplied"); + return -EINVAL; + } + + audio_user = get_session_user(*session); + if (!audio_user) { + pr_err("Invalid session ID"); + return -EINVAL; + } + + info = audio_user->info; + + if (info->state != OPENED) { + dev_err(BT_DEV, "Audio driver not open\n"); + return -EIO; + } + + mutex_lock(&info->management_mutex); + + pf_data_bt = dev_get_platdata(info->dev_bt); + pf_data_fm = dev_get_platdata(info->dev_fm); + + if (!cg2900_audio_sessions[*session]) { + dev_err(BT_DEV, "Session %d not opened\n", *session); + err = -EACCES; + goto err_unlock_mutex; + } + + kfree(cg2900_audio_sessions[*session]); + cg2900_audio_sessions[*session] = NULL; + + info->nbr_of_users_active--; + if (info->nbr_of_users_active == 0) { + /* No more sessions open. Close channels */ + pf_data_fm->close(pf_data_fm); + pf_data_bt->close(pf_data_bt); + info->state = CLOSED; + } + + dev_info(BT_DEV, "Session %d closed\n", *session); + + *session = 0; + +err_unlock_mutex: + mutex_unlock(&info->management_mutex); + return err; +} +EXPORT_SYMBOL_GPL(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 audio_info *info; + + pr_debug("cg2900_audio_set_dai_config session %d", session); + + audio_user = get_session_user(session); + if (!audio_user) + return -EINVAL; + + info = audio_user->info; + + if (info->state != OPENED) { + dev_err(BT_DEV, "Audio driver not open\n"); + return -EIO; + } + + /* Different command is used for CG2900 PG1 */ + if (info->revision == CG2900_CHIP_REV_PG1) + err = set_dai_config_pg1(audio_user, config); + else + err = set_dai_config_pg2(audio_user, config); + + return err; +} +EXPORT_SYMBOL_GPL(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; + struct audio_info *info; + + pr_debug("cg2900_audio_get_dai_config session %d", session); + + if (!config) { + pr_err("NULL supplied as config structure"); + return -EINVAL; + } + + audio_user = get_session_user(session); + if (!audio_user) + return -EINVAL; + + info = audio_user->info; + + if (info->state != OPENED) { + dev_err(BT_DEV, "Audio driver not open\n"); + return -EIO; + } + + /* + * 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(&info->management_mutex); + if (info->i2s_config_known) + memcpy(&config->conf.i2s, + &info->i2s_config, + sizeof(config->conf.i2s)); + else + err = -EIO; + mutex_unlock(&info->management_mutex); + break; + + case PORT_1_I2S_PCM: + mutex_lock(&info->management_mutex); + if (info->i2s_pcm_config_known) + memcpy(&config->conf.i2s_pcm, + &info->i2s_pcm_config, + sizeof(config->conf.i2s_pcm)); + else + err = -EIO; + mutex_unlock(&info->management_mutex); + break; + + default: + dev_err(BT_DEV, "Unknown port configuration %d\n", + config->port); + err = -EIO; + break; + }; + + return err; +} +EXPORT_SYMBOL_GPL(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; + struct audio_info *info; + + pr_debug("cg2900_audio_config_endpoint\n"); + + if (!config) { + pr_err("NULL supplied as configuration structure"); + return -EINVAL; + } + + audio_user = get_session_user(session); + if (!audio_user) + return -EINVAL; + + info = audio_user->info; + + if (info->state != OPENED) { + dev_err(BT_DEV, "Audio driver not open\n"); + return -EIO; + } + + 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, &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: + dev_err(BT_DEV, "Unsupported endpoint_id %d\n", + config->endpoint_id); + return -EACCES; + } + + return 0; +} +EXPORT_SYMBOL_GPL(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; + struct audio_info *info; + + pr_debug("cg2900_audio_start_stream session %d ep_1 %d ep_2 %d", + session, ep_1, ep_2); + + audio_user = get_session_user(session); + if (!audio_user) + return -EINVAL; + + info = audio_user->info; + + if (info->state != OPENED) { + dev_err(BT_DEV, "Audio driver not open\n"); + return -EIO; + } + + /* 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 { + dev_err(BT_DEV, "Endpoint config not handled: ep1: %d, " + "ep2: %d\n", ep_1, ep_2); + err = -EINVAL; + } + + return err; +} +EXPORT_SYMBOL_GPL(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; + struct audio_info *info; + + pr_debug("cg2900_audio_stop_stream handle %d", stream_handle); + + audio_user = get_session_user(session); + if (!audio_user) + return -EINVAL; + + info = audio_user->info; + + if (info->state != OPENED) { + dev_err(BT_DEV, "Audio driver not open\n"); + return -EIO; + } + + return conn_stop_stream(audio_user, stream_handle); +} +EXPORT_SYMBOL_GPL(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; + int minor; + struct audio_info *info = NULL; + struct audio_info *tmp; + struct list_head *cursor; + + pr_debug("audio_dev_open"); + + minor = iminor(inode); + + /* Find the info struct for this file */ + list_for_each(cursor, &cg2900_audio_devices) { + tmp = list_entry(cursor, struct audio_info, list); + if (tmp->misc_dev.minor == minor) { + info = tmp; + break; + } + } + if (!info) { + pr_err("Could not identify device in inode"); + return -EINVAL; + } + + /* + * 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) { + dev_err(BT_DEV, "Couldn't allocate char_dev_info\n"); + return -ENOMEM; + } + filp->private_data = char_dev_info; + char_dev_info->info = info; + info->filp = filp; + + mutex_init(&char_dev_info->management_mutex); + mutex_init(&char_dev_info->rw_mutex); + skb_queue_head_init(&char_dev_info->rx_queue); + + mutex_lock(&char_dev_info->management_mutex); + err = cg2900_audio_open(&char_dev_info->session, info->dev_bt->parent); + mutex_unlock(&char_dev_info->management_mutex); + if (err) { + dev_err(BT_DEV, "Failed to open CG2900 Audio driver (%d)\n", + 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 = filp->private_data; + struct audio_info *info; + + if (!dev) { + pr_err("audio_dev_release: Transport closed"); + return -EBADF; + } + + info = dev->info; + + dev_dbg(BT_DEV, "audio_dev_release\n"); + + 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 + */ + dev_err(BT_DEV, "Error %d when closing CG2900 audio driver\n", + err); + + mutex_unlock(&dev->management_mutex); + + kfree(dev); + filp->private_data = NULL; + info->filp = 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 = filp->private_data; + struct audio_info *info; + unsigned int bytes_to_copy; + int err = 0; + struct sk_buff *skb; + + if (!dev) { + pr_err("audio_dev_read: Transport closed"); + return -EBADF; + } + + info = dev->info; + + dev_dbg(BT_DEV, "audio_dev_read count %d\n", count); + + mutex_lock(&dev->rw_mutex); + + skb = skb_dequeue(&dev->rx_queue); + if (!skb) { + /* No data to read */ + bytes_to_copy = 0; + goto finished; + } + + bytes_to_copy = min(count, (unsigned int)(skb->len)); + + err = copy_to_user(buf, skb->data, bytes_to_copy); + if (err) { + dev_err(BT_DEV, "copy_to_user error %d\n", err); + skb_queue_head(&dev->rx_queue, skb); + err = -EFAULT; + goto error_handling; + } + + skb_pull(skb, bytes_to_copy); + + if (skb->len > 0) + skb_queue_head(&dev->rx_queue, skb); + else + kfree_skb(skb); + + 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, see API). + * * Data according to OpCode (see API). No padding between parameters. + * + * 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 = filp->private_data; + struct audio_info *info; + 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; + + pr_debug("audio_dev_write count %d", count); + + if (!dev) { + pr_err("audio_dev_write: Transport closed"); + return -EBADF; + } + info = dev->info; + + rec_data = kmalloc(count, GFP_KERNEL); + if (!rec_data) { + dev_err(BT_DEV, "kmalloc failed (%d bytes)\n", count); + return -ENOMEM; + } + + mutex_lock(&dev->rw_mutex); + + err = copy_from_user(rec_data, buf, count); + if (err) { + dev_err(BT_DEV, "copy_from_user failed (%d)\n", 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]; + /* OpCode is int size to keep data int aligned */ + curr_data += sizeof(unsigned int); + bytes_left -= sizeof(unsigned int); + + switch (op_code) { + case CG2900_OPCODE_SET_DAI_CONF: + if (bytes_left < sizeof(dai_config)) { + dev_err(BT_DEV, "Not enough data supplied for " + "CG2900_OPCODE_SET_DAI_CONF\n"); + err = -EINVAL; + goto finished_mutex_unlock; + } + memcpy(&dai_config, curr_data, sizeof(dai_config)); + dev_dbg(BT_DEV, "CG2900_OPCODE_SET_DAI_CONF port %d\n", + dai_config.port); + err = cg2900_audio_set_dai_config(dev->session, &dai_config); + break; + + case CG2900_OPCODE_GET_DAI_CONF: + if (bytes_left < sizeof(dai_config)) { + dev_err(BT_DEV, "Not enough data supplied for " + "CG2900_OPCODE_GET_DAI_CONF\n"); + 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)); + dev_dbg(BT_DEV, "CG2900_OPCODE_GET_DAI_CONF port %d\n", + dai_config.port); + err = cg2900_audio_get_dai_config(dev->session, &dai_config); + if (!err) { + int len; + struct sk_buff *skb; + + /* + * Command succeeded. Store data so it can be returned + * when calling read. + */ + len = sizeof(op_code) + sizeof(dai_config); + skb = alloc_skb(len, GFP_KERNEL); + if (!skb) { + dev_err(BT_DEV, "CG2900_OPCODE_GET_DAI_CONF: " + "Could not allocate skb\n"); + err = -ENOMEM; + goto finished_mutex_unlock; + } + memcpy(skb_put(skb, sizeof(op_code)), &op_code, + sizeof(op_code)); + memcpy(skb_put(skb, sizeof(dai_config)), + &dai_config, sizeof(dai_config)); + skb_queue_tail(&dev->rx_queue, skb); + } + break; + + case CG2900_OPCODE_CONFIGURE_ENDPOINT: + if (bytes_left < sizeof(ep_config)) { + dev_err(BT_DEV, "Not enough data supplied for " + "CG2900_OPCODE_CONFIGURE_ENDPOINT\n"); + err = -EINVAL; + goto finished_mutex_unlock; + } + memcpy(&ep_config, curr_data, sizeof(ep_config)); + dev_dbg(BT_DEV, "CG2900_OPCODE_CONFIGURE_ENDPOINT ep_id %d\n", + ep_config.endpoint_id); + err = cg2900_audio_config_endpoint(dev->session, &ep_config); + break; + + case CG2900_OPCODE_START_STREAM: + if (bytes_left < (sizeof(ep_1) + sizeof(ep_2))) { + dev_err(BT_DEV, "Not enough data supplied for " + "CG2900_OPCODE_START_STREAM\n"); + 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)); + dev_dbg(BT_DEV, "CG2900_OPCODE_START_STREAM ep_1 %d ep_2 %d\n", + ep_1, ep_2); + + err = cg2900_audio_start_stream(dev->session, + ep_1, ep_2, &stream_handle); + if (!err) { + int len; + struct sk_buff *skb; + + /* + * Command succeeded. Store data so it can be returned + * when calling read. + */ + len = sizeof(op_code) + sizeof(stream_handle); + skb = alloc_skb(len, GFP_KERNEL); + if (!skb) { + dev_err(BT_DEV, "CG2900_OPCODE_START_STREAM: " + "Could not allocate skb\n"); + err = -ENOMEM; + goto finished_mutex_unlock; + } + memcpy(skb_put(skb, sizeof(op_code)), &op_code, + sizeof(op_code)); + memcpy(skb_put(skb, sizeof(stream_handle)), + &stream_handle, sizeof(stream_handle)); + skb_queue_tail(&dev->rx_queue, skb); + + dev_dbg(BT_DEV, "stream_handle %d\n", stream_handle); + } + break; + + case CG2900_OPCODE_STOP_STREAM: + if (bytes_left < sizeof(stream_handle)) { + dev_err(BT_DEV, "Not enough data supplied for " + "CG2900_OPCODE_STOP_STREAM\n"); + err = -EINVAL; + goto finished_mutex_unlock; + } + memcpy(&stream_handle, curr_data, sizeof(stream_handle)); + dev_dbg(BT_DEV, "CG2900_OPCODE_STOP_STREAM stream_handle %d\n", + stream_handle); + err = cg2900_audio_stop_stream(dev->session, stream_handle); + break; + + default: + dev_err(BT_DEV, "Received bad op_code %d\n", 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 = filp->private_data; + struct audio_info *info; + unsigned int mask = 0; + + if (!dev) { + pr_err("audio_dev_poll: Transport closed"); + return POLLERR | POLLRDHUP; + } + info = dev->info; + + if (RESET == info->state) + mask |= POLLERR | POLLRDHUP | POLLPRI; + else + /* Unless RESET we can transmit */ + mask |= POLLOUT; + + if (!skb_queue_empty(&dev->rx_queue)) + 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 +}; + +/** + * probe_common() - Register misc device. + * @info: Audio info structure. + * @dev: Current device. + * + * Returns: + * 0 if there is no error. + * -ENOMEM if allocation fails. + * Error codes from misc_register. + */ +static int probe_common(struct audio_info *info, struct device *dev) +{ + struct audio_cb_info *cb_info; + struct cg2900_user_data *pf_data; + int err; + + cb_info = kzalloc(sizeof(*cb_info), GFP_KERNEL); + if (!cb_info) { + dev_err(dev, "Failed to allocate cb_info\n"); + return -ENOMEM; + } + init_waitqueue_head(&cb_info->wq); + skb_queue_head_init(&cb_info->skb_queue); + + pf_data = dev_get_platdata(dev); + cg2900_set_usr(pf_data, cb_info); + pf_data->dev = dev; + pf_data->read_cb = read_cb; + pf_data->reset_cb = reset_cb; + + /* Only register misc device when both devices (BT and FM) are probed */ + if (!info->dev_bt || !info->dev_fm) + return 0; + + /* Prepare and register MISC device */ + info->misc_dev.minor = MISC_DYNAMIC_MINOR; + info->misc_dev.name = NAME; + info->misc_dev.fops = &char_dev_fops; + info->misc_dev.parent = dev; + info->misc_dev.mode = S_IRUGO | S_IWUGO; + + err = misc_register(&info->misc_dev); + if (err) { + dev_err(dev, "Error %d registering misc dev\n", err); + return err; + } + info->misc_registered = true; + + dev_info(dev, "CG2900 Audio driver started\n"); + return 0; +} + +/** + * cg2900_audio_bt_probe() - Initialize CG2900 BT audio resources. + * @pdev: Platform device. + * + * Returns: + * 0 if there is no error. + * -ENOMEM if allocation fails. + * -EEXIST if device has already been started. + * Error codes from probe_common. + */ +static int __devinit cg2900_audio_bt_probe(struct platform_device *pdev) +{ + int err; + struct audio_info *info; + + dev_dbg(&pdev->dev, "cg2900_audio_bt_probe\n"); + + info = get_info(&pdev->dev); + if (IS_ERR(info)) + return PTR_ERR(info); + + info->dev_bt = &pdev->dev; + dev_set_drvdata(&pdev->dev, info); + + err = probe_common(info, &pdev->dev); + if (err) { + dev_err(&pdev->dev, "Could not probe audio BT (%d)\n", err); + dev_set_drvdata(&pdev->dev, NULL); + device_removed(info); + } + + return err; +} + +/** + * cg2900_audio_bt_probe() - Initialize CG2900 FM audio resources. + * @pdev: Platform device. + * + * Returns: + * 0 if there is no error. + * -ENOMEM if allocation fails. + * -EEXIST if device has already been started. + * Error codes from probe_common. + */ +static int __devinit cg2900_audio_fm_probe(struct platform_device *pdev) +{ + int err; + struct audio_info *info; + + dev_dbg(&pdev->dev, "cg2900_audio_fm_probe\n"); + + info = get_info(&pdev->dev); + if (IS_ERR(info)) + return PTR_ERR(info); + + info->dev_fm = &pdev->dev; + dev_set_drvdata(&pdev->dev, info); + + err = probe_common(info, &pdev->dev); + if (err) { + dev_err(&pdev->dev, "Could not probe audio FM (%d)\n", err); + dev_set_drvdata(&pdev->dev, NULL); + device_removed(info); + } + + return err; +} + +/** + * common_remove() - Dergister misc device. + * @info: Audio info structure. + * @dev: Current device. + * + * Returns: + * 0 if success. + * Error codes from misc_deregister. + */ +static int common_remove(struct audio_info *info, struct device *dev) +{ + int err; + struct audio_cb_info *cb_info; + struct cg2900_user_data *pf_data; + + pf_data = dev_get_platdata(dev); + cb_info = cg2900_get_usr(pf_data); + skb_queue_purge(&cb_info->skb_queue); + wake_up_all(&cb_info->wq); + kfree(cb_info); + + if (!info->misc_registered) + return 0; + + err = misc_deregister(&info->misc_dev); + if (err) + dev_err(dev, "Error %d deregistering misc dev\n", err); + info->misc_registered = false; + + if (info->filp) + info->filp->private_data = NULL; + + dev_info(dev, "CG2900 Audio driver removed\n"); + return err; +} + +/** + * cg2900_audio_bt_remove() - Release CG2900 audio resources. + * @pdev: Platform device. + * + * Returns: + * 0 if success. + * Error codes from common_remove. + */ +static int __devexit cg2900_audio_bt_remove(struct platform_device *pdev) +{ + int err; + struct audio_info *info; + + dev_dbg(&pdev->dev, "cg2900_audio_bt_remove\n"); + + info = dev_get_drvdata(&pdev->dev); + + info->dev_bt = NULL; + + err = common_remove(info, &pdev->dev); + if (err) + dev_err(&pdev->dev, + "cg2900_audio_bt_remove:common_remove failed\n"); + + device_removed(info); + + return 0; +} + +/** + * cg2900_audio_fm_remove() - Release CG2900 audio resources. + * @pdev: Platform device. + * + * Returns: + * 0 if success. + * Error codes from common_remove. + */ +static int __devexit cg2900_audio_fm_remove(struct platform_device *pdev) +{ + int err; + struct audio_info *info; + + dev_dbg(&pdev->dev, "cg2900_audio_fm_remove\n"); + + info = dev_get_drvdata(&pdev->dev); + + info->dev_fm = NULL; + + err = common_remove(info, &pdev->dev); + if (err) + dev_err(&pdev->dev, + "cg2900_audio_fm_remove:common_remove failed\n"); + + device_removed(info); + + return 0; +} + +static struct platform_driver cg2900_audio_bt_driver = { + .driver = { + .name = "cg2900-audiobt", + .owner = THIS_MODULE, + }, + .probe = cg2900_audio_bt_probe, + .remove = __devexit_p(cg2900_audio_bt_remove), +}; + +static struct platform_driver cg2900_audio_fm_driver = { + .driver = { + .name = "cg2900-audiofm", + .owner = THIS_MODULE, + }, + .probe = cg2900_audio_fm_probe, + .remove = __devexit_p(cg2900_audio_fm_remove), +}; + +/** + * cg2900_audio_init() - Initialize module. + * + * Registers platform driver. + */ +static int __init cg2900_audio_init(void) +{ + int err; + + pr_debug("cg2900_audio_init"); + + err = platform_driver_register(&cg2900_audio_bt_driver); + if (err) + return err; + return platform_driver_register(&cg2900_audio_fm_driver); +} + +/** + * cg2900_audio_exit() - Remove module. + * + * Unregisters platform driver. + */ +static void __exit cg2900_audio_exit(void) +{ + pr_debug("cg2900_audio_exit"); + platform_driver_unregister(&cg2900_audio_fm_driver); + platform_driver_unregister(&cg2900_audio_bt_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/drivers/staging/cg2900/mfd/cg2900_char_devices.c b/drivers/staging/cg2900/mfd/cg2900_char_devices.c new file mode 100644 index 00000000000..81d230227db --- /dev/null +++ b/drivers/staging/cg2900/mfd/cg2900_char_devices.c @@ -0,0 +1,719 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson. + * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson. + * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson. + * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Linux Bluetooth HCI H:4 Driver for ST-Ericsson connectivity controller. + */ +#define NAME "cg2900_char_dev" +#define pr_fmt(fmt) NAME ": " fmt "\n" + +#include <linux/compiler.h> +#include <linux/device.h> +#include <linux/fs.h> +#include <linux/file.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/mfd/core.h> + +#include "cg2900.h" +#include "cg2900_core.h" + +#define MAIN_DEV (dev->dev) + +/** + * struct char_dev_user - Stores device information. + * @dev: Current device. + * @miscdev: Registered device struct. + * @filp: Current file pointer. + * @name: Name of device. + * @rx_queue: Data queue. + * @rx_wait_queue: Wait queue. + * @reset_wait_queue: Reset Wait queue. + * @read_mutex: Read mutex. + * @write_mutex: Write mutex. + * @list: List header for inserting into device list. + */ +struct char_dev_user { + struct device *dev; + struct miscdevice miscdev; + struct file *filp; + char *name; + struct sk_buff_head rx_queue; + wait_queue_head_t rx_wait_queue; + wait_queue_head_t reset_wait_queue; + struct mutex read_mutex; + struct mutex write_mutex; + struct list_head list; +}; + +/** + * struct char_info - Stores all current users. + * @open_mutex: Open mutex (used for both open and release). + * @man_mutex: Management mutex. + * @dev_users: List of char dev users. + */ +struct char_info { + struct mutex open_mutex; + struct mutex man_mutex; + struct list_head dev_users; +}; + +static struct char_info *char_info; + +/** + * char_dev_read_cb() - Handle data received from controller. + * @dev: Device receiving data. + * @skb: Buffer with data coming from controller. + * + * The char_dev_read_cb() function handles data received from the CG2900 driver. + */ +static void char_dev_read_cb(struct cg2900_user_data *dev, struct sk_buff *skb) +{ + struct char_dev_user *char_dev = dev_get_drvdata(dev->dev); + + dev_dbg(dev->dev, "char_dev_read_cb len %d\n", skb->len); + + skb_queue_tail(&char_dev->rx_queue, skb); + + wake_up_interruptible(&char_dev->rx_wait_queue); +} + +/** + * char_dev_reset_cb() - Handle reset from controller. + * @dev: Device resetting. + * + * The char_dev_reset_cb() function handles reset from the CG2900 driver. + */ +static void char_dev_reset_cb(struct cg2900_user_data *dev) +{ + struct char_dev_user *char_dev = dev_get_drvdata(dev->dev); + + dev_dbg(dev->dev, "char_dev_reset_cb\n"); + + wake_up_interruptible(&char_dev->rx_wait_queue); + wake_up_interruptible(&char_dev->reset_wait_queue); +} + +/** + * char_dev_open() - Open char device. + * @inode: Device driver information. + * @filp: Pointer to the file struct. + * + * The char_dev_open() function opens the char device. + * + * Returns: + * 0 if there is no error. + * -EINVAL if device cannot be found in device list. + * Error codes from cg2900->open. + */ +static int char_dev_open(struct inode *inode, struct file *filp) +{ + int err; + int minor; + struct char_dev_user *dev = NULL; + struct char_dev_user *tmp; + struct list_head *cursor; + struct cg2900_user_data *user; + + mutex_lock(&char_info->open_mutex); + + minor = iminor(inode); + + /* Find the device for this file */ + mutex_lock(&char_info->man_mutex); + list_for_each(cursor, &char_info->dev_users) { + tmp = list_entry(cursor, struct char_dev_user, list); + if (tmp->miscdev.minor == minor) { + dev = tmp; + break; + } + } + mutex_unlock(&char_info->man_mutex); + if (!dev) { + pr_err("Could not identify device in inode"); + err = -EINVAL; + goto error_handling; + } + + filp->private_data = dev; + dev->filp = filp; + user = dev_get_platdata(dev->dev); + + /* First initiate wait queues for this device. */ + init_waitqueue_head(&dev->rx_wait_queue); + init_waitqueue_head(&dev->reset_wait_queue); + + /* Register to CG2900 Driver */ + err = user->open(user); + if (err) { + dev_err(MAIN_DEV, + "Couldn't register to CG2900 for H:4 channel %s\n", + dev->name); + goto error_handling; + } + dev_info(MAIN_DEV, "char_dev %s opened\n", dev->name); + +error_handling: + mutex_unlock(&char_info->open_mutex); + return err; +} + +/** + * char_dev_release() - Release char device. + * @inode: Device driver information. + * @filp: Pointer to the file struct. + * + * The char_dev_release() function release the char device. + * + * Returns: + * 0 if there is no error. + * -EBADF if NULL pointer was supplied in private data. + */ +static int char_dev_release(struct inode *inode, struct file *filp) +{ + int err = 0; + struct char_dev_user *dev = filp->private_data; + struct cg2900_user_data *user; + + pr_debug("char_dev_release"); + + if (!dev) { + pr_err("char_dev_release: Calling with NULL pointer"); + return -EBADF; + } + + mutex_lock(&char_info->open_mutex); + mutex_lock(&dev->read_mutex); + mutex_lock(&dev->write_mutex); + + user = dev_get_platdata(dev->dev); + if (user->opened) + user->close(user); + + dev_info(MAIN_DEV, "char_dev %s closed\n", dev->name); + + filp->private_data = NULL; + dev->filp = NULL; + wake_up_interruptible(&dev->rx_wait_queue); + wake_up_interruptible(&dev->reset_wait_queue); + + /* Purge the queue since the device is closed now */ + skb_queue_purge(&dev->rx_queue); + + mutex_unlock(&dev->write_mutex); + mutex_unlock(&dev->read_mutex); + mutex_unlock(&char_info->open_mutex); + + return err; +} + +/** + * char_dev_read() - Queue and copy buffer to user. + * @filp: Pointer to the file struct. + * @buf: Received buffer. + * @count: Size of buffer. + * @f_pos: Position in buffer. + * + * The char_dev_read() function queues and copy the received buffer to + * the user space char device. If no data is available this function will block. + * + * Returns: + * Bytes successfully read (could be 0). + * -EBADF if NULL pointer was supplied in private data. + * -EFAULT if copy_to_user fails. + * Error codes from wait_event_interruptible. + */ +static ssize_t char_dev_read(struct file *filp, char __user *buf, size_t count, + loff_t *f_pos) +{ + struct char_dev_user *dev = filp->private_data; + struct cg2900_user_data *user; + struct sk_buff *skb; + int bytes_to_copy; + int err = 0; + + pr_debug("char_dev_read"); + + if (!dev) { + pr_err("char_dev_read: Calling with NULL pointer"); + return -EBADF; + } + mutex_lock(&dev->read_mutex); + + user = dev_get_platdata(dev->dev); + + if (user->opened && skb_queue_empty(&dev->rx_queue)) { + err = wait_event_interruptible(dev->rx_wait_queue, + (!(skb_queue_empty(&dev->rx_queue))) || + !user->opened); + if (err) { + dev_err(MAIN_DEV, "Failed to wait for event\n"); + goto error_handling; + } + } + + if (!user->opened) { + dev_err(MAIN_DEV, "Channel has been closed\n"); + err = -EBADF; + goto error_handling; + } + + skb = skb_dequeue(&dev->rx_queue); + if (!skb) { + dev_dbg(MAIN_DEV, + "skb queue is empty - return with zero bytes\n"); + bytes_to_copy = 0; + goto finished; + } + + bytes_to_copy = min(count, skb->len); + + err = copy_to_user(buf, skb->data, bytes_to_copy); + if (err) { + dev_err(MAIN_DEV, "Error %d from copy_to_user\n", err); + skb_queue_head(&dev->rx_queue, skb); + err = -EFAULT; + goto error_handling; + } + + skb_pull(skb, bytes_to_copy); + + if (skb->len > 0) + skb_queue_head(&dev->rx_queue, skb); + else + kfree_skb(skb); + + goto finished; + +error_handling: + mutex_unlock(&dev->read_mutex); + return (ssize_t)err; +finished: + mutex_unlock(&dev->read_mutex); + return bytes_to_copy; +} + +/** + * char_dev_write() - Copy buffer from user and write to CG2900 driver. + * @filp: Pointer to the file struct. + * @buf: Write buffer. + * @count: Size of the buffer write. + * @f_pos: Position of buffer. + * + * Returns: + * Bytes successfully written (could be 0). + * -EBADF if NULL pointer was supplied in private data. + * -EFAULT if copy_from_user fails. + */ +static ssize_t char_dev_write(struct file *filp, const char __user *buf, + size_t count, loff_t *f_pos) +{ + struct sk_buff *skb; + struct char_dev_user *dev = filp->private_data; + struct cg2900_user_data *user; + int err = 0; + + pr_debug("char_dev_write"); + + if (!dev) { + pr_err("char_dev_write: Calling with NULL pointer"); + return -EBADF; + } + + user = dev_get_platdata(dev->dev); + if (!user->opened) { + dev_err(MAIN_DEV, "char_dev_write: Channel not opened\n"); + return -EACCES; + } + + mutex_lock(&dev->write_mutex); + + skb = user->alloc_skb(count, GFP_ATOMIC); + if (!skb) { + dev_err(MAIN_DEV, "Couldn't allocate sk_buff with length %d\n", + count); + goto error_handling; + } + + err = copy_from_user(skb_put(skb, count), buf, count); + if (err) { + dev_err(MAIN_DEV, "Error %d from copy_from_user\n", err); + kfree_skb(skb); + err = -EFAULT; + goto error_handling; + } + + err = user->write(user, skb); + if (err) { + dev_err(MAIN_DEV, "cg2900_write failed (%d)\n", err); + kfree_skb(skb); + goto error_handling; + } + + mutex_unlock(&dev->write_mutex); + return count; + +error_handling: + mutex_unlock(&dev->write_mutex); + return err; +} + +/** + * char_dev_unlocked_ioctl() - Handle IOCTL call to the interface. + * @filp: Pointer to the file struct. + * @cmd: IOCTL command. + * @arg: IOCTL argument. + * + * Returns: + * 0 if there is no error. + * -EINVAL if supplied cmd is not supported. + * For cmd CG2900_CHAR_DEV_IOCTL_CHECK4RESET 0x01 is returned if device is + * reset and 0x02 is returned if device is closed. + */ +static long char_dev_unlocked_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + struct char_dev_user *dev = filp->private_data; + struct cg2900_user_data *user; + struct cg2900_rev_data rev_data; + int err = 0; + int ret_val; + void __user *user_arg = (void __user *)arg; + + if (!dev) { + pr_err("char_dev_unlocked_ioctl: Calling with NULL pointer"); + return -EBADF; + } + + dev_dbg(dev->dev, "char_dev_unlocked_ioctl for %s\n" + "\tDIR: %d\n" + "\tTYPE: %d\n" + "\tNR: %d\n" + "\tSIZE: %d", + dev->name, _IOC_DIR(cmd), _IOC_TYPE(cmd), _IOC_NR(cmd), + _IOC_SIZE(cmd)); + + user = dev_get_platdata(dev->dev); + + switch (cmd) { + case CG2900_CHAR_DEV_IOCTL_RESET: + if (!user->opened) + return -EACCES; + dev_dbg(MAIN_DEV, "ioctl reset command for device %s\n", + dev->name); + err = user->reset(user); + break; + + case CG2900_CHAR_DEV_IOCTL_CHECK4RESET: + if (user->opened) + ret_val = CG2900_CHAR_DEV_IOCTL_EVENT_IDLE; + else + ret_val = CG2900_CHAR_DEV_IOCTL_EVENT_RESET; + + dev_dbg(MAIN_DEV, "ioctl check for reset command for device %s", + dev->name); + + err = copy_to_user(user_arg, &ret_val, sizeof(ret_val)); + if (err) { + dev_err(MAIN_DEV, + "Error %d from copy_to_user for reset\n", err); + return -EFAULT; + } + break; + + case CG2900_CHAR_DEV_IOCTL_GET_REVISION: + if (!user->get_local_revision(user, &rev_data)) { + dev_err(MAIN_DEV, "No revision data available\n"); + return -EIO; + } + dev_dbg(MAIN_DEV, "ioctl check for local revision info\n" + "\trevision 0x%04X\n" + "\tsub_version 0x%04X\n", + rev_data.revision, rev_data.sub_version); + err = copy_to_user(user_arg, &rev_data, sizeof(rev_data)); + if (err) { + dev_err(MAIN_DEV, + "Error %d from copy_to_user for " + "revision\n", err); + return -EFAULT; + } + break; + + default: + dev_err(MAIN_DEV, "Unknown ioctl command %08X\n", cmd); + err = -EINVAL; + break; + }; + + return err; +} + +/** + * char_dev_poll() - Handle POLL call to the interface. + * @filp: Pointer to the file struct. + * @wait: Poll table supplied to caller. + * + * Returns: + * Mask of current set POLL values + */ +static unsigned int char_dev_poll(struct file *filp, poll_table *wait) +{ + struct char_dev_user *dev = filp->private_data; + struct cg2900_user_data *user; + unsigned int mask = 0; + + if (!dev) { + pr_debug("char_dev_poll: Device not open"); + return POLLERR | POLLRDHUP; + } + + user = dev_get_platdata(dev->dev); + + poll_wait(filp, &dev->reset_wait_queue, wait); + poll_wait(filp, &dev->rx_wait_queue, wait); + + if (!user->opened) + mask |= POLLERR | POLLRDHUP | POLLPRI; + else + mask |= POLLOUT; /* We can TX unless there is an error */ + + if (!(skb_queue_empty(&dev->rx_queue))) + mask |= POLLIN | POLLRDNORM; + + return mask; +} + +/* + * struct char_dev_fops - Char devices file operations. + * @read: Function that reads from the char device. + * @write: Function that writes to the char device. + * @unlocked_ioctl: Function that performs IO operations with + * the char device. + * @poll: Function that checks if there are possible operations + * with the char device. + * @open: Function that opens the char device. + * @release: Function that release the char device. + */ +static const struct file_operations char_dev_fops = { + .read = char_dev_read, + .write = char_dev_write, + .unlocked_ioctl = char_dev_unlocked_ioctl, + .poll = char_dev_poll, + .open = char_dev_open, + .release = char_dev_release +}; + +/** + * remove_dev() - Remove char device structure for device. + * @dev_usr: Char device user. + * + * The remove_dev() function releases the char_dev structure for this device. + */ +static void remove_dev(struct char_dev_user *dev_usr) +{ + if (!dev_usr) + return; + + dev_dbg(dev_usr->dev, + "Removing char device %s with major %d and minor %d\n", + dev_usr->name, + MAJOR(dev_usr->miscdev.this_device->devt), + MINOR(dev_usr->miscdev.this_device->devt)); + + skb_queue_purge(&dev_usr->rx_queue); + + mutex_destroy(&dev_usr->read_mutex); + mutex_destroy(&dev_usr->write_mutex); + + dev_usr->dev = NULL; + if (dev_usr->filp) + dev_usr->filp->private_data = NULL; + + /* Remove device node in file system. */ + misc_deregister(&dev_usr->miscdev); + kfree(dev_usr); +} + +/** + * cg2900_char_probe() - Initialize char device module. + * @pdev: Platform device. + * + * Returns: + * 0 if success. + * -ENOMEM if allocation fails. + * -EACCES if device already have been initiated. + */ +static int __devinit cg2900_char_probe(struct platform_device *pdev) +{ + int err = 0; + struct char_dev_user *dev_usr; + struct cg2900_user_data *user; + struct device *dev = &pdev->dev; + + dev_dbg(&pdev->dev, "cg2900_char_probe\n"); + + user = dev_get_platdata(dev); + user->dev = dev; + user->read_cb = char_dev_read_cb; + user->reset_cb = char_dev_reset_cb; + + dev_usr = kzalloc(sizeof(*dev_usr), GFP_KERNEL); + if (!dev_usr) { + dev_err(&pdev->dev, "Couldn't allocate dev_usr\n"); + return -ENOMEM; + } + + dev_set_drvdata(&pdev->dev, dev_usr); + dev_usr->dev = &pdev->dev; + + /* Store device name */ + dev_usr->name = user->channel_data.char_dev_name; + + /* Prepare miscdevice struct before registering the device */ + dev_usr->miscdev.minor = MISC_DYNAMIC_MINOR; + dev_usr->miscdev.name = dev_usr->name; + dev_usr->miscdev.nodename = dev_usr->name; + dev_usr->miscdev.fops = &char_dev_fops; + dev_usr->miscdev.parent = &pdev->dev; + dev_usr->miscdev.mode = S_IRUGO | S_IWUGO; + + err = misc_register(&dev_usr->miscdev); + if (err) { + dev_err(&pdev->dev, "Error %d registering misc dev\n", err); + goto err_free_usr; + } + + dev_dbg(&pdev->dev, "Added char device %s with major %d and minor %d\n", + dev_usr->name, MAJOR(dev_usr->miscdev.this_device->devt), + MINOR(dev_usr->miscdev.this_device->devt)); + + mutex_init(&dev_usr->read_mutex); + mutex_init(&dev_usr->write_mutex); + + skb_queue_head_init(&dev_usr->rx_queue); + + mutex_lock(&char_info->man_mutex); + list_add_tail(&dev_usr->list, &char_info->dev_users); + mutex_unlock(&char_info->man_mutex); + + return 0; + +err_free_usr: + kfree(dev_usr); + dev_set_drvdata(&pdev->dev, NULL); + return err; +} + +/** + * cg2900_char_remove() - Release the char device module. + * @pdev: Platform device. + * + * Returns: + * 0 if success (always success). + */ +static int __devexit cg2900_char_remove(struct platform_device *pdev) +{ + struct list_head *cursor, *next; + struct char_dev_user *tmp; + struct char_dev_user *user; + + dev_dbg(&pdev->dev, "cg2900_char_remove\n"); + + user = dev_get_drvdata(&pdev->dev); + + mutex_lock(&char_info->man_mutex); + list_for_each_safe(cursor, next, &char_info->dev_users) { + tmp = list_entry(cursor, struct char_dev_user, list); + if (tmp == user) { + list_del(cursor); + remove_dev(tmp); + dev_set_drvdata(&pdev->dev, NULL); + break; + } + } + mutex_unlock(&char_info->man_mutex); + return 0; +} + +static struct platform_driver cg2900_char_driver = { + .driver = { + .name = "cg2900-chardev", + .owner = THIS_MODULE, + }, + .probe = cg2900_char_probe, + .remove = __devexit_p(cg2900_char_remove), +}; + +/** + * cg2900_char_init() - Initialize module. + * + * Registers platform driver. + */ +static int __init cg2900_char_init(void) +{ + pr_debug("cg2900_char_init"); + + /* Initialize private data. */ + char_info = kzalloc(sizeof(*char_info), GFP_ATOMIC); + if (!char_info) { + pr_err("Could not alloc char_info struct"); + return -ENOMEM; + } + + mutex_init(&char_info->open_mutex); + mutex_init(&char_info->man_mutex); + INIT_LIST_HEAD(&char_info->dev_users); + + return platform_driver_register(&cg2900_char_driver); +} + +/** + * cg2900_char_exit() - Remove module. + * + * Unregisters platform driver. + */ +static void __exit cg2900_char_exit(void) +{ + struct list_head *cursor, *next; + struct char_dev_user *tmp; + + pr_debug("cg2900_char_exit"); + + platform_driver_unregister(&cg2900_char_driver); + + if (!char_info) + return; + + list_for_each_safe(cursor, next, &char_info->dev_users) { + tmp = list_entry(cursor, struct char_dev_user, list); + list_del(cursor); + remove_dev(tmp); + } + + mutex_destroy(&char_info->open_mutex); + mutex_destroy(&char_info->man_mutex); + + kfree(char_info); + char_info = NULL; +} + +module_init(cg2900_char_init); +module_exit(cg2900_char_exit); + +MODULE_AUTHOR("Henrik Possung ST-Ericsson"); +MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("ST-Ericsson CG2900 Char Devices Driver"); diff --git a/drivers/staging/cg2900/mfd/cg2900_chip.c b/drivers/staging/cg2900/mfd/cg2900_chip.c new file mode 100644 index 00000000000..2ae418c9f03 --- /dev/null +++ b/drivers/staging/cg2900/mfd/cg2900_chip.c @@ -0,0 +1,3791 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson. + * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson. + * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson. + * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson. + * Hemant Gupta (hemant.gupta@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Linux Bluetooth HCI H:4 Driver for ST-Ericsson CG2900 GPS/BT/FM controller. + */ +#define NAME "cg2900_chip" +#define pr_fmt(fmt) NAME ": " fmt "\n" + +#include <asm/byteorder.h> +#include <linux/device.h> +#include <linux/firmware.h> +#include <linux/gfp.h> +#include <linux/init.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/limits.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/sched.h> +#include <linux/skbuff.h> +#include <linux/stat.h> +#include <linux/string.h> +#include <linux/time.h> +#include <linux/timer.h> +#include <linux/types.h> +#include <linux/wait.h> +#include <linux/workqueue.h> +#include <linux/mfd/core.h> +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci.h> + +#include "cg2900.h" +#include "cg2900_chip.h" +#include "cg2900_core.h" +#include "cg2900_lib.h" + +#ifndef MAX +#define MAX(x, y) (((x) > (y)) ? (x) : (y)) +#endif + +#define MAIN_DEV (main_info->dev) +#define BOOT_DEV (info->user_in_charge->dev) + +#define WQ_NAME "cg2900_chip_wq" + +/* + * After waiting the first 500 ms we should just try to get the selftest results + * for another number of poll attempts + */ +#define MAX_NBR_OF_POLLS 50 + +#define LINE_TOGGLE_DETECT_TIMEOUT 50 /* ms */ +#define CHIP_READY_TIMEOUT 100 /* ms */ +#define CHIP_STARTUP_TIMEOUT 15000 /* ms */ +#define CHIP_SHUTDOWN_TIMEOUT 15000 /* ms */ +#define POWER_SW_OFF_WAIT 500 /* ms */ +#define SELFTEST_INITIAL 500 /* ms */ +#define SELFTEST_POLLING 20 /* ms */ + +/** CHANNEL_BT_CMD - Bluetooth HCI H:4 channel + * for Bluetooth commands in the ST-Ericsson connectivity controller. + */ +#define CHANNEL_BT_CMD 0x01 + +/** CHANNEL_BT_ACL - Bluetooth HCI H:4 channel + * for Bluetooth ACL data in the ST-Ericsson connectivity controller. + */ +#define CHANNEL_BT_ACL 0x02 + +/** CHANNEL_BT_EVT - Bluetooth HCI H:4 channel + * for Bluetooth events in the ST-Ericsson connectivity controller. + */ +#define CHANNEL_BT_EVT 0x04 + +/** CHANNEL_NFC - Bluetooth HCI H:4 channel + * for NFC in the ST-Ericsson connectivity controller. + */ +#define CHANNEL_NFC 0x05 + +/** CHANNEL_FM_RADIO - Bluetooth HCI H:4 channel + * for FM radio in the ST-Ericsson connectivity controller. + */ +#define CHANNEL_FM_RADIO 0x08 + +/** CHANNEL_GNSS - Bluetooth HCI H:4 channel + * for GNSS in the ST-Ericsson connectivity controller. + */ +#define CHANNEL_GNSS 0x09 + +/** CHANNEL_DEBUG - Bluetooth HCI H:4 channel + * for internal debug data in the ST-Ericsson connectivity controller. + */ +#define CHANNEL_DEBUG 0x0B + +/** CHANNEL_STE_TOOLS - Bluetooth HCI H:4 channel + * for development tools data in the ST-Ericsson connectivity controller. + */ +#define CHANNEL_STE_TOOLS 0x0D + +/** CHANNEL_HCI_LOGGER - Bluetooth HCI H:4 channel + * for logging all transmitted H4 packets (on all channels). + */ +#define CHANNEL_HCI_LOGGER 0xFA + +/** CHANNEL_CORE - Bluetooth HCI H:4 channel + * for user space control of the ST-Ericsson connectivity controller. + */ +#define CHANNEL_CORE 0xFD + +/** CHANNEL_HCI_RAW - Bluetooth HCI H:4 channel + * for user space read/write on the ST-Ericsson connectivity controller. + */ +#define CHANNEL_HCI_RAW 0xFE + +/** CG2900_BT_CMD - Bluetooth HCI H4 channel for Bluetooth commands. + */ +#define CG2900_BT_CMD "cg2900_bt_cmd" + +/** CG2900_BT_ACL - Bluetooth HCI H4 channel for Bluetooth ACL data. + */ +#define CG2900_BT_ACL "cg2900_bt_acl" + +/** CG2900_BT_EVT - Bluetooth HCI H4 channel for Bluetooth events. + */ +#define CG2900_BT_EVT "cg2900_bt_evt" + +/** CG2900_NFC - Bluetooth HCI H4 channel for NFC. + */ +#define CG2900_NFC "cg2900_nfc" + +/** CG2900_FM_RADIO - Bluetooth HCI H4 channel for FM radio. + */ +#define CG2900_FM_RADIO "cg2900_fm_radio" + +/** CG2900_GNSS - Bluetooth HCI H4 channel for GNSS. + */ +#define CG2900_GNSS "cg2900_gnss" + +/** CG2900_DEBUG - Bluetooth HCI H4 channel for internal debug data. + */ +#define CG2900_DEBUG "cg2900_debug" + +/** CG2900_STE_TOOLS - Bluetooth HCI H4 channel for development tools data. + */ +#define CG2900_STE_TOOLS "cg2900_ste_tools" + +/** CG2900_HCI_LOGGER - BT channel for logging all transmitted H4 packets. + * Data read is copy of all data transferred on the other channels. + * Only write allowed is configuration of the HCI Logger. + */ +#define CG2900_HCI_LOGGER "cg2900_hci_logger" + +/** CG2900_BT_AUDIO - HCI Channel for BT audio configuration commands. + * Maps to Bluetooth command and event channels. + */ +#define CG2900_BT_AUDIO "cg2900_bt_audio" + +/** CG2900_FM_AUDIO - HCI channel for FM audio configuration commands. + * Maps to FM Radio channel. + */ +#define CG2900_FM_AUDIO "cg2900_fm_audio" + +/** CG2900_CORE- Channel for keeping ST-Ericsson CG2900 enabled. + * Opening this channel forces the chip to stay powered. + * No data can be written to or read from this channel. + */ +#define CG2900_CORE "cg2900_core" + +/** CG2900_HCI_RAW - Channel for HCI RAW data exchange. + * Opening this channel will not allow any others HCI Channels + * to be opened except Logger Channel. + */ +#define CG2900_HCI_RAW "cg2900_hci_raw" + +/** + * enum main_state - Main-state for CG2900 driver. + * @CG2900_INIT: CG2900 initializing. + * @CG2900_IDLE: No user registered to CG2900 driver. + * @CG2900_BOOTING: CG2900 booting after first user is registered. + * @CG2900_CLOSING: CG2900 closing after last user has deregistered. + * @CG2900_RESETING: CG2900 reset requested. + * @CG2900_ACTIVE: CG2900 up and running with at least one user. + * @CG2900_ACTIVE_BEFORE_SELFTEST: CG2900 up and running before + * BT self test is run. + */ +enum main_state { + CG2900_INIT, + CG2900_IDLE, + CG2900_BOOTING, + CG2900_CLOSING, + CG2900_RESETING, + CG2900_ACTIVE, + CG2900_ACTIVE_BEFORE_SELFTEST +}; + +/** + * enum boot_state - BOOT-state for CG2900 chip driver. + * @BOOT_NOT_STARTED: Boot has not yet started. + * @BOOT_SEND_BD_ADDRESS: VS Store In FS command with BD address + * has been sent. + * @BOOT_GET_FILES_TO_LOAD: CG2900 chip driver is retrieving file to + * load. + * @BOOT_DOWNLOAD_PATCH: CG2900 chip driver is downloading + * patches. + * @BOOT_ACTIVATE_PATCHES_AND_SETTINGS: CG2900 chip driver is activating patches + * and settings. + * @BOOT_READ_SELFTEST_RESULT: CG2900 is performing selftests that + * shall be read out. + * @BOOT_DISABLE_BT: Disable BT Core. + * @BOOT_READY: CG2900 chip driver boot is ready. + * @BOOT_FAILED: CG2900 chip driver boot failed. + */ +enum boot_state { + BOOT_NOT_STARTED, + BOOT_SEND_BD_ADDRESS, + BOOT_GET_FILES_TO_LOAD, + BOOT_DOWNLOAD_PATCH, + BOOT_ACTIVATE_PATCHES_AND_SETTINGS, + BOOT_READ_SELFTEST_RESULT, + BOOT_DISABLE_BT, + BOOT_READY, + BOOT_FAILED +}; + +/** + * enum closing_state - CLOSING-state for CG2900 chip driver. + * @CLOSING_RESET: HCI RESET_CMD has been sent. + * @CLOSING_POWER_SWITCH_OFF: HCI VS_POWER_SWITCH_OFF command has been sent. + * @CLOSING_SHUT_DOWN: We have now shut down the chip. + */ +enum closing_state { + CLOSING_RESET, + CLOSING_POWER_SWITCH_OFF, + CLOSING_SHUT_DOWN +}; + +/** + * enum file_load_state - BOOT_FILE_LOAD-state for CG2900 chip driver. + * @FILE_LOAD_GET_PATCH: Loading patches. + * @FILE_LOAD_GET_STATIC_SETTINGS: Loading static settings. + * @FILE_LOAD_NO_MORE_FILES: No more files to load. + * @FILE_LOAD_FAILED: File loading failed. + */ +enum file_load_state { + FILE_LOAD_GET_PATCH, + FILE_LOAD_GET_STATIC_SETTINGS, + FILE_LOAD_NO_MORE_FILES, + FILE_LOAD_FAILED +}; + +/** + * enum download_state - BOOT_DOWNLOAD state. + * @DOWNLOAD_PENDING: Download in progress. + * @DOWNLOAD_SUCCESS: Download successfully finished. + * @DOWNLOAD_FAILED: Downloading failed. + */ +enum download_state { + DOWNLOAD_PENDING, + DOWNLOAD_SUCCESS, + DOWNLOAD_FAILED +}; + +/** + * enum fm_radio_mode - FM Radio mode. + * It's needed because some FM do-commands generate interrupts only when + * the FM driver is in specific mode and we need to know if we should expect + * the interrupt. + * @FM_RADIO_MODE_IDLE: Radio mode is Idle (default). + * @FM_RADIO_MODE_FMT: Radio mode is set to FMT (transmitter). + * @FM_RADIO_MODE_FMR: Radio mode is set to FMR (receiver). + */ +enum fm_radio_mode { + FM_RADIO_MODE_IDLE = 0, + FM_RADIO_MODE_FMT = 1, + FM_RADIO_MODE_FMR = 2 +}; + + +/** + * struct cg2900_channel_item - List object for channel. + * @list: list_head struct. + * @user: User for this channel. + */ +struct cg2900_channel_item { + struct list_head list; + struct cg2900_user_data *user; +}; + +/** + * struct cg2900_delayed_work_struct - Work structure for CG2900 chip. + * @delayed_work: Work structure. + * @data: Pointer to private data. + */ +struct cg2900_delayed_work_struct { + struct delayed_work work; + void *data; +}; + +/** + * struct cg2900_skb_data - Structure for storing private data in an sk_buffer. + * @dev: CG2900 device for this sk_buffer. + */ +struct cg2900_skb_data { + struct cg2900_user_data *user; +}; +#define cg2900_skb_data(__skb) ((struct cg2900_skb_data *)((__skb)->cb)) + +/** + * struct cg2900_chip_info - Main info structure for CG2900 chip driver. + * @dev: Current device. Same as @chip_dev->dev. + * @patch_file_name: Stores patch file name. + * @settings_file_name: Stores settings file name. + * @file_info: Firmware file info (patch or settings). + * @boot_state: Current BOOT-state of CG2900 chip driver. + * @closing_state: Current CLOSING-state of CG2900 chip driver. + * @file_load_state: Current BOOT_FILE_LOAD-state of CG2900 chip + * driver. + * @download_state: Current BOOT_DOWNLOAD-state of CG2900 chip + * driver. + * @wq: CG2900 chip driver workqueue. + * @chip_dev: Chip handler info. + * @tx_bt_lock: Spinlock used to protect some global structures + * related to internal BT command flow control. + * @tx_fm_lock: Spinlock used to protect some global structures + * related to internal FM command flow control. + * @tx_fm_audio_awaiting_irpt: Indicates if an FM interrupt event related to + * audio driver command is expected. + * @fm_radio_mode: Current FM radio mode. + * @tx_nr_pkts_allowed_bt: Number of packets allowed to send on BT HCI CMD + * H4 channel. + * @audio_bt_cmd_op: Stores the OpCode of the last sent audio driver + * HCI BT CMD. + * @audio_fm_cmd_id: Stores the command id of the last sent + * HCI FM RADIO command by the fm audio user. + * @hci_fm_cmd_func: Stores the command function of the last sent + * HCI FM RADIO command by the fm radio user. + * @tx_queue_bt: TX queue for HCI BT commands when nr of commands + * allowed is 0 (CG2900 internal flow control). + * @tx_queue_fm: TX queue for HCI FM commands when nr of commands + * allowed is 0 (CG2900 internal flow control). + * @user_in_charge: User currently operating. Normally used at + * channel open and close. + * @last_user: Last user of this chip. To avoid complications + * this will never be set for bt_audio and + * fm_audio. + * @logger: Logger user of this chip. + * @hci_raw: HCI Raw user of this chip. + * @selftest_work: Delayed work for reading selftest results. + * @nbr_of_polls: Number of times we should poll for selftest + * results. + * @startup: True if system is starting up. + * @mfd_size: Number of MFD cells. + * @mfd_char_size: Number of MFD char device cells. + */ +struct cg2900_chip_info { + struct device *dev; + char *patch_file_name; + char *settings_file_name; + struct cg2900_file_info file_info; + enum main_state main_state; + enum boot_state boot_state; + enum closing_state closing_state; + enum file_load_state file_load_state; + enum download_state download_state; + struct workqueue_struct *wq; + struct cg2900_chip_dev *chip_dev; + spinlock_t tx_bt_lock; + spinlock_t tx_fm_lock; + spinlock_t rw_lock; + bool tx_fm_audio_awaiting_irpt; + enum fm_radio_mode fm_radio_mode; + int tx_nr_pkts_allowed_bt; + u16 audio_bt_cmd_op; + u16 audio_fm_cmd_id; + u16 hci_fm_cmd_func; + struct sk_buff_head tx_queue_bt; + struct sk_buff_head tx_queue_fm; + struct list_head open_channels; + struct cg2900_user_data *user_in_charge; + struct cg2900_user_data *last_user; + struct cg2900_user_data *logger; + struct cg2900_user_data *hci_raw; + struct cg2900_user_data *bt_audio; + struct cg2900_user_data *fm_audio; + struct cg2900_delayed_work_struct selftest_work; + int nbr_of_polls; + bool startup; + int mfd_size; + int mfd_extra_size; + int mfd_char_size; + int mfd_extra_char_size; +}; + +/** + * struct main_info - Main info structure for CG2900 chip driver. + * @dev: Device structure. + * @cell_base_id: Base ID for MFD cells. + * @man_mutex: Management mutex. + */ +struct main_info { + struct device *dev; + int cell_base_id; + struct mutex man_mutex; +}; + +static struct main_info *main_info; + +/* + * main_wait_queue - Main Wait Queue in CG2900 driver. + */ +static DECLARE_WAIT_QUEUE_HEAD(main_wait_queue); +/* + * Clock enabler should be released earlier + * before running read self test + * as that is not required for clock enabler + * as is the case for WLAN + */ +static DECLARE_WAIT_QUEUE_HEAD(clk_user_wait_queue); + +static struct mfd_cell cg2900_devs[]; +static struct mfd_cell cg2900_char_devs[]; +static struct mfd_cell cg2910_extra_devs[]; +static struct mfd_cell cg2910_extra_char_devs[]; + +static void chip_startup_finished(struct cg2900_chip_info *info, int err); +static void chip_shutdown(struct cg2900_user_data *user); + +/** + * bt_is_open() - Checks if any BT user is in open state. + * @info: CG2900 info. + * + * Returns: + * true if a BT channel is open. + * false if no BT channel is open. + */ +static bool bt_is_open(struct cg2900_chip_info *info) +{ + struct list_head *cursor; + struct cg2900_channel_item *tmp; + + list_for_each(cursor, &info->open_channels) { + tmp = list_entry(cursor, struct cg2900_channel_item, list); + if (tmp->user->h4_channel == CHANNEL_BT_CMD) + return true; + } + return false; +} + +/** + * fm_is_open() - Checks if any FM user is in open state. + * @info: CG2900 info. + * + * Returns: + * true if a FM channel is open. + * false if no FM channel is open. + */ +static bool fm_is_open(struct cg2900_chip_info *info) +{ + struct list_head *cursor; + struct cg2900_channel_item *tmp; + + list_for_each(cursor, &info->open_channels) { + tmp = list_entry(cursor, struct cg2900_channel_item, list); + if (tmp->user->h4_channel == CHANNEL_FM_RADIO) + return true; + } + return false; +} + +/** + * fm_irpt_expected() - check if this FM command will generate an interrupt. + * @cmd_id: command identifier. + * + * Returns: + * true if the command will generate an interrupt. + * false if it won't. + */ +static bool fm_irpt_expected(struct cg2900_chip_info *info, u16 cmd_id) +{ + bool retval = false; + + switch (cmd_id) { + case CG2900_FM_DO_AIP_FADE_START: + if (info->fm_radio_mode == FM_RADIO_MODE_FMT) + retval = true; + break; + + case CG2900_FM_DO_AUP_BT_FADE_START: + case CG2900_FM_DO_AUP_EXT_FADE_START: + case CG2900_FM_DO_AUP_FADE_START: + if (info->fm_radio_mode == FM_RADIO_MODE_FMR) + retval = true; + break; + + case CG2900_FM_DO_FMR_SETANTENNA: + case CG2900_FM_DO_FMR_SP_AFSWITCH_START: + case CG2900_FM_DO_FMR_SP_AFUPDATE_START: + case CG2900_FM_DO_FMR_SP_BLOCKSCAN_START: + case CG2900_FM_DO_FMR_SP_PRESETPI_START: + case CG2900_FM_DO_FMR_SP_SCAN_START: + case CG2900_FM_DO_FMR_SP_SEARCH_START: + case CG2900_FM_DO_FMR_SP_SEARCHPI_START: + case CG2900_FM_DO_FMR_SP_TUNE_SETCHANNEL: + case CG2900_FM_DO_FMR_SP_TUNE_STEPCHANNEL: + case CG2900_FM_DO_FMT_PA_SETCTRL: + case CG2900_FM_DO_FMT_PA_SETMODE: + case CG2900_FM_DO_FMT_SP_TUNE_SETCHANNEL: + case CG2900_FM_DO_GEN_ANTENNACHECK_START: + case CG2900_FM_DO_GEN_GOTOMODE: + case CG2900_FM_DO_GEN_POWERSUPPLY_SETMODE: + case CG2900_FM_DO_GEN_SELECTREFERENCECLOCK: + case CG2900_FM_DO_GEN_SETPROCESSINGCLOCK: + case CG2900_FM_DO_GEN_SETREFERENCECLOCKPLL: + case CG2900_FM_DO_TST_TX_RAMP_START: + retval = true; + break; + + default: + break; + } + + if (retval) + dev_dbg(info->dev, "Following interrupt event expected for this" + " Cmd complete evt: cmd_id = 0x%X\n", + cmd_id); + + return retval; +} + +/** + * fm_is_do_cmd_irpt() - Check if irpt_val is one of the FM DO command related interrupts. + * @irpt_val: interrupt value. + * + * Returns: + * true if it's do-command related interrupt value. + * false if it's not. + */ +static bool fm_is_do_cmd_irpt(u16 irpt_val) +{ + if ((irpt_val & CG2900_FM_IRPT_OPERATION_SUCCEEDED) || + (irpt_val & CG2900_FM_IRPT_OPERATION_FAILED)) { + dev_dbg(MAIN_DEV, "Irpt evt for FM do-command found, " + "irpt_val = 0x%X\n", irpt_val); + return true; + } + + return false; +} + +/** + * fm_reset_flow_ctrl - Clears up internal FM flow control. + * + * Resets outstanding commands and clear FM TX list and set CG2900 FM mode to + * idle. + */ +static void fm_reset_flow_ctrl(struct cg2900_chip_info *info) +{ + dev_dbg(info->dev, "fm_reset_flow_ctrl\n"); + + skb_queue_purge(&info->tx_queue_fm); + + /* Reset the fm_cmd_id. */ + info->audio_fm_cmd_id = CG2900_FM_CMD_NONE; + info->hci_fm_cmd_func = CG2900_FM_CMD_PARAM_NONE; + + info->fm_radio_mode = FM_RADIO_MODE_IDLE; +} + + +/** + * fm_parse_cmd - Parses a FM command packet. + * @data: FM command packet. + * @cmd_func: Out: FM legacy command function. + * @cmd_id: Out: FM legacy command ID. + */ +static void fm_parse_cmd(u8 *data, u8 *cmd_func, u16 *cmd_id) +{ + /* Move past H4-header to start of actual package */ + struct fm_leg_cmd *pkt = (struct fm_leg_cmd *)(data + HCI_H4_SIZE); + + *cmd_func = CG2900_FM_CMD_PARAM_NONE; + *cmd_id = CG2900_FM_CMD_NONE; + + if (pkt->opcode != CG2900_FM_GEN_ID_LEGACY) { + dev_err(MAIN_DEV, "fm_parse_cmd: Not an FM legacy command " + "0x%02X\n", pkt->opcode); + return; + } + + *cmd_func = pkt->fm_function; + if (*cmd_func == CG2900_FM_CMD_PARAM_WRITECOMMAND) + *cmd_id = cg2900_get_fm_cmd_id(le16_to_cpu(pkt->fm_cmd.head)); +} + + +/** + * fm_parse_event - Parses a FM event packet + * @data: FM event packet. + * @event: Out: FM event. + * @cmd_func: Out: FM legacy command function. + * @cmd_id: Out: FM legacy command ID. + * @intr_val: Out: FM interrupt value. + */ +static void fm_parse_event(u8 *data, u8 *event, u8 *cmd_func, u16 *cmd_id, + u16 *intr_val) +{ + /* Move past H4-header to start of actual package */ + union fm_leg_evt_or_irq *pkt = (union fm_leg_evt_or_irq *)data; + + *cmd_func = CG2900_FM_CMD_PARAM_NONE; + *cmd_id = CG2900_FM_CMD_NONE; + *intr_val = 0; + *event = CG2900_FM_EVENT_UNKNOWN; + + if (pkt->evt.opcode == CG2900_FM_GEN_ID_LEGACY && + pkt->evt.read_write == CG2900_FM_CMD_LEG_PARAM_WRITE) { + /* Command complete */ + *event = CG2900_FM_EVENT_CMD_COMPLETE; + *cmd_func = pkt->evt.fm_function; + if (*cmd_func == CG2900_FM_CMD_PARAM_WRITECOMMAND) + *cmd_id = cg2900_get_fm_cmd_id( + le16_to_cpu(pkt->evt.response_head)); + } else if (pkt->irq_v2.opcode == CG2900_FM_GEN_ID_LEGACY && + pkt->irq_v2.event_type == CG2900_FM_CMD_LEG_PARAM_IRQ) { + /* Interrupt, PG2 style */ + *event = CG2900_FM_EVENT_INTERRUPT; + *intr_val = le16_to_cpu(pkt->irq_v2.irq); + } else if (pkt->irq_v1.opcode == CG2900_FM_GEN_ID_LEGACY) { + /* Interrupt, PG1 style */ + *event = CG2900_FM_EVENT_INTERRUPT; + *intr_val = le16_to_cpu(pkt->irq_v1.irq); + } else + dev_err(MAIN_DEV, "fm_parse_event: Not an FM legacy command " + "0x%X %X %X %X\n", data[0], data[1], data[2], data[3]); +} + +/** + * fm_update_mode - Updates the FM mode state machine. + * @data: FM command packet. + * + * Parses a FM command packet and updates the FM mode state machine. + */ +static void fm_update_mode(struct cg2900_chip_info *info, u8 *data) +{ + u8 cmd_func; + u16 cmd_id; + + fm_parse_cmd(data, &cmd_func, &cmd_id); + + if (cmd_func == CG2900_FM_CMD_PARAM_WRITECOMMAND && + cmd_id == CG2900_FM_DO_GEN_GOTOMODE) { + /* Move past H4-header to start of actual package */ + struct fm_leg_cmd *pkt = + (struct fm_leg_cmd *)(data + HCI_H4_SIZE); + + info->fm_radio_mode = le16_to_cpu(pkt->fm_cmd.data[0]); + dev_dbg(info->dev, "FM Radio mode changed to %d\n", + info->fm_radio_mode); + } +} + + +/** + * transmit_skb_from_tx_queue_bt() - Check flow control info and transmit skb. + * + * The transmit_skb_from_tx_queue_bt() function checks if there are tickets + * available and commands waiting in the TX queue and if so transmits them + * to the controller. + * It shall always be called within spinlock_bh. + */ +static void transmit_skb_from_tx_queue_bt(struct cg2900_chip_dev *dev) +{ + struct cg2900_user_data *user; + struct cg2900_chip_info *info = dev->c_data; + struct sk_buff *skb; + + dev_dbg(dev->dev, "transmit_skb_from_tx_queue_bt\n"); + + /* Dequeue an skb from the head of the list */ + skb = skb_dequeue(&info->tx_queue_bt); + while (skb) { + if (info->tx_nr_pkts_allowed_bt <= 0) { + /* + * If no more packets allowed just return, we'll get + * back here after next Command Complete/Status event. + * Put skb back at head of queue. + */ + skb_queue_head(&info->tx_queue_bt, skb); + return; + } + + (info->tx_nr_pkts_allowed_bt)--; + dev_dbg(dev->dev, "tx_nr_pkts_allowed_bt = %d\n", + info->tx_nr_pkts_allowed_bt); + + user = cg2900_skb_data(skb)->user; /* user is never NULL */ + + /* + * If it's a command from audio application, store the OpCode, + * it'll be used later to decide where to dispatch + * the Command Complete event. + */ + if (info->bt_audio == user) { + struct hci_command_hdr *hdr = (struct hci_command_hdr *) + (skb->data + HCI_H4_SIZE); + + info->audio_bt_cmd_op = le16_to_cpu(hdr->opcode); + dev_dbg(user->dev, + "Sending cmd from audio driver, saving " + "OpCode = 0x%04X\n", info->audio_bt_cmd_op); + } + + cg2900_tx_to_chip(user, info->logger, skb); + + /* Dequeue an skb from the head of the list */ + skb = skb_dequeue(&info->tx_queue_bt); + } +} + +/** + * transmit_skb_from_tx_queue_fm() - Check flow control info and transmit skb. + * + * The transmit_skb_from_tx_queue_fm() function checks if it possible to + * transmit and commands waiting in the TX queue and if so transmits them + * to the controller. + * It shall always be called within spinlock_bh. + */ +static void transmit_skb_from_tx_queue_fm(struct cg2900_chip_dev *dev) +{ + struct cg2900_user_data *user; + struct cg2900_chip_info *info = dev->c_data; + struct sk_buff *skb; + + dev_dbg(dev->dev, "transmit_skb_from_tx_queue_fm\n"); + + /* Dequeue an skb from the head of the list */ + skb = skb_dequeue(&info->tx_queue_fm); + while (skb) { + u16 cmd_id; + u8 cmd_func; + + if (info->audio_fm_cmd_id != CG2900_FM_CMD_NONE || + info->hci_fm_cmd_func != CG2900_FM_CMD_PARAM_NONE) { + /* + * There are currently outstanding FM commands. + * Wait for them to finish. We will get back here later. + * Queue back the skb at head of list. + */ + skb_queue_head(&info->tx_queue_bt, skb); + return; + } + + user = cg2900_skb_data(skb)->user; /* user is never NULL */ + + if (!user->opened) { + /* + * Channel is not open. That means that the user that + * originally sent it has deregistered. + * Just throw it away and check the next skb in the + * queue. + */ + kfree_skb(skb); + /* Dequeue an skb from the head of the list */ + skb = skb_dequeue(&info->tx_queue_fm); + continue; + } + + fm_parse_cmd(&(skb->data[0]), &cmd_func, &cmd_id); + + /* + * Store the FM command function , it'll be used later to decide + * where to dispatch the Command Complete event. + */ + if (info->fm_audio == user) { + info->audio_fm_cmd_id = cmd_id; + dev_dbg(user->dev, "Sending FM audio cmd 0x%04X\n", + info->audio_fm_cmd_id); + } else { + /* FM radio command */ + info->hci_fm_cmd_func = cmd_func; + fm_update_mode(info, &skb->data[0]); + dev_dbg(user->dev, "Sending FM radio cmd 0x%04X\n", + info->hci_fm_cmd_func); + } + + /* + * We have only one ticket on FM. Just return after + * sending the skb. + */ + cg2900_tx_to_chip(user, info->logger, skb); + return; + } +} + +/** + * update_flow_ctrl_bt() - Update number of outstanding commands for BT CMD. + * @dev: Current chip device. + * @skb: skb with received packet. + * + * The update_flow_ctrl_bt() checks if incoming data packet is + * BT Command Complete/Command Status Event and if so updates number of tickets + * and number of outstanding commands. It also calls function to send queued + * commands (if the list of queued commands is not empty). + */ +static void update_flow_ctrl_bt(struct cg2900_chip_dev *dev, + const struct sk_buff * const skb) +{ + u8 *data = skb->data; + struct hci_event_hdr *event; + struct cg2900_chip_info *info = dev->c_data; + + event = (struct hci_event_hdr *)data; + data += sizeof(*event); + + if (HCI_EV_CMD_COMPLETE == event->evt) { + struct hci_ev_cmd_complete *complete; + complete = (struct hci_ev_cmd_complete *)data; + + /* + * If it's HCI Command Complete Event then we might get some + * HCI tickets back. Also we can decrease the number outstanding + * HCI commands (if it's not NOP command or one of the commands + * that generate both Command Status Event and Command Complete + * Event). + * Check if we have any HCI commands waiting in the TX list and + * send them if there are tickets available. + */ + spin_lock_bh(&info->tx_bt_lock); + info->tx_nr_pkts_allowed_bt = complete->ncmd; + dev_dbg(dev->dev, "New tx_nr_pkts_allowed_bt = %d\n", + info->tx_nr_pkts_allowed_bt); + + if (!skb_queue_empty(&info->tx_queue_bt)) + transmit_skb_from_tx_queue_bt(dev); + spin_unlock_bh(&info->tx_bt_lock); + } else if (HCI_EV_CMD_STATUS == event->evt) { + struct hci_ev_cmd_status *status; + status = (struct hci_ev_cmd_status *)data; + + /* + * If it's HCI Command Status Event then we might get some + * HCI tickets back. Also we can decrease the number outstanding + * HCI commands (if it's not NOP command). + * Check if we have any HCI commands waiting in the TX queue and + * send them if there are tickets available. + */ + spin_lock_bh(&info->tx_bt_lock); + info->tx_nr_pkts_allowed_bt = status->ncmd; + dev_dbg(dev->dev, "New tx_nr_pkts_allowed_bt = %d\n", + info->tx_nr_pkts_allowed_bt); + + if (!skb_queue_empty(&info->tx_queue_bt)) + transmit_skb_from_tx_queue_bt(dev); + spin_unlock_bh(&info->tx_bt_lock); + } +} + +/** + * update_flow_ctrl_fm() - Update packets allowed for FM channel. + * @dev: Current chip device. + * @skb: skb with received packet. + * + * The update_flow_ctrl_fm() checks if incoming data packet is FM packet + * indicating that the previous command has been handled and if so update + * packets. It also calls function to send queued commands (if the list of + * queued commands is not empty). + */ +static void update_flow_ctrl_fm(struct cg2900_chip_dev *dev, + const struct sk_buff * const skb) +{ + u8 cmd_func = CG2900_FM_CMD_PARAM_NONE; + u16 cmd_id = CG2900_FM_CMD_NONE; + u16 irpt_val = 0; + u8 event = CG2900_FM_EVENT_UNKNOWN; + struct cg2900_chip_info *info = dev->c_data; + + fm_parse_event(&(skb->data[0]), &event, &cmd_func, &cmd_id, &irpt_val); + + if (event == CG2900_FM_EVENT_CMD_COMPLETE) { + /* FM legacy command complete event */ + spin_lock_bh(&info->tx_fm_lock); + /* + * Check if it's not an write command complete event, because + * then it cannot be a DO command. + * If it's a write command complete event check that is not a + * DO command complete event before setting the outstanding + * FM packets to none. + */ + if (cmd_func != CG2900_FM_CMD_PARAM_WRITECOMMAND || + !fm_irpt_expected(info, cmd_id)) { + info->hci_fm_cmd_func = CG2900_FM_CMD_PARAM_NONE; + info->audio_fm_cmd_id = CG2900_FM_CMD_NONE; + dev_dbg(dev->dev, + "FM_Write: Outstanding FM commands:\n" + "\tRadio: 0x%04X\n" + "\tAudio: 0x%04X\n", + info->hci_fm_cmd_func, + info->audio_fm_cmd_id); + transmit_skb_from_tx_queue_fm(dev); + + /* + * If there was a write do command complete event check if it is + * DO command previously sent by the FM audio user. If that's + * the case we need remember that in order to be able to + * dispatch the interrupt to the correct user. + */ + } else if (cmd_id == info->audio_fm_cmd_id) { + info->tx_fm_audio_awaiting_irpt = true; + dev_dbg(dev->dev, + "FM Audio waiting for interrupt = true\n"); + } + spin_unlock_bh(&info->tx_fm_lock); + } else if (event == CG2900_FM_EVENT_INTERRUPT) { + /* FM legacy interrupt */ + if (fm_is_do_cmd_irpt(irpt_val)) { + /* + * If it is an interrupt related to a DO command update + * the outstanding flow control and transmit blocked + * FM commands. + */ + spin_lock_bh(&info->tx_fm_lock); + info->hci_fm_cmd_func = CG2900_FM_CMD_PARAM_NONE; + info->audio_fm_cmd_id = CG2900_FM_CMD_NONE; + dev_dbg(dev->dev, + "FM_INT: Outstanding FM commands:\n" + "\tRadio: 0x%04X\n" + "\tAudio: 0x%04X\n", + info->hci_fm_cmd_func, + info->audio_fm_cmd_id); + info->tx_fm_audio_awaiting_irpt = false; + dev_dbg(dev->dev, + "FM Audio waiting for interrupt = false\n"); + transmit_skb_from_tx_queue_fm(dev); + spin_unlock_bh(&info->tx_fm_lock); + } + } +} + +/** + * send_bt_enable() - Send HCI VS BT Enable command to the chip. + * @info: Chip info structure. + * @bt_enable: Value for BT Enable parameter (e.g. CG2900_BT_DISABLE). + */ +static void send_bt_enable(struct cg2900_chip_info *info, u8 bt_enable) +{ + struct bt_vs_bt_enable_cmd cmd; + + cmd.op_code = cpu_to_le16(CG2900_BT_OP_VS_BT_ENABLE); + cmd.plen = BT_PARAM_LEN(sizeof(cmd)); + cmd.enable = bt_enable; + cg2900_send_bt_cmd(info->user_in_charge, info->logger, + &cmd, sizeof(cmd)); +} + +/** + * send_bd_address() - Send HCI VS command with BD address to the chip. + */ +static void send_bd_address(struct cg2900_chip_info *info) +{ + struct bt_vs_store_in_fs_cmd *cmd; + u8 plen = sizeof(*cmd) + BT_BDADDR_SIZE; + + cmd = kmalloc(plen, GFP_KERNEL); + if (!cmd) { + dev_err(info->dev, "send_bd_address could not allocate cmd\n"); + return; + } + + cmd->opcode = cpu_to_le16(CG2900_BT_OP_VS_STORE_IN_FS); + cmd->plen = BT_PARAM_LEN(plen); + cmd->user_id = CG2900_VS_STORE_IN_FS_USR_ID_BD_ADDR; + cmd->len = BT_BDADDR_SIZE; + /* Now copy the BD address received from user space control app. */ + memcpy(cmd->data, bd_address, BT_BDADDR_SIZE); + + dev_dbg(BOOT_DEV, "New boot_state: BOOT_SEND_BD_ADDRESS\n"); + info->boot_state = BOOT_SEND_BD_ADDRESS; + + cg2900_send_bt_cmd(info->user_in_charge, info->logger, cmd, plen); + + kfree(cmd); +} + +/** + * send_settings_file() - Transmit settings file. + * + * The send_settings_file() function transmit settings file. + * The file is read in parts to fit in HCI packets. When finished, + * close the settings file and send HCI reset to activate settings and patches. + */ +static void send_settings_file(struct cg2900_chip_info *info) +{ + int bytes_sent; + + bytes_sent = cg2900_read_and_send_file_part(info->user_in_charge, + info->logger, + &info->file_info); + if (bytes_sent > 0) { + /* Data sent. Wait for CmdComplete */ + return; + } else if (bytes_sent < 0) { + dev_err(BOOT_DEV, "send_settings_file: Error %d occurred\n", + bytes_sent); + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + chip_startup_finished(info, bytes_sent); + return; + } + + /* No data was sent. This file is finished */ + info->download_state = DOWNLOAD_SUCCESS; + + /* Settings file finished. Release used resources */ + dev_dbg(BOOT_DEV, "Settings file finished, release used resources\n"); + release_firmware(info->file_info.fw_file); + info->file_info.fw_file = NULL; + + dev_dbg(BOOT_DEV, "New file_load_state: FILE_LOAD_NO_MORE_FILES\n"); + info->file_load_state = FILE_LOAD_NO_MORE_FILES; + + /* Create and send HCI VS Store In FS command with bd address. */ + send_bd_address(info); +} + +/** + * send_patch_file - Transmit patch file. + * + * The send_patch_file() function transmit patch file. + * The file is read in parts to fit in HCI packets. When the complete file is + * transmitted, the file is closed. + * When finished, continue with settings file. + */ +static void send_patch_file(struct cg2900_chip_dev *dev) +{ + int err; + int bytes_sent; + struct cg2900_chip_info *info = dev->c_data; + int file_name_size = strlen("CG29XX_XXXX_XXXX_settings.fw"); + + bytes_sent = cg2900_read_and_send_file_part(info->user_in_charge, + info->logger, + &info->file_info); + if (bytes_sent > 0) { + /* Data sent. Wait for CmdComplete */ + return; + } else if (bytes_sent < 0) { + dev_err(BOOT_DEV, "send_patch_file: Error %d occurred\n", + bytes_sent); + err = bytes_sent; + goto error_handling; + } + + /* No data was sent. This file is finished */ + info->download_state = DOWNLOAD_SUCCESS; + + dev_dbg(BOOT_DEV, "Patch file finished, release used resources\n"); + release_firmware(info->file_info.fw_file); + info->file_info.fw_file = NULL; + + /* + * Create the settings file name from HCI revision and sub_version. + * file_name_size does not include terminating NULL character + * so add 1. + */ + err = snprintf(info->settings_file_name, file_name_size + 1, + "CG29XX_%04X_%04X_settings.fw", dev->chip.hci_revision, + dev->chip.hci_sub_version); + if (err == file_name_size) { + dev_dbg(BOOT_DEV, "Downloading settings file %s\n", + info->settings_file_name); + } else { + dev_err(BOOT_DEV, "Settings file name failed! err=%d\n", err); + goto error_handling; + } + + /* Retrieve the settings file */ + err = request_firmware(&info->file_info.fw_file, + info->settings_file_name, + info->dev); + if (err) { + dev_err(BOOT_DEV, "Couldn't get settings file (%d)\n", err); + goto error_handling; + } + /* Now send the settings file */ + dev_dbg(BOOT_DEV, + "New file_load_state: FILE_LOAD_GET_STATIC_SETTINGS\n"); + info->file_load_state = FILE_LOAD_GET_STATIC_SETTINGS; + dev_dbg(BOOT_DEV, "New download_state: DOWNLOAD_PENDING\n"); + info->download_state = DOWNLOAD_PENDING; + send_settings_file(info); + return; + +error_handling: + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + chip_startup_finished(info, err); +} + +/** + * work_power_off_chip() - Work item to power off the chip. + * @work: Reference to work data. + * + * The work_power_off_chip() function handles transmission of the HCI command + * vs_power_switch_off and then informs the CG2900 Core that this chip driver is + * finished and the Core driver can now shut off the chip. + */ +static void work_power_off_chip(struct work_struct *work) +{ + struct sk_buff *skb = NULL; + u8 *h4_header; + struct cg2900_platform_data *pf_data; + struct cg2900_work *my_work; + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + + if (!work) { + dev_err(MAIN_DEV, "work_power_off_chip: work == NULL\n"); + return; + } + + my_work = container_of(work, struct cg2900_work, work); + dev = my_work->user_data; + info = dev->c_data; + + /* + * Get the VS Power Switch Off command to use based on connected + * connectivity controller + */ + pf_data = dev_get_platdata(dev->dev); + if (pf_data->get_power_switch_off_cmd) + skb = pf_data->get_power_switch_off_cmd(dev, NULL); + + /* + * Transmit the received command. + * If no command found for the device, just continue + */ + if (!skb) { + dev_err(dev->dev, + "Could not retrieve PowerSwitchOff command\n"); + goto shut_down_chip; + } + + dev_dbg(dev->dev, + "Got power_switch_off command. Add H4 header and transmit\n"); + + /* + * Move the data pointer to the H:4 header position and store + * the H4 header + */ + h4_header = skb_push(skb, CG2900_SKB_RESERVE); + *h4_header = CHANNEL_BT_CMD; + + dev_dbg(dev->dev, "New closing_state: CLOSING_POWER_SWITCH_OFF\n"); + info->closing_state = CLOSING_POWER_SWITCH_OFF; + + if (info->user_in_charge) + cg2900_tx_to_chip(info->user_in_charge, info->logger, skb); + else + cg2900_tx_no_user(dev, skb); + + /* + * Mandatory to wait 500ms after the power_switch_off command has been + * transmitted, in order to make sure that the controller is ready. + */ + schedule_timeout_killable(msecs_to_jiffies(POWER_SW_OFF_WAIT)); + +shut_down_chip: + dev_dbg(dev->dev, "New closing_state: CLOSING_SHUT_DOWN\n"); + info->closing_state = CLOSING_SHUT_DOWN; + + /* Close the transport, which will power off the chip */ + if (dev->t_cb.close) + dev->t_cb.close(dev); + + /* Chip shut-down finished, set correct state and wake up the chip. */ + dev_dbg(dev->dev, "New main_state: CG2900_IDLE\n"); + info->main_state = CG2900_IDLE; + wake_up_all(&main_wait_queue); + + /* If this is called during system startup, register the devices. */ + if (info->startup) { + int err; + + err = mfd_add_devices(dev->dev, main_info->cell_base_id, + cg2900_devs, info->mfd_size, NULL, 0); + if (err) { + dev_err(dev->dev, "Failed to add cg2900_devs (%d)\n", + err); + goto finished; + } + + err = mfd_add_devices(dev->dev, main_info->cell_base_id, + cg2900_char_devs, info->mfd_char_size, + NULL, 0); + if (err) { + dev_err(dev->dev, "Failed to add cg2900_char_devs (%d)" + "\n", err); + mfd_remove_devices(dev->dev); + goto finished; + } + + /* + * Increase base ID so next connected transport will not get the + * same device IDs. + */ + main_info->cell_base_id += + MAX(info->mfd_size, info->mfd_char_size); + + if (dev->chip.hci_revision == CG2910_PG1_REV || + dev->chip.hci_revision == CG2910_PG2_REV) { + err = mfd_add_devices(dev->dev, main_info->cell_base_id, + cg2910_extra_devs, + info->mfd_extra_size, NULL, 0); + if (err) { + dev_err(dev->dev, "Failed to add cg2910_extra_devs " + "(%d)\n", err); + goto finished; + } + + err = mfd_add_devices(dev->dev, main_info->cell_base_id, + cg2910_extra_char_devs, + info->mfd_extra_char_size, NULL, 0); + if (err) { + dev_err(dev->dev, "Failed to add cg2910_extra_char_devs " + "(%d)\n", err); + mfd_remove_devices(dev->dev); + goto finished; + } + + /* + * Increase base ID so next connected transport + * will not get the same device IDs. + */ + main_info->cell_base_id += + MAX(info->mfd_extra_size, + info->mfd_extra_char_size); + } + + info->startup = false; + } + +finished: + kfree(my_work); +} + +/** + * work_chip_shutdown() - Shut down the chip. + * @work: Reference to work data. + */ +static void work_chip_shutdown(struct work_struct *work) +{ + struct cg2900_work *my_work; + struct cg2900_user_data *user; + + if (!work) { + dev_err(MAIN_DEV, "work_chip_shutdown: work == NULL\n"); + return; + } + + my_work = container_of(work, struct cg2900_work, work); + user = my_work->user_data; + + chip_shutdown(user); + + kfree(my_work); +} + +/** + * work_reset_after_error() - Handle reset. + * @work: Reference to work data. + * + * Handle a reset after received Command Complete event. + */ +static void work_reset_after_error(struct work_struct *work) +{ + struct cg2900_work *my_work; + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + + if (!work) { + dev_err(MAIN_DEV, "work_reset_after_error: work == NULL\n"); + return; + } + + my_work = container_of(work, struct cg2900_work, work); + dev = my_work->user_data; + info = dev->c_data; + + chip_startup_finished(info, -EIO); + + kfree(my_work); +} + +/** + * work_load_patch_and_settings() - Start loading patches and settings. + * @work: Reference to work data. + */ +static void work_load_patch_and_settings(struct work_struct *work) +{ + int err = 0; + struct cg2900_work *my_work; + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + int file_name_size = strlen("CG29XX_XXXX_XXXX_patch.fw"); + + if (!work) { + dev_err(MAIN_DEV, + "work_load_patch_and_settings: work == NULL\n"); + return; + } + + my_work = container_of(work, struct cg2900_work, work); + dev = my_work->user_data; + info = dev->c_data; + + /* Check that we are in the right state */ + if (info->boot_state != BOOT_GET_FILES_TO_LOAD) + goto finished; + + /* + * Create the patch file name from HCI revision and sub_version. + * file_name_size does not include terminating NULL character + * so add 1. + */ + err = snprintf(info->patch_file_name, file_name_size + 1, + "CG29XX_%04X_%04X_patch.fw", dev->chip.hci_revision, + dev->chip.hci_sub_version); + if (err == file_name_size) { + dev_dbg(BOOT_DEV, "Downloading patch file %s\n", + info->patch_file_name); + } else { + dev_err(BOOT_DEV, "Patch file name failed! err=%d\n", err); + goto error_handling; + } + + /* We now all info needed */ + dev_dbg(BOOT_DEV, "New boot_state: BOOT_DOWNLOAD_PATCH\n"); + info->boot_state = BOOT_DOWNLOAD_PATCH; + dev_dbg(BOOT_DEV, "New download_state: DOWNLOAD_PENDING\n"); + info->download_state = DOWNLOAD_PENDING; + dev_dbg(BOOT_DEV, "New file_load_state: FILE_LOAD_GET_PATCH\n"); + info->file_load_state = FILE_LOAD_GET_PATCH; + info->file_info.chunk_id = 0; + info->file_info.file_offset = 0; + info->file_info.fw_file = NULL; + + /* OK. Now it is time to download the patches */ + err = request_firmware(&(info->file_info.fw_file), + info->patch_file_name, + dev->dev); + if (err < 0) { + dev_err(BOOT_DEV, "Couldn't get patch file (%d)\n", err); + goto error_handling; + } + send_patch_file(dev); + + goto finished; + +error_handling: + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + chip_startup_finished(info, -EIO); +finished: + kfree(my_work); +} + +/** + * work_cont_file_download() - A file block has been written. + * @work: Reference to work data. + * + * Handle a received HCI VS Write File Block Complete event. + * Normally this means continue to send files to the controller. + */ +static void work_cont_file_download(struct work_struct *work) +{ + struct cg2900_work *my_work; + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + + if (!work) { + dev_err(MAIN_DEV, "work_cont_file_download: work == NULL\n"); + return; + } + + my_work = container_of(work, struct cg2900_work, work); + dev = my_work->user_data; + info = dev->c_data; + + /* Continue to send patches or settings to the controller */ + if (info->file_load_state == FILE_LOAD_GET_PATCH) + send_patch_file(dev); + else if (info->file_load_state == FILE_LOAD_GET_STATIC_SETTINGS) + send_settings_file(info); + else + dev_dbg(BOOT_DEV, "No more files to load\n"); + + kfree(my_work); +} + +/** + * work_send_read_selftest_cmd() - HCI VS Read_SelfTests_Result command shall be sent. + * @work: Reference to work data. + */ +static void work_send_read_selftest_cmd(struct work_struct *work) +{ + struct delayed_work *del_work; + struct cg2900_delayed_work_struct *current_work; + struct cg2900_chip_info *info; + struct hci_command_hdr cmd; + + if (!work) { + dev_err(MAIN_DEV, + "work_send_read_selftest_cmd: work == NULL\n"); + return; + } + + del_work = to_delayed_work(work); + current_work = container_of(del_work, + struct cg2900_delayed_work_struct, work); + info = current_work->data; + + if (info->boot_state != BOOT_READ_SELFTEST_RESULT) + return; + + cmd.opcode = cpu_to_le16(CG2900_BT_OP_VS_READ_SELTESTS_RESULT); + cmd.plen = 0; /* No parameters for Read Selftests Result */ + cg2900_send_bt_cmd(info->user_in_charge, info->logger, &cmd, + sizeof(cmd)); +} + +/** + * handle_reset_cmd_complete() - Handles HCI Reset Command Complete event. + * @data: Pointer to received HCI data packet. + * + * Returns: + * true, if packet was handled internally, + * false, otherwise. + */ +static bool handle_reset_cmd_complete(struct cg2900_chip_dev *dev, u8 *data) +{ + u8 status = data[0]; + struct cg2900_chip_info *info = dev->c_data; + + dev_dbg(BOOT_DEV, "Received Reset complete event with status 0x%X\n", + status); + + if (CG2900_CLOSING != info->main_state && + CLOSING_RESET != info->closing_state) + return false; + + if (HCI_BT_ERROR_NO_ERROR != status) { + /* + * Continue in case of error, the chip is going to be shut down + * anyway. + */ + dev_err(BOOT_DEV, "Command complete for HciReset received with " + "error 0x%X\n", status); + } + + cg2900_create_work_item(info->wq, work_power_off_chip, dev); + + return true; +} + +/** + * handle_vs_store_in_fs_cmd_complete() - Handles HCI VS StoreInFS Command Complete event. + * @data: Pointer to received HCI data packet. + * + * Returns: + * true, if packet was handled internally, + * false, otherwise. + */ +static bool handle_vs_store_in_fs_cmd_complete(struct cg2900_chip_dev *dev, + u8 *data) +{ + u8 status = data[0]; + struct cg2900_chip_info *info = dev->c_data; + + dev_dbg(BOOT_DEV, + "Received Store_in_FS complete event with status 0x%X\n", + status); + + if (info->boot_state != BOOT_SEND_BD_ADDRESS) + return false; + + if (HCI_BT_ERROR_NO_ERROR == status) { + struct hci_command_hdr cmd; + + /* Send HCI SystemReset command to activate patches */ + dev_dbg(BOOT_DEV, + "New boot_state: BOOT_ACTIVATE_PATCHES_AND_SETTINGS\n"); + info->boot_state = BOOT_ACTIVATE_PATCHES_AND_SETTINGS; + + cmd.opcode = cpu_to_le16(CG2900_BT_OP_VS_SYSTEM_RESET); + cmd.plen = 0; /* No parameters for System Reset */ + cg2900_send_bt_cmd(info->user_in_charge, info->logger, &cmd, + sizeof(cmd)); + } else { + dev_err(BOOT_DEV, + "Command complete for StoreInFS received with error " + "0x%X\n", status); + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + cg2900_create_work_item(info->wq, work_reset_after_error, dev); + } + + return true; +} + +/** + * handle_vs_write_file_block_cmd_complete() - Handles HCI VS WriteFileBlock Command Complete event. + * @data: Pointer to received HCI data packet. + * + * Returns: + * true, if packet was handled internally, + * false, otherwise. + */ +static bool handle_vs_write_file_block_cmd_complete(struct cg2900_chip_dev *dev, + u8 *data) +{ + u8 status = data[0]; + struct cg2900_chip_info *info = dev->c_data; + + if (info->boot_state != BOOT_DOWNLOAD_PATCH || + info->download_state != DOWNLOAD_PENDING) + return false; + + if (HCI_BT_WRONG_SEQ_ERROR == status && info->file_info.chunk_id == 1 && + (CG2905_PG1_1_REV == dev->chip.hci_revision || + CG2910_PG1_REV == dev->chip.hci_revision)) { + /* + * Because of bug in CG2905/CG2910 PG1 H/W, the first chunk + * will return an error of wrong sequence number. As a + * workaround the first chunk needs to be sent again. + */ + info->file_info.chunk_id = 0; + info->file_info.file_offset = 0; + /* + * Set the status back to success so that it continues on the + * success path rather than failure. + */ + status = HCI_BT_ERROR_NO_ERROR; + } + + if (HCI_BT_ERROR_NO_ERROR == status) + cg2900_create_work_item(info->wq, work_cont_file_download, dev); + else { + dev_err(BOOT_DEV, + "Command complete for WriteFileBlock received with" + " error 0x%X\n", status); + dev_dbg(BOOT_DEV, "New download_state: DOWNLOAD_FAILED\n"); + info->download_state = DOWNLOAD_FAILED; + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + if (info->file_info.fw_file) { + release_firmware(info->file_info.fw_file); + info->file_info.fw_file = NULL; + } + cg2900_create_work_item(info->wq, work_reset_after_error, dev); + } + + return true; +} + +/** + * handle_vs_write_file_block_cmd_status() - Handles HCI VS WriteFileBlock Command Status event. + * @status: Returned status of WriteFileBlock command. + * + * Returns: + * true, if packet was handled internally, + * false, otherwise. + */ +static bool handle_vs_write_file_block_cmd_status(struct cg2900_chip_dev *dev, + u8 status) +{ + struct cg2900_chip_info *info = dev->c_data; + + if (info->boot_state != BOOT_DOWNLOAD_PATCH || + info->download_state != DOWNLOAD_PENDING) + return false; + + /* + * Only do something if there is an error. Otherwise we will wait for + * CmdComplete. + */ + if (HCI_BT_ERROR_NO_ERROR != status) { + dev_err(BOOT_DEV, + "Command status for WriteFileBlock received with" + " error 0x%X\n", status); + dev_dbg(BOOT_DEV, "New download_state: DOWNLOAD_FAILED\n"); + info->download_state = DOWNLOAD_FAILED; + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + if (info->file_info.fw_file) { + release_firmware(info->file_info.fw_file); + info->file_info.fw_file = NULL; + } + cg2900_create_work_item(info->wq, work_reset_after_error, dev); + } + + return true; +} + +/** + * handle_vs_power_switch_off_cmd_complete() - Handles HCI VS PowerSwitchOff Command Complete event. + * @data: Pointer to received HCI data packet. + * + * Returns: + * true, if packet was handled internally, + * false, otherwise. + */ +static bool handle_vs_power_switch_off_cmd_complete(struct cg2900_chip_dev *dev, + u8 *data) +{ + u8 status = data[0]; + struct cg2900_chip_info *info = dev->c_data; + + if (CLOSING_POWER_SWITCH_OFF != info->closing_state) + return false; + + dev_dbg(BOOT_DEV, + "handle_vs_power_switch_off_cmd_complete status %d\n", status); + + /* + * We were waiting for this but we don't need to do anything upon + * reception except warn for error status + */ + if (HCI_BT_ERROR_NO_ERROR != status) + dev_err(BOOT_DEV, + "Command Complete for PowerSwitchOff received with " + "error 0x%X", status); + + return true; +} + +/** + * handle_vs_system_reset_cmd_complete() - Handle HCI VS SystemReset Command Complete event. + * @data: Pointer to received HCI data packet. + * + * Returns: + * true, if packet was handled internally, + * false, otherwise. + */ +static bool handle_vs_system_reset_cmd_complete(struct cg2900_chip_dev *dev, + u8 *data) +{ + u8 status = data[0]; + struct cg2900_chip_info *info = dev->c_data; + + if (info->boot_state != BOOT_ACTIVATE_PATCHES_AND_SETTINGS) + return false; + + dev_dbg(BOOT_DEV, "handle_vs_system_reset_cmd_complete status %d\n", + status); + + if (HCI_BT_ERROR_NO_ERROR == status) { + if (dev->chip.hci_revision == CG2900_PG2_REV && + dev->chip.hci_revision == CG2905_PG1_1_REV && + dev->chip.hci_revision == CG2910_PG1_REV && + dev->chip.hci_revision == CG2910_PG2_REV) { + /* + * We must now wait for the selftest results. They will + * take a certain amount of time to finish so start a + * delayed work that will then send the command. + */ + dev_dbg(BOOT_DEV, + "New boot_state: BOOT_READ_SELFTEST_RESULT\n"); + info->boot_state = BOOT_READ_SELFTEST_RESULT; + queue_delayed_work(info->wq, &info->selftest_work.work, + msecs_to_jiffies(SELFTEST_INITIAL)); + info->nbr_of_polls = 0; + } else { + /* + * We are now almost finished. Shut off BT Core. It will + * be re-enabled by the Bluetooth driver when needed. + */ + dev_dbg(BOOT_DEV, "New boot_state: BOOT_DISABLE_BT\n"); + info->boot_state = BOOT_DISABLE_BT; + send_bt_enable(info, CG2900_BT_DISABLE); + } + } else { + dev_err(BOOT_DEV, + "Received Reset complete event with status 0x%X\n", + status); + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + chip_startup_finished(info, -EIO); + } + + return true; +} + +/** + * handle_vs_read_selftests_cmd_complete() - Handle HCI VS ReadSelfTestsResult Command Complete event. + * @dev: Current chip. + * @data: Pointer to received HCI data packet. + * + * Returns: + * true, if packet was handled internally, + * false, otherwise. + */ +static bool handle_vs_read_selftests_cmd_complete(struct cg2900_chip_dev *dev, + u8 *data) +{ + struct bt_vs_read_selftests_result_evt *evt = + (struct bt_vs_read_selftests_result_evt *)data; + struct cg2900_chip_info *info = dev->c_data; + + if (info->boot_state != BOOT_READ_SELFTEST_RESULT) + return false; + + dev_dbg(BOOT_DEV, + "handle_vs_read_selftests_cmd_complete status %d result %d\n", + evt->status, evt->result); + + if (HCI_BT_ERROR_NO_ERROR != evt->status) + goto err_handling; + + if (CG2900_BT_SELFTEST_SUCCESSFUL == evt->result || + CG2900_BT_SELFTEST_FAILED == evt->result) { + if (CG2900_BT_SELFTEST_FAILED == evt->result) + dev_err(BOOT_DEV, "CG2900 self test failed\n"); + + /* + * We are now almost finished. Shut off BT Core. It will + * be re-enabled by the Bluetooth driver when needed. + */ + dev_dbg(BOOT_DEV, "New boot_state: BOOT_DISABLE_BT\n"); + info->boot_state = BOOT_DISABLE_BT; + send_bt_enable(info, CG2900_BT_DISABLE); + return true; + } else if (CG2900_BT_SELFTEST_NOT_COMPLETED == evt->result) { + /* + * Self tests are not yet finished. Wait some more time + * before resending the command + */ + if (info->nbr_of_polls > MAX_NBR_OF_POLLS) { + dev_err(BOOT_DEV, "Selftest results reached max" + " number of polls\n"); + goto err_handling; + } + queue_delayed_work(info->wq, &info->selftest_work.work, + msecs_to_jiffies(SELFTEST_POLLING)); + info->nbr_of_polls++; + return true; + } + +err_handling: + dev_err(BOOT_DEV, + "Received Read SelfTests Result complete event with " + "status 0x%X and result 0x%X\n", + evt->status, evt->result); + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + chip_startup_finished(info, -EIO); + return true; +} + +/** + * handle_vs_bt_enable_cmd_status() - Handles HCI VS BtEnable Command Status event. + * @status: Returned status of BtEnable command. + * + * Returns: + * true, if packet was handled internally, + * false, otherwise. + */ +static bool handle_vs_bt_enable_cmd_status(struct cg2900_chip_dev *dev, + u8 status) +{ + struct cg2900_chip_info *info = dev->c_data; + + if (info->boot_state != BOOT_DISABLE_BT) + return false; + + dev_dbg(BOOT_DEV, "handle_vs_bt_enable_cmd_status status %d\n", status); + + /* + * Only do something if there is an error. Otherwise we will wait for + * CmdComplete. + */ + if (HCI_BT_ERROR_NO_ERROR != status) { + dev_err(BOOT_DEV, + "Received BtEnable status event with status 0x%X\n", + status); + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + chip_startup_finished(info, -EIO); + } + + return true; +} + +/** + * handle_vs_bt_enable_cmd_complete() - Handle HCI VS BtEnable Command Complete event. + * @data: Pointer to received HCI data packet. + * + * Returns: + * true, if packet was handled internally, + * false, otherwise. + */ +static bool handle_vs_bt_enable_cmd_complete(struct cg2900_chip_dev *dev, + u8 *data) +{ + u8 status = data[0]; + struct cg2900_chip_info *info = dev->c_data; + + if (info->boot_state != BOOT_DISABLE_BT) + return false; + + dev_dbg(BOOT_DEV, "handle_vs_bt_enable_cmd_complete status %d\n", + status); + + if (HCI_BT_ERROR_NO_ERROR == status) { + /* + * The boot sequence is now finished successfully. + * Set states and signal to waiting thread. + */ + dev_dbg(BOOT_DEV, "New boot_state: BOOT_READY\n"); + info->boot_state = BOOT_READY; + chip_startup_finished(info, 0); + } else { + dev_err(BOOT_DEV, + "Received BtEnable complete event with status 0x%X\n", + status); + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + chip_startup_finished(info, -EIO); + } + + return true; +} + +/** + * handle_rx_data_bt_evt() - Check if received data should be handled in CG2900 chip driver. + * @skb: Data packet + * + * The handle_rx_data_bt_evt() function checks if received data should be + * handled in CG2900 chip driver. If so handle it correctly. + * Received data is always HCI BT Event. + * + * Returns: + * True, if packet was handled internally, + * False, otherwise. + */ +static bool handle_rx_data_bt_evt(struct cg2900_chip_dev *dev, + struct sk_buff *skb) +{ + bool pkt_handled = false; + /* skb cannot be NULL here so it is safe to de-reference */ + u8 *data = skb->data; + struct hci_event_hdr *evt; + u16 op_code; + + evt = (struct hci_event_hdr *)data; + data += sizeof(*evt); + + /* First check the event code. */ + if (HCI_EV_CMD_COMPLETE == evt->evt) { + struct hci_ev_cmd_complete *cmd_complete; + + cmd_complete = (struct hci_ev_cmd_complete *)data; + op_code = le16_to_cpu(cmd_complete->opcode); + dev_dbg(dev->dev, + "Received Command Complete: op_code = 0x%04X\n", + op_code); + /* Move to first byte after OCF */ + data += sizeof(*cmd_complete); + + if (op_code == HCI_OP_RESET) + pkt_handled = handle_reset_cmd_complete(dev, data); + else if (op_code == CG2900_BT_OP_VS_STORE_IN_FS) + pkt_handled = handle_vs_store_in_fs_cmd_complete(dev, + data); + else if (op_code == CG2900_BT_OP_VS_WRITE_FILE_BLOCK) + pkt_handled = + handle_vs_write_file_block_cmd_complete(dev, + data); + else if (op_code == CG2900_BT_OP_VS_POWER_SWITCH_OFF) + pkt_handled = + handle_vs_power_switch_off_cmd_complete(dev, + data); + else if (op_code == CG2900_BT_OP_VS_SYSTEM_RESET) { + struct cg2900_chip_info *info = dev->c_data; + /* + * Don't wait till READ_SELFTESTS_RESULT is complete + * for clock users + */ + dev_dbg(dev->dev, + "New main_state: CG2900_ACTIVE_BEFORE_SELFTEST\n"); + info->main_state = CG2900_ACTIVE_BEFORE_SELFTEST; + wake_up_all(&clk_user_wait_queue); + + pkt_handled = + handle_vs_system_reset_cmd_complete(dev, data); + } else if (op_code == CG2900_BT_OP_VS_BT_ENABLE) + pkt_handled = handle_vs_bt_enable_cmd_complete(dev, + data); + else if (op_code == CG2900_BT_OP_VS_READ_SELTESTS_RESULT) + pkt_handled = handle_vs_read_selftests_cmd_complete(dev, + data); + } else if (HCI_EV_CMD_STATUS == evt->evt) { + struct hci_ev_cmd_status *cmd_status; + + cmd_status = (struct hci_ev_cmd_status *)data; + + op_code = le16_to_cpu(cmd_status->opcode); + + dev_dbg(dev->dev, "Received Command Status: op_code = 0x%04X\n", + op_code); + + if (op_code == CG2900_BT_OP_VS_WRITE_FILE_BLOCK) + pkt_handled = handle_vs_write_file_block_cmd_status + (dev, cmd_status->status); + else if (op_code == CG2900_BT_OP_VS_BT_ENABLE) + pkt_handled = handle_vs_bt_enable_cmd_status + (dev, cmd_status->status); + } else if (HCI_EV_VENDOR_SPECIFIC == evt->evt) { + /* + * In the new versions of CG29xx i.e. after CG2900 we + * will recieve HCI_Event_VS_Write_File_Block_Complete + * instead of Command Complete while downloading + * the patches/settings file + */ + struct bt_vs_evt *cmd_evt; + cmd_evt = (struct bt_vs_evt *)data; + + if (cmd_evt->evt_id == CG2900_EV_VS_WRITE_FILE_BLOCK_COMPLETE) { + struct bt_vs_write_file_block_evt *write_block_evt; + write_block_evt = + (struct bt_vs_write_file_block_evt *) + cmd_evt->data; + dev_dbg(dev->dev, "Received VS write file block complete\n"); + pkt_handled = handle_vs_write_file_block_cmd_complete( + dev, &write_block_evt->status); + } else { + dev_err(dev->dev, "Vendor Specific Event 0x%x" + "Not Supported\n", cmd_evt->evt_id); + return false; + } + } else if (HCI_EV_HW_ERROR == evt->evt) { + struct hci_ev_hw_error *hw_error; + + hw_error = (struct hci_ev_hw_error *)data; + /* + * Only do a printout. There might be a receiving stack that can + * handle this event + */ + dev_err(dev->dev, "HW Error event received with error 0x%02X\n", + hw_error->hw_code); + return false; + } else + return false; + + if (pkt_handled) + kfree_skb(skb); + + return pkt_handled; +} + +/** + * transmit_skb_with_flow_ctrl_bt() - Send the BT skb to the controller if it is allowed or queue it. + * @user: Current user. + * @skb: Data packet. + * + * The transmit_skb_with_flow_ctrl_bt() function checks if there are + * tickets available and if so transmits buffer to controller. Otherwise the skb + * and user name is stored in a list for later sending. + * If enabled, copy the transmitted data to the HCI logger as well. + */ +static void transmit_skb_with_flow_ctrl_bt(struct cg2900_user_data *user, + struct sk_buff *skb) +{ + struct cg2900_chip_dev *dev = cg2900_get_prv(user); + struct cg2900_chip_info *info = dev->c_data; + + /* + * Because there are more users of some H4 channels (currently audio + * application for BT command and FM channel) we need to have an + * internal HCI command flow control in CG2900 driver. + * So check here how many tickets we have and store skb in a queue if + * there are no tickets left. The skb will be sent later when we get + * more ticket(s). + */ + spin_lock_bh(&info->tx_bt_lock); + + if (info->tx_nr_pkts_allowed_bt > 0) { + info->tx_nr_pkts_allowed_bt--; + dev_dbg(user->dev, "New tx_nr_pkts_allowed_bt = %d\n", + info->tx_nr_pkts_allowed_bt); + + /* + * If it's command from audio app store the OpCode, + * it'll be used later to decide where to dispatch Command + * Complete event. + */ + if (info->bt_audio == user) { + struct hci_command_hdr *hdr = (struct hci_command_hdr *) + (skb->data + HCI_H4_SIZE); + + info->audio_bt_cmd_op = le16_to_cpu(hdr->opcode); + dev_dbg(user->dev, + "Sending cmd from audio driver, saving " + "OpCode = 0x%X\n", + info->audio_bt_cmd_op); + } + + cg2900_tx_to_chip(user, info->logger, skb); + } else { + dev_dbg(user->dev, "Not allowed to send cmd to controller, " + "storing in TX queue\n"); + + cg2900_skb_data(skb)->user = user; + skb_queue_tail(&info->tx_queue_bt, skb); + } + spin_unlock_bh(&info->tx_bt_lock); +} + +/** + * transmit_skb_with_flow_ctrl_fm() - Send the FM skb to the controller if it is allowed or queue it. + * @user: Current user. + * @skb: Data packet. + * + * The transmit_skb_with_flow_ctrl_fm() function checks if chip is available and + * if so transmits buffer to controller. Otherwise the skb and user name is + * stored in a list for later sending. + * Also it updates the FM radio mode if it's FM GOTOMODE command, this is needed + * to know how to handle some FM DO commands complete events. + * If enabled, copy the transmitted data to the HCI logger as well. + */ +static void transmit_skb_with_flow_ctrl_fm(struct cg2900_user_data *user, + struct sk_buff *skb) +{ + u8 cmd_func = CG2900_FM_CMD_PARAM_NONE; + u16 cmd_id = CG2900_FM_CMD_NONE; + struct cg2900_chip_dev *dev = cg2900_get_prv(user); + struct cg2900_chip_info *info = dev->c_data; + + fm_parse_cmd(&(skb->data[0]), &cmd_func, &cmd_id); + + /* + * If this is an FM IP disable or reset send command and also reset + * the flow control and audio user. + */ + if (cmd_func == CG2900_FM_CMD_PARAM_DISABLE || + cmd_func == CG2900_FM_CMD_PARAM_RESET) { + spin_lock_bh(&info->tx_fm_lock); + fm_reset_flow_ctrl(info); + spin_unlock_bh(&info->tx_fm_lock); + cg2900_tx_to_chip(user, info->logger, skb); + return; + } + + /* + * If this is a FM user and no FM audio user command pending just send + * FM command. It is up to the user of the FM channel to handle its own + * flow control. + */ + spin_lock_bh(&info->tx_fm_lock); + if (info->fm_audio != user && + info->audio_fm_cmd_id == CG2900_FM_CMD_NONE) { + info->hci_fm_cmd_func = cmd_func; + dev_dbg(user->dev, "Sending FM radio command 0x%04X\n", + info->hci_fm_cmd_func); + /* If a GotoMode command update FM mode */ + fm_update_mode(info, &skb->data[0]); + cg2900_tx_to_chip(user, info->logger, skb); + } else if (info->fm_audio == user && + info->hci_fm_cmd_func == CG2900_FM_CMD_PARAM_NONE && + info->audio_fm_cmd_id == CG2900_FM_CMD_NONE) { + /* + * If it's command from fm audio user store the command id. + * It'll be used later to decide where to dispatch + * command complete event. + */ + info->audio_fm_cmd_id = cmd_id; + dev_dbg(user->dev, "Sending FM audio command 0x%04X\n", + info->audio_fm_cmd_id); + cg2900_tx_to_chip(user, info->logger, skb); + } else { + dev_dbg(user->dev, + "Not allowed to send FM cmd to controller, storing in " + "TX queue\n"); + + cg2900_skb_data(skb)->user = user; + skb_queue_tail(&info->tx_queue_fm, skb); + } + spin_unlock_bh(&info->tx_fm_lock); +} + +/** + * is_bt_audio_user() - Checks if this packet is for the BT audio user. + * @info: CG2900 info. + * @h4_channel: H:4 channel for this packet. + * @skb: Packet to check. + * + * Returns: + * true if packet is for BT audio user. + * false otherwise. + */ +static bool is_bt_audio_user(struct cg2900_chip_info *info, int h4_channel, + const struct sk_buff * const skb) +{ + struct hci_event_hdr *hdr; + u8 *payload; + u16 opcode; + + if (h4_channel != CHANNEL_BT_EVT) + return false; + + hdr = (struct hci_event_hdr *)skb->data; + payload = (u8 *)(hdr + 1); /* follows header */ + + if (HCI_EV_CMD_COMPLETE == hdr->evt) + opcode = le16_to_cpu( + ((struct hci_ev_cmd_complete *)payload)->opcode); + else if (HCI_EV_CMD_STATUS == hdr->evt) + opcode = le16_to_cpu( + ((struct hci_ev_cmd_status *)payload)->opcode); + else + return false; + + if (opcode != info->audio_bt_cmd_op) + return false; + + dev_dbg(info->bt_audio->dev, "Audio BT OpCode match = 0x%04X\n", + opcode); + info->audio_bt_cmd_op = CG2900_BT_OPCODE_NONE; + return true; +} + +/** + * is_fm_audio_user() - Checks if this packet is for the FM audio user. + * @info: CG2900 info. + * @h4_channel: H:4 channel for this packet. + * @skb: Packet to check. + * + * Returns: + * true if packet is for BT audio user. + * false otherwise. + */ +static bool is_fm_audio_user(struct cg2900_chip_info *info, int h4_channel, + const struct sk_buff * const skb) +{ + u8 cmd_func; + u16 cmd_id; + u16 irpt_val; + u8 event; + + if (h4_channel != CHANNEL_FM_RADIO) + return false; + + cmd_func = CG2900_FM_CMD_PARAM_NONE; + cmd_id = CG2900_FM_CMD_NONE; + irpt_val = 0; + event = CG2900_FM_EVENT_UNKNOWN; + + fm_parse_event(&skb->data[0], &event, &cmd_func, &cmd_id, + &irpt_val); + /* Check if command complete event FM legacy interface. */ + if ((event == CG2900_FM_EVENT_CMD_COMPLETE) && + (cmd_func == CG2900_FM_CMD_PARAM_WRITECOMMAND) && + (cmd_id == info->audio_fm_cmd_id)) { + dev_dbg(info->fm_audio->dev, + "FM Audio Function Code match = 0x%04X\n", + cmd_id); + return true; + } + + /* Check if Interrupt legacy interface. */ + if ((event == CG2900_FM_EVENT_INTERRUPT) && + (fm_is_do_cmd_irpt(irpt_val)) && + (info->tx_fm_audio_awaiting_irpt)) + return true; + + return false; +} + +/** + * data_from_chip() - Called when data is received from the chip. + * @dev: Chip info. + * @cg2900_dev: CG2900 user for this packet. + * @skb: Packet received. + * + * The data_from_chip() function updates flow control and checks + * if packet is a response for a packet it itself has transmitted. If not it + * finds the correct user and sends the packet* to the user. + */ +static void data_from_chip(struct cg2900_chip_dev *dev, + struct sk_buff *skb) +{ + int h4_channel; + struct list_head *cursor; + struct cg2900_channel_item *tmp; + struct cg2900_chip_info *info = dev->c_data; + struct cg2900_user_data *user = NULL; + + spin_lock_bh(&info->rw_lock); + /* Copy RX Data into logger.*/ + if (info->logger) + cg2900_send_to_hci_logger(info->logger, skb, + LOGGER_DIRECTION_RX); + + /* + * HCI Raw user can only have exclusive access to chip, there won't be + * other users once it's opened. + */ + if (info->hci_raw && info->hci_raw->opened) { + info->hci_raw->read_cb(info->hci_raw, skb); + spin_unlock_bh(&info->rw_lock); + return; + } + + h4_channel = skb->data[0]; + skb_pull(skb, HCI_H4_SIZE); + + /* First check if it is a BT or FM audio event */ + if (is_bt_audio_user(info, h4_channel, skb)) + user = info->bt_audio; + else if (is_fm_audio_user(info, h4_channel, skb)) + user = info->fm_audio; + spin_unlock_bh(&info->rw_lock); + + /* Now check if we should update flow control */ + if (h4_channel == CHANNEL_BT_EVT) + update_flow_ctrl_bt(dev, skb); + else if (h4_channel == CHANNEL_FM_RADIO) + update_flow_ctrl_fm(dev, skb); + + /* Then check if this is a response to data we have sent */ + if (h4_channel == CHANNEL_BT_EVT && handle_rx_data_bt_evt(dev, skb)) + return; + + spin_lock_bh(&info->rw_lock); + + if (user) + goto user_found; + + /* Let's see if it is the last user */ + if (info->last_user && info->last_user->h4_channel == h4_channel) { + user = info->last_user; + goto user_found; + } + + /* + * Search through the list of all open channels to find the user. + * We skip the audio channels since they have already been checked + * earlier in this function. + */ + list_for_each(cursor, &info->open_channels) { + tmp = list_entry(cursor, struct cg2900_channel_item, list); + if (tmp->user->h4_channel == h4_channel && + !tmp->user->is_audio) { + user = tmp->user; + goto user_found; + } + } + +user_found: + if (user != info->bt_audio && user != info->fm_audio) + info->last_user = user; + + spin_unlock_bh(&info->rw_lock); + + if (user) + user->read_cb(user, skb); + else { + dev_err(dev->dev, + "Could not find corresponding user to h4_channel %d\n", + h4_channel); + kfree_skb(skb); + } +} + +/** + * chip_removed() - Called when transport has been removed. + * @dev: Chip device. + * + * Removes registered MFD devices and frees internal resources. + */ +static void chip_removed(struct cg2900_chip_dev *dev) +{ + struct cg2900_chip_info *info = dev->c_data; + + cancel_delayed_work(&info->selftest_work.work); + mfd_remove_devices(dev->dev); + kfree(info->settings_file_name); + kfree(info->patch_file_name); + destroy_workqueue(info->wq); + kfree(info); + dev->c_data = NULL; + dev->c_cb.chip_removed = NULL; + dev->c_cb.data_from_chip = NULL; +} + +/** + * last_bt_user_removed() - Called when last BT user is removed. + * @info: Chip handler info. + * + * Clears out TX queue for BT. + */ +static void last_bt_user_removed(struct cg2900_chip_info *info) +{ + spin_lock_bh(&info->tx_bt_lock); + skb_queue_purge(&info->tx_queue_bt); + + /* + * Reset number of packets allowed and number of outstanding + * BT commands. + */ + info->tx_nr_pkts_allowed_bt = 1; + /* Reset the audio_bt_cmd_op. */ + info->audio_bt_cmd_op = CG2900_BT_OPCODE_NONE; + spin_unlock_bh(&info->tx_bt_lock); +} + +/** + * last_fm_user_removed() - Called when last FM user is removed. + * @info: Chip handler info. + * + * Clears out TX queue for BT. + */ +static void last_fm_user_removed(struct cg2900_chip_info *info) +{ + spin_lock_bh(&info->tx_fm_lock); + fm_reset_flow_ctrl(info); + spin_unlock_bh(&info->tx_fm_lock); +} + +/** + * chip_shutdown() - Reset and power the chip off. + * @user: MFD device. + */ +static void chip_shutdown(struct cg2900_user_data *user) +{ + struct hci_command_hdr cmd; + struct cg2900_chip_dev *dev = cg2900_get_prv(user); + struct cg2900_chip_info *info = dev->c_data; + + dev_dbg(user->dev, "chip_shutdown\n"); + + /* First do a quick power switch of the chip to assure a good state */ + if (dev->t_cb.set_chip_power) + dev->t_cb.set_chip_power(dev, false); + + /* + * Wait 50ms before continuing to be sure that the chip detects + * chip power off. + */ + schedule_timeout_killable( + msecs_to_jiffies(LINE_TOGGLE_DETECT_TIMEOUT)); + + if (dev->t_cb.set_chip_power) + dev->t_cb.set_chip_power(dev, true); + + /* Wait 100ms before continuing to be sure that the chip is ready */ + schedule_timeout_killable(msecs_to_jiffies(CHIP_READY_TIMEOUT)); + + if (user != info->bt_audio && user != info->fm_audio) + info->last_user = user; + info->user_in_charge = user; + + /* + * Transmit HCI reset command to ensure the chip is using + * the correct transport and to put BT part in reset. + */ + dev_dbg(user->dev, "New closing_state: CLOSING_RESET\n"); + info->closing_state = CLOSING_RESET; + cmd.opcode = cpu_to_le16(HCI_OP_RESET); + cmd.plen = 0; /* No parameters for HCI reset */ + cg2900_send_bt_cmd(info->user_in_charge, info->logger, &cmd, + sizeof(cmd)); +} + +/** + * chip_startup_finished() - Called when chip startup has finished. + * @info: Chip handler info. + * @err: Result of chip startup, 0 for no error. + * + * Shuts down the chip upon error, sets state to active, wakes waiting threads, + * and informs transport that startup has finished. + */ +static void chip_startup_finished(struct cg2900_chip_info *info, int err) +{ + dev_dbg(BOOT_DEV, "chip_startup_finished (%d)\n", err); + + if (err) + /* Shutdown the chip */ + cg2900_create_work_item(info->wq, work_chip_shutdown, + info->user_in_charge); + else { + dev_dbg(BOOT_DEV, "New main_state: CG2900_ACTIVE\n"); + info->main_state = CG2900_ACTIVE; + } + + wake_up_all(&main_wait_queue); + + if (err) { + /* + * This will wakeup clock user too + * if it started the initialization process + */ + wake_up_all(&clk_user_wait_queue); + return; + } + + if (!info->chip_dev->t_cb.chip_startup_finished) + dev_dbg(BOOT_DEV, "chip_startup_finished callback not found\n"); + else + info->chip_dev->t_cb.chip_startup_finished(info->chip_dev); +} + +/** + * cg2900_open() - Called when user wants to open an H4 channel. + * @user: MFD device to open. + * + * Checks that H4 channel is not already opened. If chip is not started, starts + * up the chip. Sets channel as opened and adds user to active users. + * + * Returns: + * 0 if success. + * -EINVAL if user is NULL or read_cb is NULL. + * -EBUSY if chip is in transit state (being started or shutdown). + * -EACCES if H4 channel is already opened. + * -ENOMEM if allocation fails. + * -EIO if chip startup fails. + * Error codes generated by t_cb.open. + */ +static int cg2900_open(struct cg2900_user_data *user) +{ + int err; + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + struct list_head *cursor; + struct cg2900_channel_item *tmp; + enum main_state state_to_check = CG2900_ACTIVE; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, "cg2900_open: Calling with NULL pointer\n"); + return -EINVAL; + } + + if (!user->read_cb) { + dev_err(user->dev, "cg2900_open: read_cb missing\n"); + return -EINVAL; + } + + dev_dbg(user->dev, "cg2900_open\n"); + + dev = cg2900_get_prv(user); + info = dev->c_data; + + /* HCI Raw channel shall have exclusive access to chip. */ + if (info->hci_raw && user->h4_channel != CHANNEL_HCI_RAW && + user->h4_channel != CHANNEL_HCI_LOGGER) { + dev_err(user->dev, "cg2900_open: Cannot open %s " + "channel while HCI Raw channel is opened\n", + user->channel_data.char_dev_name); + return -EACCES; + } + + mutex_lock(&main_info->man_mutex); + + /* + * Add a minor wait in order to avoid CPU blocking, looping openings. + * Note there will of course be no wait if we are already in the right + * state. + */ + err = wait_event_timeout(main_wait_queue, + (CG2900_IDLE == info->main_state || + CG2900_ACTIVE == info->main_state), + msecs_to_jiffies(LINE_TOGGLE_DETECT_TIMEOUT)); + if (err <= 0) { + if (CG2900_INIT == info->main_state) + dev_err(user->dev, "Transport not opened\n"); + else + dev_err(user->dev, "cg2900_open currently busy (0x%X). " + "Try again\n", info->main_state); + err = -EBUSY; + goto err_free_mutex; + } + + err = 0; + + list_for_each(cursor, &info->open_channels) { + tmp = list_entry(cursor, struct cg2900_channel_item, list); + if (tmp->user->h4_channel == user->h4_channel && + tmp->user->is_audio == user->is_audio) { + dev_err(user->dev, "Channel %d is already opened\n", + user->h4_channel); + err = -EACCES; + goto err_free_mutex; + } + } + + tmp = kzalloc(sizeof(*tmp), GFP_KERNEL); + if (!tmp) { + dev_err(user->dev, "Could not allocate tmp\n"); + err = -ENOMEM; + goto err_free_mutex; + } + tmp->user = user; + + if (CG2900_ACTIVE != info->main_state && + !user->chip_independent) { + /* Open transport and start-up the chip */ + if (dev->t_cb.set_chip_power) + dev->t_cb.set_chip_power(dev, true); + + /* Wait to be sure that the chip is ready */ + schedule_timeout_killable( + msecs_to_jiffies(CHIP_READY_TIMEOUT)); + + if (dev->t_cb.open) { + err = dev->t_cb.open(dev); + if (err) { + if (dev->t_cb.set_chip_power) + dev->t_cb.set_chip_power(dev, false); + goto err_free_list_item; + } + } + + /* Start the boot sequence */ + info->user_in_charge = user; + if (user != info->bt_audio && user != info->fm_audio) + info->last_user = user; + dev_dbg(user->dev, "New boot_state: BOOT_GET_FILES_TO_LOAD\n"); + info->boot_state = BOOT_GET_FILES_TO_LOAD; + dev_dbg(user->dev, "New main_state: CG2900_BOOTING\n"); + info->main_state = CG2900_BOOTING; + cg2900_create_work_item(info->wq, work_load_patch_and_settings, + dev); + + dev_dbg(user->dev, "Wait 15sec for chip to start\n"); + if (user->is_clk_user) { + dev_dbg(user->dev, "Clock user is Waiting here\n"); + wait_event_timeout(clk_user_wait_queue, + CG2900_ACTIVE_BEFORE_SELFTEST + == info->main_state, + msecs_to_jiffies(CHIP_STARTUP_TIMEOUT)); + + state_to_check = CG2900_ACTIVE_BEFORE_SELFTEST; + } else { + dev_dbg(user->dev, "Not a Clock user\n"); + wait_event_timeout(main_wait_queue, + (CG2900_ACTIVE == info->main_state || + CG2900_IDLE == info->main_state), + msecs_to_jiffies(CHIP_STARTUP_TIMEOUT)); + } + + if (state_to_check != info->main_state) { + dev_err(user->dev, "CG2900 init failed\n"); + + if (dev->t_cb.close) + dev->t_cb.close(dev); + + dev_dbg(user->dev, "main_state: CG2900_IDLE\n"); + info->main_state = CG2900_IDLE; + err = -EIO; + goto err_free_list_item; + } + + if (CG2905_PG1_1_REV == dev->chip.hci_revision || + CG2910_PG1_REV == dev->chip.hci_revision) { + /* + * Switch to higher baud rate + * Because of bug in CG2905/CG2910 PG1 H/W, + * We have to download the ptc/ssf files + * at lower baud and then switch to Higher Baud + */ + if (info->chip_dev->t_cb.set_baud_rate) + info->chip_dev->t_cb.set_baud_rate( + info->chip_dev, false); + } + + } + + list_add_tail(&tmp->list, &info->open_channels); + + user->opened = true; + + dev_dbg(user->dev, "H:4 channel opened\n"); + + mutex_unlock(&main_info->man_mutex); + return 0; +err_free_list_item: + kfree(tmp); +err_free_mutex: + mutex_unlock(&main_info->man_mutex); + return err; +} + +/** + * cg2900_hci_log_open() - Called when user wants to open HCI logger channel. + * @user: MFD device to open. + * + * Registers user as hci_logger and calls @cg2900_open to open the channel. + * + * Returns: + * 0 if success. + * -EINVAL if user is NULL. + * -EEXIST if H4 channel is already opened. + * Error codes generated by cg2900_open. + */ +static int cg2900_hci_log_open(struct cg2900_user_data *user) +{ + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + int err; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, + "cg2900_hci_log_open: Calling with NULL pointer\n"); + return -EINVAL; + } + + dev_dbg(user->dev, "cg2900_hci_log_open\n"); + + dev = cg2900_get_prv(user); + info = dev->c_data; + + if (info->logger) { + dev_err(user->dev, "HCI Logger already stored\n"); + return -EEXIST; + } + + info->logger = user; + err = cg2900_open(user); + if (err) + info->logger = NULL; + return err; +} + +/** + * cg2900_hci_raw_open() - Called when user wants to open HCI Raw channel. + * @user: MFD device to open. + * + * Registers user as hci_raw and calls @cg2900_open to open the channel. + * + * Returns: + * 0 if success. + * -EINVAL if user is NULL. + * -EEXIST if H4 channel is already opened. + * -EACCES if H4 channel iother than HCI RAW is already opened. + * Error codes generated by cg2900_open. + */ +static int cg2900_hci_raw_open(struct cg2900_user_data *user) +{ + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + struct list_head *cursor; + struct cg2900_channel_item *tmp; + int err; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, + "cg2900_hci_raw_open: Calling with NULL pointer\n"); + return -EINVAL; + } + + dev_dbg(user->dev, "cg2900_hci_raw_open\n"); + + dev = cg2900_get_prv(user); + info = dev->c_data; + + if (info->hci_raw) { + dev_err(user->dev, "HCI Raw Channel already stored\n"); + return -EEXIST; + } + + if (!list_empty(&info->open_channels)) { + /* + * Go through each open channel to check if it is logger + * channel or some other channel. + */ + list_for_each(cursor, &info->open_channels) { + tmp = list_entry(cursor, + struct cg2900_channel_item, list); + if (tmp->user->h4_channel != CHANNEL_HCI_LOGGER) { + dev_err(user->dev, "Other channels other than " + "Logger is already opened. Cannot open " + "HCI Raw Channel\n"); + return -EACCES; + } + } + } + + info->hci_raw = user; + err = cg2900_open(user); + if (err) + info->hci_raw = NULL; + return err; +} + +/** + * cg2900_bt_audio_open() - Called when user wants to open BT audio channel. + * @user: MFD device to open. + * + * Registers user as bt_audio and calls @cg2900_open to open the channel. + * + * Returns: + * 0 if success. + * -EINVAL if user is NULL. + * -EEXIST if H4 channel is already opened. + * Error codes generated by cg2900_open. + */ +static int cg2900_bt_audio_open(struct cg2900_user_data *user) +{ + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + int err; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, + "cg2900_bt_audio_open: Calling with NULL pointer\n"); + return -EINVAL; + } + + dev_dbg(user->dev, "cg2900_bt_audio_open\n"); + + dev = cg2900_get_prv(user); + info = dev->c_data; + + if (info->bt_audio) { + dev_err(user->dev, "BT Audio already stored\n"); + return -EEXIST; + } + + info->bt_audio = user; + err = cg2900_open(user); + if (err) + info->bt_audio = NULL; + return err; +} + +/** + * cg2900_fm_audio_open() - Called when user wants to open FM audio channel. + * @user: MFD device to open. + * + * Registers user as fm_audio and calls @cg2900_open to open the channel. + * + * Returns: + * 0 if success. + * -EINVAL if user is NULL. + * -EEXIST if H4 channel is already opened. + * Error codes generated by cg2900_open. + */ +static int cg2900_fm_audio_open(struct cg2900_user_data *user) +{ + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + int err; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, + "cg2900_fm_audio_open: Calling with NULL pointer\n"); + return -EINVAL; + } + + dev_dbg(user->dev, "cg2900_fm_audio_open\n"); + + dev = cg2900_get_prv(user); + info = dev->c_data; + + if (info->fm_audio) { + dev_err(user->dev, "FM Audio already stored\n"); + return -EEXIST; + } + + info->fm_audio = user; + err = cg2900_open(user); + if (err) + info->fm_audio = NULL; + return err; +} + +/** + * cg2900_close() - Called when user wants to close an H4 channel. + * @user: MFD device to close. + * + * Clears up internal resources, sets channel as closed, and shuts down chip if + * this was the last user. + */ +static void cg2900_close(struct cg2900_user_data *user) +{ + bool keep_powered = false; + struct list_head *cursor, *next; + struct cg2900_channel_item *tmp; + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, "cg2900_close: Calling with NULL pointer\n"); + return; + } + + dev_dbg(user->dev, "cg2900_close\n"); + + dev = cg2900_get_prv(user); + info = dev->c_data; + + mutex_lock(&main_info->man_mutex); + + /* + * Go through each open channel. Remove our channel and check if there + * is any other channel that want to keep the chip running + */ + list_for_each_safe(cursor, next, &info->open_channels) { + tmp = list_entry(cursor, struct cg2900_channel_item, list); + if (tmp->user == user) { + list_del(cursor); + kfree(tmp); + } else if (!tmp->user->chip_independent) + keep_powered = true; + } + + if (user->h4_channel == CHANNEL_BT_CMD && !bt_is_open(info)) + last_bt_user_removed(info); + else if (user->h4_channel == CHANNEL_FM_RADIO && !fm_is_open(info)) + last_fm_user_removed(info); + + if (keep_powered) + /* This was not the last user, we're done. */ + goto finished; + + if (CG2900_IDLE == info->main_state) + /* Chip has already been shut down. */ + goto finished; + + dev_dbg(user->dev, "New main_state: CG2900_CLOSING\n"); + info->main_state = CG2900_CLOSING; + chip_shutdown(user); + + dev_dbg(user->dev, "Wait up to 15 seconds for chip to shut-down\n"); + wait_event_timeout(main_wait_queue, + (CG2900_IDLE == info->main_state), + msecs_to_jiffies(CHIP_SHUTDOWN_TIMEOUT)); + + /* Force shutdown if we timed out */ + if (CG2900_IDLE != info->main_state) { + dev_err(user->dev, + "ST-Ericsson CG2900 Core Driver was shut-down with " + "problems\n"); + + if (dev->t_cb.close) + dev->t_cb.close(dev); + + dev_dbg(user->dev, "New main_state: CG2900_IDLE\n"); + info->main_state = CG2900_IDLE; + } + +finished: + mutex_unlock(&main_info->man_mutex); + user->opened = false; + dev_dbg(user->dev, "H:4 channel closed\n"); +} + +/** + * cg2900_hci_log_close() - Called when user wants to close HCI logger channel. + * @user: MFD device to close. + * + * Clears hci_logger user and calls @cg2900_close to close the channel. + */ +static void cg2900_hci_log_close(struct cg2900_user_data *user) +{ + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, + "cg2900_hci_log_close: Calling with NULL pointer\n"); + return; + } + + dev_dbg(user->dev, "cg2900_hci_log_close\n"); + + dev = cg2900_get_prv(user); + info = dev->c_data; + + if (user != info->logger) { + dev_err(user->dev, "cg2900_hci_log_close: Trying to remove " + "another user\n"); + return; + } + + info->logger = NULL; + cg2900_close(user); +} + +/** + * cg2900_hci_raw_close() - Called when user wants to close HCI Raw channel. + * @user: MFD device to close. + * + * Clears hci_raw user and calls @cg2900_close to close the channel. + */ +static void cg2900_hci_raw_close(struct cg2900_user_data *user) +{ + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, + "cg2900_hci_raw_close: Calling with NULL pointer\n"); + return; + } + + dev_dbg(user->dev, "cg2900_hci_raw_close\n"); + + dev = cg2900_get_prv(user); + info = dev->c_data; + + if (user != info->hci_raw) { + dev_err(user->dev, "cg2900_hci_raw_close: Trying to remove " + "another user\n"); + return; + } + + info->hci_raw = NULL; + cg2900_close(user); +} + +/** + * cg2900_bt_audio_close() - Called when user wants to close BT audio channel. + * @user: MFD device to close. + * + * Clears bt_audio user and calls @cg2900_close to close the channel. + */ +static void cg2900_bt_audio_close(struct cg2900_user_data *user) +{ + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, + "cg2900_bt_audio_close: Calling with NULL pointer\n"); + return; + } + + dev_dbg(user->dev, "cg2900_bt_audio_close\n"); + + dev = cg2900_get_prv(user); + info = dev->c_data; + + if (user != info->bt_audio) { + dev_err(user->dev, "cg2900_bt_audio_close: Trying to remove " + "another user\n"); + return; + } + + info->bt_audio = NULL; + cg2900_close(user); +} + +/** + * cg2900_fm_audio_close() - Called when user wants to close FM audio channel. + * @user: MFD device to close. + * + * Clears fm_audio user and calls @cg2900_close to close the channel. + */ +static void cg2900_fm_audio_close(struct cg2900_user_data *user) +{ + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, + "cg2900_fm_audio_close: Calling with NULL pointer\n"); + return; + } + + dev_dbg(user->dev, "cg2900_fm_audio_close\n"); + + dev = cg2900_get_prv(user); + info = dev->c_data; + + if (user != info->fm_audio) { + dev_err(user->dev, "cg2900_fm_audio_close: Trying to remove " + "another user\n"); + return; + } + + info->fm_audio = NULL; + cg2900_close(user); +} + +/** + * cg2900_reset() - Called when user wants to reset the chip. + * @user: MFD device to reset. + * + * Closes down the chip and calls reset_cb for all open users. + * + * Returns: + * 0 if success. + * -EINVAL if user is NULL. + */ +static int cg2900_reset(struct cg2900_user_data *user) +{ + struct list_head *cursor, *next; + struct cg2900_channel_item *tmp; + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + + if (!user) { + dev_err(MAIN_DEV, "cg2900_reset: Calling with NULL pointer\n"); + return -EINVAL; + } + + dev = cg2900_get_prv(user); + info = dev->c_data; + + dev_info(user->dev, "cg2900_reset\n"); + + BUG_ON(!main_info); + + mutex_lock(&main_info->man_mutex); + + dev_dbg(user->dev, "New main_state: CG2900_RESETING\n"); + info->main_state = CG2900_RESETING; + + chip_shutdown(user); + + /* + * Inform all opened channels about the reset and free the user devices + */ + list_for_each_safe(cursor, next, &info->open_channels) { + tmp = list_entry(cursor, struct cg2900_channel_item, list); + list_del(cursor); + tmp->user->opened = false; + tmp->user->reset_cb(tmp->user); + kfree(tmp); + } + + /* Reset finished. We are now idle until first channel is opened */ + dev_dbg(user->dev, "New main_state: CG2900_IDLE\n"); + info->main_state = CG2900_IDLE; + + mutex_unlock(&main_info->man_mutex); + + /* + * Send wake-up since this might have been called from a failed boot. + * No harm done if it is a CG2900 chip user who called. + */ + wake_up_all(&main_wait_queue); + + return 0; +} + +/** + * cg2900_alloc_skb() - Allocates socket buffer. + * @size: Sk_buffer size in bytes. + * @priority: GFP priorit for allocation. + * + * Allocates a sk_buffer and reserves space for H4 header. + * + * Returns: + * sk_buffer if success. + * NULL if allocation fails. + */ +static struct sk_buff *cg2900_alloc_skb(unsigned int size, gfp_t priority) +{ + struct sk_buff *skb; + + dev_dbg(MAIN_DEV, "cg2900_alloc_skb size %d bytes\n", size); + + /* Allocate the SKB and reserve space for the header */ + skb = alloc_skb(size + CG2900_SKB_RESERVE, priority); + if (skb) + skb_reserve(skb, CG2900_SKB_RESERVE); + + return skb; +} + +/** + * cg2900_write() - Called when user wants to write to the chip. + * @user: MFD device representing H4 channel to write to. + * @skb: Sk_buffer to transmit. + * + * Transmits the sk_buffer to the chip. If it is a BT cmd or FM audio packet it + * is checked that it is allowed to transmit the chip. + * Note that if error is returned it is up to the user to free the skb. + * + * Returns: + * 0 if success. + * -EINVAL if user or skb is NULL. + * -EACCES if channel is closed. + */ +static int cg2900_write(struct cg2900_user_data *user, struct sk_buff *skb) +{ + u8 *h4_header; + struct cg2900_chip_dev *dev; + struct cg2900_chip_info *info; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, "cg2900_write: Calling with NULL pointer\n"); + return -EINVAL; + } + + if (!skb) { + dev_err(user->dev, "cg2900_write with no sk_buffer\n"); + return -EINVAL; + } + + dev = cg2900_get_prv(user); + info = dev->c_data; + + dev_dbg(user->dev, "cg2900_write length %d bytes\n", skb->len); + + if (!user->opened) { + dev_err(user->dev, + "Trying to transmit data on a closed channel\n"); + return -EACCES; + } + + if (user->h4_channel == CHANNEL_HCI_RAW) { + /* + * Since the data transmitted on HCI Raw channel + * can be byte by byte, flow control cannot be used. + * This should be handled by user space application + * of the HCI Raw channel, so just transmit the + * received data to chip. + */ + cg2900_tx_to_chip(user, info->logger, skb); + return 0; + } + + /* + * Move the data pointer to the H:4 header position and + * store the H4 header. + */ + h4_header = skb_push(skb, CG2900_SKB_RESERVE); + *h4_header = (u8)user->h4_channel; + + if (user->h4_channel == CHANNEL_BT_CMD) + transmit_skb_with_flow_ctrl_bt(user, skb); + else if (user->h4_channel == CHANNEL_FM_RADIO) + transmit_skb_with_flow_ctrl_fm(user, skb); + else + cg2900_tx_to_chip(user, info->logger, skb); + + return 0; +} + +/** + * cg2900_no_write() - Used for channels where it is not allowed to write. + * @user: MFD device representing H4 channel to write to. + * @skb: Sk_buffer to transmit. + * + * Returns: + * -EPERM. + */ +static int cg2900_no_write(struct cg2900_user_data *user, + __attribute__((unused)) struct sk_buff *skb) +{ + dev_err(user->dev, "Not allowed to send on this channel\n"); + return -EPERM; +} + +/** + * cg2900_get_local_revision() - Called to retrieve revision data for the chip. + * @user: MFD device to check. + * @rev_data: Revision data to fill in. + * + * Returns: + * true if success. + * false upon failure. + */ +static bool cg2900_get_local_revision(struct cg2900_user_data *user, + struct cg2900_rev_data *rev_data) +{ + struct cg2900_chip_dev *dev; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, "cg2900_get_local_revision: Calling with " + "NULL pointer\n"); + return false; + } + + if (!rev_data) { + dev_err(user->dev, "Calling with rev_data NULL\n"); + return false; + } + + dev = cg2900_get_prv(user); + + rev_data->revision = dev->chip.hci_revision; + rev_data->sub_version = dev->chip.hci_sub_version; + + return true; +} + +static struct cg2900_user_data btcmd_data = { + .h4_channel = CHANNEL_BT_CMD, +}; +static struct cg2900_user_data btacl_data = { + .h4_channel = CHANNEL_BT_ACL, +}; +static struct cg2900_user_data btevt_data = { + .h4_channel = CHANNEL_BT_EVT, +}; +static struct cg2900_user_data nfc_data = { + .h4_channel = CHANNEL_NFC, +}; +static struct cg2900_user_data fm_data = { + .h4_channel = CHANNEL_FM_RADIO, +}; +static struct cg2900_user_data gnss_data = { + .h4_channel = CHANNEL_GNSS, +}; +static struct cg2900_user_data debug_data = { + .h4_channel = CHANNEL_DEBUG, +}; +static struct cg2900_user_data ste_tools_data = { + .h4_channel = CHANNEL_STE_TOOLS, +}; +static struct cg2900_user_data hci_logger_data = { + .h4_channel = CHANNEL_HCI_LOGGER, + .chip_independent = true, + .write = cg2900_no_write, + .open = cg2900_hci_log_open, + .close = cg2900_hci_log_close, +}; +static struct cg2900_user_data core_data = { + .h4_channel = CHANNEL_CORE, + .write = cg2900_no_write, +}; +static struct cg2900_user_data audio_bt_data = { + .h4_channel = CHANNEL_BT_CMD, + .is_audio = true, + .open = cg2900_bt_audio_open, + .close = cg2900_bt_audio_close, +}; +static struct cg2900_user_data audio_fm_data = { + .h4_channel = CHANNEL_FM_RADIO, + .is_audio = true, + .open = cg2900_fm_audio_open, + .close = cg2900_fm_audio_close, +}; +static struct cg2900_user_data hci_raw_data = { + .h4_channel = CHANNEL_HCI_RAW, + .open = cg2900_hci_raw_open, + .close = cg2900_hci_raw_close, +}; + +static struct mfd_cell cg2900_devs[] = { + { + .name = "cg2900-btcmd", + .platform_data = &btcmd_data, + .pdata_size = sizeof(btcmd_data), + }, + { + .name = "cg2900-btacl", + .platform_data = &btacl_data, + .pdata_size = sizeof(btacl_data), + }, + { + .name = "cg2900-btevt", + .platform_data = &btevt_data, + .pdata_size = sizeof(btevt_data), + }, + { + .name = "cg2900-fm", + .platform_data = &fm_data, + .pdata_size = sizeof(fm_data), + }, + { + .name = "cg2900-gnss", + .platform_data = &gnss_data, + .pdata_size = sizeof(gnss_data), + }, + { + .name = "cg2900-debug", + .platform_data = &debug_data, + .pdata_size = sizeof(debug_data), + }, + { + .name = "cg2900-stetools", + .platform_data = &ste_tools_data, + .pdata_size = sizeof(ste_tools_data), + }, + { + .name = "cg2900-hcilogger", + .platform_data = &hci_logger_data, + .pdata_size = sizeof(hci_logger_data), + }, + { + .name = "cg2900-core", + .platform_data = &core_data, + .pdata_size = sizeof(core_data), + }, + { + .name = "cg2900-audiobt", + .platform_data = &audio_bt_data, + .pdata_size = sizeof(audio_bt_data), + }, + { + .name = "cg2900-audiofm", + .platform_data = &audio_fm_data, + .pdata_size = sizeof(audio_fm_data), + }, + { + .name = "cg2900-hciraw", + .platform_data = &hci_raw_data, + .pdata_size = sizeof(hci_raw_data), + }, +}; + +static struct mfd_cell cg2910_extra_devs[] = { + { + .name = "cg2900-nfc", + .platform_data = &nfc_data, + .pdata_size = sizeof(nfc_data) + } +}; + +static struct cg2900_user_data char_btcmd_data = { + .channel_data = { + .char_dev_name = CG2900_BT_CMD, + }, + .h4_channel = CHANNEL_BT_CMD, +}; +static struct cg2900_user_data char_btacl_data = { + .channel_data = { + .char_dev_name = CG2900_BT_ACL, + }, + .h4_channel = CHANNEL_BT_ACL, +}; +static struct cg2900_user_data char_btevt_data = { + .channel_data = { + .char_dev_name = CG2900_BT_EVT, + }, + .h4_channel = CHANNEL_BT_EVT, +}; +static struct cg2900_user_data char_nfc_data = { + .channel_data = { + .char_dev_name = CG2900_NFC, + }, + .h4_channel = CHANNEL_NFC, +}; +static struct cg2900_user_data char_fm_data = { + .channel_data = { + .char_dev_name = CG2900_FM_RADIO, + }, + .h4_channel = CHANNEL_FM_RADIO, +}; +static struct cg2900_user_data char_gnss_data = { + .channel_data = { + .char_dev_name = CG2900_GNSS, + }, + .h4_channel = CHANNEL_GNSS, +}; +static struct cg2900_user_data char_debug_data = { + .channel_data = { + .char_dev_name = CG2900_DEBUG, + }, + .h4_channel = CHANNEL_DEBUG, +}; +static struct cg2900_user_data char_ste_tools_data = { + .channel_data = { + .char_dev_name = CG2900_STE_TOOLS, + }, + .h4_channel = CHANNEL_STE_TOOLS, +}; +static struct cg2900_user_data char_hci_logger_data = { + .channel_data = { + .char_dev_name = CG2900_HCI_LOGGER, + }, + .h4_channel = CHANNEL_HCI_LOGGER, + .chip_independent = true, + .write = cg2900_no_write, + .open = cg2900_hci_log_open, + .close = cg2900_hci_log_close, +}; +static struct cg2900_user_data char_core_data = { + .channel_data = { + .char_dev_name = CG2900_CORE, + }, + .h4_channel = CHANNEL_CORE, + .write = cg2900_no_write, +}; +static struct cg2900_user_data char_audio_bt_data = { + .channel_data = { + .char_dev_name = CG2900_BT_AUDIO, + }, + .h4_channel = CHANNEL_BT_CMD, + .is_audio = true, +}; +static struct cg2900_user_data char_audio_fm_data = { + .channel_data = { + .char_dev_name = CG2900_FM_AUDIO, + }, + .h4_channel = CHANNEL_FM_RADIO, + .is_audio = true, +}; +static struct cg2900_user_data char_hci_raw_data = { + .channel_data = { + .char_dev_name = CG2900_HCI_RAW, + }, + .h4_channel = CHANNEL_HCI_RAW, + .open = cg2900_hci_raw_open, + .close = cg2900_hci_raw_close, +}; + + +static struct mfd_cell cg2900_char_devs[] = { + { + .name = "cg2900-chardev", + .id = 0, + .platform_data = &char_btcmd_data, + .pdata_size = sizeof(char_btcmd_data), + }, + { + .name = "cg2900-chardev", + .id = 1, + .platform_data = &char_btacl_data, + .pdata_size = sizeof(char_btacl_data), + }, + { + .name = "cg2900-chardev", + .id = 2, + .platform_data = &char_btevt_data, + .pdata_size = sizeof(char_btevt_data), + }, + { + .name = "cg2900-chardev", + .id = 3, + .platform_data = &char_fm_data, + .pdata_size = sizeof(char_fm_data), + }, + { + .name = "cg2900-chardev", + .id = 4, + .platform_data = &char_gnss_data, + .pdata_size = sizeof(char_gnss_data), + }, + { + .name = "cg2900-chardev", + .id = 5, + .platform_data = &char_debug_data, + .pdata_size = sizeof(char_debug_data), + }, + { + .name = "cg2900-chardev", + .id = 6, + .platform_data = &char_ste_tools_data, + .pdata_size = sizeof(char_ste_tools_data), + }, + { + .name = "cg2900-chardev", + .id = 7, + .platform_data = &char_hci_logger_data, + .pdata_size = sizeof(char_hci_logger_data), + }, + { + .name = "cg2900-chardev", + .id = 8, + .platform_data = &char_core_data, + .pdata_size = sizeof(char_core_data), + }, + { + .name = "cg2900-chardev", + .id = 9, + .platform_data = &char_audio_bt_data, + .pdata_size = sizeof(char_audio_bt_data), + }, + { + .name = "cg2900-chardev", + .id = 10, + .platform_data = &char_audio_fm_data, + .pdata_size = sizeof(char_audio_fm_data), + }, + { + .name = "cg2900-chardev", + .id = 11, + .platform_data = &char_hci_raw_data, + .pdata_size = sizeof(char_hci_raw_data), + }, +}; + +static struct mfd_cell cg2910_extra_char_devs[] = { + { + .name = "cg2900-chardev", + .id = 12, + .platform_data = &char_nfc_data, + .pdata_size = sizeof(char_nfc_data) + } +}; + +/** + * set_plat_data() - Initializes data for an MFD cell. + * @cell: MFD cell. + * @dev: Current chip. + * + * Sets each callback to default function unless already set. + */ +static void set_plat_data(struct mfd_cell *cell, struct cg2900_chip_dev *dev) +{ + struct cg2900_user_data *pf_data = cell->platform_data; + + if (!pf_data->open) + pf_data->open = cg2900_open; + if (!pf_data->close) + pf_data->close = cg2900_close; + if (!pf_data->reset) + pf_data->reset = cg2900_reset; + if (!pf_data->alloc_skb) + pf_data->alloc_skb = cg2900_alloc_skb; + if (!pf_data->write) + pf_data->write = cg2900_write; + if (!pf_data->get_local_revision) + pf_data->get_local_revision = cg2900_get_local_revision; + + cg2900_set_prv(pf_data, dev); +} + +/** + * check_chip_support() - Checks if connected chip is handled by this driver. + * @dev: Chip info structure. + * + * First check if chip is supported by this driver. If that is the case fill in + * the callbacks in @dev and initiate internal variables. Finally create MFD + * devices for all supported H4 channels. When finished power off the chip. + * + * Returns: + * true if chip is handled by this driver. + * false otherwise. + */ +static bool check_chip_support(struct cg2900_chip_dev *dev) +{ + struct cg2900_platform_data *pf_data; + struct cg2900_chip_info *info; + int i; + + dev_dbg(dev->dev, "check_chip_support\n"); + + /* + * Check if this is a CG29XX revision. + * We do not care about the sub-version at the moment. Change this if + * necessary. + */ + if (dev->chip.manufacturer != CG2900_SUPP_MANUFACTURER + || !(check_chip_revision_support(dev->chip.hci_revision))) { + dev_err(dev->dev, "Unsupported Chip revision:0x%x\n", + dev->chip.hci_revision); + return false; + } + + /* Store needed data */ + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + dev_err(dev->dev, "Couldn't allocate info struct\n"); + return false; + } + + /* Initialize all variables */ + skb_queue_head_init(&info->tx_queue_bt); + skb_queue_head_init(&info->tx_queue_fm); + + INIT_LIST_HEAD(&info->open_channels); + + spin_lock_init(&info->tx_bt_lock); + spin_lock_init(&info->tx_fm_lock); + spin_lock_init(&info->rw_lock); + + info->tx_nr_pkts_allowed_bt = 1; + info->audio_bt_cmd_op = CG2900_BT_OPCODE_NONE; + info->audio_fm_cmd_id = CG2900_FM_CMD_NONE; + info->hci_fm_cmd_func = CG2900_FM_CMD_PARAM_NONE; + info->fm_radio_mode = FM_RADIO_MODE_IDLE; + info->chip_dev = dev; + info->dev = dev->dev; + + info->wq = create_singlethread_workqueue(WQ_NAME); + if (!info->wq) { + dev_err(dev->dev, "Could not create workqueue\n"); + goto err_handling_free_info; + } + + info->patch_file_name = kzalloc(NAME_MAX + 1, GFP_ATOMIC); + if (!info->patch_file_name) { + dev_err(dev->dev, + "Couldn't allocate name buffer for patch file\n"); + goto err_handling_destroy_wq; + } + + info->settings_file_name = kzalloc(NAME_MAX + 1, GFP_ATOMIC); + if (!info->settings_file_name) { + dev_err(dev->dev, + "Couldn't allocate name buffers settings file\n"); + goto err_handling_free_patch_name; + } + + info->selftest_work.data = info; + INIT_DELAYED_WORK(&info->selftest_work.work, + work_send_read_selftest_cmd); + + dev->c_data = info; + /* Set the callbacks */ + dev->c_cb.data_from_chip = data_from_chip; + dev->c_cb.chip_removed = chip_removed; + + mutex_lock(&main_info->man_mutex); + + pf_data = dev_get_platdata(dev->dev); + btcmd_data.channel_data.bt_bus = pf_data->bus; + btacl_data.channel_data.bt_bus = pf_data->bus; + btevt_data.channel_data.bt_bus = pf_data->bus; + + /* Set the Platform data based on supported chip */ + for (i = 0; i < ARRAY_SIZE(cg2900_devs); i++) + set_plat_data(&cg2900_devs[i], dev); + for (i = 0; i < ARRAY_SIZE(cg2900_char_devs); i++) + set_plat_data(&cg2900_char_devs[i], dev); + info->mfd_size = ARRAY_SIZE(cg2900_devs); + info->mfd_char_size = ARRAY_SIZE(cg2900_char_devs); + + if (dev->chip.hci_revision == CG2910_PG1_REV || + dev->chip.hci_revision == CG2910_PG2_REV) { + for (i = 0; i < ARRAY_SIZE(cg2910_extra_devs); i++) + set_plat_data(&cg2910_extra_devs[i], dev); + for (i = 0; i < ARRAY_SIZE(cg2910_extra_char_devs); i++) + set_plat_data(&cg2910_extra_char_devs[i], dev); + info->mfd_extra_size = ARRAY_SIZE(cg2910_extra_devs); + info->mfd_extra_char_size = ARRAY_SIZE(cg2910_extra_char_devs); + } + + + info->startup = true; + + /* + * The devices will be registered when chip has been powered down, i.e. + * when the system startup is ready. + */ + + mutex_unlock(&main_info->man_mutex); + + dev_info(dev->dev, "Chip supported by the CG2900 driver\n"); + + /* Finish by turning off the chip */ + cg2900_create_work_item(info->wq, work_power_off_chip, dev); + + return true; + +err_handling_free_patch_name: + kfree(info->patch_file_name); +err_handling_destroy_wq: + destroy_workqueue(info->wq); +err_handling_free_info: + kfree(info); + return false; +} + +static struct cg2900_id_callbacks chip_support_callbacks = { + .check_chip_support = check_chip_support, +}; + +/** + * cg2900_chip_probe() - Initialize CG2900 chip handler resources. + * @pdev: Platform device. + * + * This function initializes the CG2900 driver, then registers to + * the CG2900 Core. + * + * Returns: + * 0 if success. + * -ENOMEM for failed alloc or structure creation. + * Error codes generated by cg2900_register_chip_driver. + */ +static int __devinit cg2900_chip_probe(struct platform_device *pdev) +{ + int err; + + dev_dbg(&pdev->dev, "cg2900_chip_probe\n"); + + main_info = kzalloc(sizeof(*main_info), GFP_ATOMIC); + if (!main_info) { + dev_err(&pdev->dev, "Couldn't allocate main_info\n"); + return -ENOMEM; + } + + main_info->dev = &pdev->dev; + mutex_init(&main_info->man_mutex); + + err = cg2900_register_chip_driver(&chip_support_callbacks); + if (err) { + dev_err(&pdev->dev, + "Couldn't register chip driver (%d)\n", err); + goto error_handling; + } + + dev_info(&pdev->dev, "CG2900 chip driver started\n"); + + return 0; + +error_handling: + mutex_destroy(&main_info->man_mutex); + kfree(main_info); + main_info = NULL; + return err; +} + +/** + * cg2900_chip_remove() - Release CG2900 chip handler resources. + * @pdev: Platform device. + * + * Returns: + * 0 if success (always success). + */ +static int __devexit cg2900_chip_remove(struct platform_device *pdev) +{ + dev_info(&pdev->dev, "CG2900 chip driver removed\n"); + + cg2900_deregister_chip_driver(&chip_support_callbacks); + + if (!main_info) + return 0; + mutex_destroy(&main_info->man_mutex); + kfree(main_info); + main_info = NULL; + return 0; +} + +static struct platform_driver cg2900_chip_driver = { + .driver = { + .name = "cg2900-chip", + .owner = THIS_MODULE, + }, + .probe = cg2900_chip_probe, + .remove = __devexit_p(cg2900_chip_remove), +}; + +/** + * cg2900_chip_init() - Initialize module. + * + * Registers platform driver. + */ +static int __init cg2900_chip_init(void) +{ + pr_debug("cg2900_chip_init"); + return platform_driver_register(&cg2900_chip_driver); +} + +/** + * cg2900_chip_exit() - Remove module. + * + * Unregisters platform driver. + */ +static void __exit cg2900_chip_exit(void) +{ + pr_debug("cg2900_chip_exit"); + platform_driver_unregister(&cg2900_chip_driver); +} + +module_init(cg2900_chip_init); +module_exit(cg2900_chip_exit); + +MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Linux CG2900 Connectivity Device Driver"); diff --git a/drivers/staging/cg2900/mfd/cg2900_chip.h b/drivers/staging/cg2900/mfd/cg2900_chip.h new file mode 100644 index 00000000000..5db655903a8 --- /dev/null +++ b/drivers/staging/cg2900/mfd/cg2900_chip.h @@ -0,0 +1,619 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson. + * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson. + * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson. + * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson. + * Hemant Gupta (hemant.gupta@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Linux Bluetooth HCI H:4 Driver for ST-Ericsson CG2900 GPS/BT/FM controller. + */ + +#ifndef _CG2900_CHIP_H_ +#define _CG2900_CHIP_H_ + +/* + * Utility + */ + +static inline void set_low_nibble(__u8 *var, __u8 value) +{ + *var = (*var & 0xf0) | (value & 0x0f); +} + +static inline void set_high_nibble(__u8 *var, __u8 value) +{ + *var = (*var & 0x0f) | (value << 4); +} + +static inline void store_bit(__u8 *var, size_t bit, __u8 value) +{ + *var = (*var & ~(1u << bit)) | (value << bit); +} + +/* + * General chip defines + */ + +/* Supported chips */ +#define CG2900_SUPP_MANUFACTURER 0x30 + +/* + * Bluetooth + */ + +#define BT_SIZE_OF_HDR (sizeof(__le16) + sizeof(__u8)) +#define BT_PARAM_LEN(__pkt_len) (__pkt_len - BT_SIZE_OF_HDR) + +struct bt_cmd_cmpl_event { + __u8 eventcode; + __u8 plen; + __u8 n_commands; + __le16 opcode; + /* + * According to BT-specification what follows is "parameters" + * and unique to every command, but all commands start the + * parameters with the status field so include it here for + * convenience + */ + __u8 status; + __u8 data[]; +} __packed; + +/* BT VS Store In FS command */ +#define CG2900_BT_OP_VS_STORE_IN_FS 0xFC22 +struct bt_vs_store_in_fs_cmd { + __le16 opcode; + __u8 plen; + __u8 user_id; + __u8 len; + __u8 data[]; +} __packed; + +#define CG2900_VS_STORE_IN_FS_USR_ID_BD_ADDR 0xFE + +#define HCI_EV_VENDOR_SPECIFIC 0xFF +#define CG2900_EV_VS_WRITE_FILE_BLOCK_COMPLETE 0x60 +/* BT VS Event */ +struct bt_vs_evt { + __u8 evt_id; + __u8 data[]; +} __packed; + +/* BT VS Write File Block Event */ +struct bt_vs_write_file_block_evt { + __u8 status; + __u8 file_blk_id; +} __packed; + +/* BT VS Write File Block command */ +#define CG2900_BT_OP_VS_WRITE_FILE_BLOCK 0xFC2E +struct bt_vs_write_file_block_cmd { + __le16 opcode; + __u8 plen; + __u8 id; + __u8 data[]; +} __packed; + +#define CG2900_BT_DISABLE 0x00 +#define CG2900_BT_ENABLE 0x01 + +/* BT VS BT Enable command */ +#define CG2900_BT_OP_VS_BT_ENABLE 0xFF10 +struct bt_vs_bt_enable_cmd { + __le16 op_code; + u8 plen; + u8 enable; +} __packed; + +/* Bytes in the command Hci_Cmd_ST_Set_Uart_Baud_Rate */ +#define CG2900_BAUD_RATE_57600 0x03 +#define CG2900_BAUD_RATE_115200 0x02 +#define CG2900_BAUD_RATE_230400 0x01 +#define CG2900_BAUD_RATE_460800 0x00 +#define CG2900_BAUD_RATE_921600 0x20 +#define CG2900_BAUD_RATE_2000000 0x25 +#define CG2900_BAUD_RATE_3000000 0x27 +#define CG2900_BAUD_RATE_3250000 0x28 +#define CG2900_BAUD_RATE_4000000 0x2B + +/* BT VS SetBaudRate command */ +#define CG2900_BT_OP_VS_SET_BAUD_RATE 0xFC09 +struct bt_vs_set_baud_rate_cmd { + __le16 opcode; + __u8 plen; + __u8 baud_rate; +} __packed; + +#define CG2900_BT_SELFTEST_SUCCESSFUL 0x00 +#define CG2900_BT_SELFTEST_FAILED 0x01 +#define CG2900_BT_SELFTEST_NOT_COMPLETED 0x02 + +/* BT VS ReadSelfTestsResult command & event */ +#define CG2900_BT_OP_VS_READ_SELTESTS_RESULT 0xFC10 +struct bt_vs_read_selftests_result_evt { + __u8 status; + __u8 result; +} __packed; + +/* Bluetooth Vendor Specific Opcodes */ +#define CG2900_BT_OP_VS_POWER_SWITCH_OFF 0xFD40 +#define CG2900_BT_OP_VS_SYSTEM_RESET 0xFF12 + +#define CG2900_BT_OPCODE_NONE 0xFFFF + +/* + * Common multimedia + */ + +#define CG2900_CODEC_TYPE_NONE 0x00 +#define CG2900_CODEC_TYPE_SBC 0x01 + +#define CG2900_PCM_MODE_SLAVE 0x00 +#define CG2900_PCM_MODE_MASTER 0x01 + +#define CG2900_I2S_MODE_MASTER 0x00 +#define CG2900_I2S_MODE_SLAVE 0x01 + +/* + * CG2900 PG1 multimedia API + */ + +#define CG2900_BT_VP_TYPE_PCM 0x00 +#define CG2900_BT_VP_TYPE_I2S 0x01 +#define CG2900_BT_VP_TYPE_SLIMBUS 0x02 +#define CG2900_BT_VP_TYPE_FM 0x03 +#define CG2900_BT_VP_TYPE_BT_SCO 0x04 +#define CG2900_BT_VP_TYPE_BT_A2DP 0x05 +#define CG2900_BT_VP_TYPE_ANALOG 0x07 + +#define CG2900_BT_VS_SET_HARDWARE_CONFIG 0xFD54 +/* These don't have the same length, so a union won't work */ +struct bt_vs_set_hw_cfg_cmd_pcm { + __le16 opcode; + __u8 plen; + __u8 vp_type; + __u8 port_id; + __u8 mode_dir; /* NB: mode is in bit 1 (not 0) */ + __u8 bit_clock; + __le16 frame_len; +} __packed; +#define HWCONFIG_PCM_SET_MODE(pcfg, mode) \ + set_low_nibble(&(pcfg)->mode_dir, (mode) << 1) +#define HWCONFIG_PCM_SET_DIR(pcfg, idx, dir) \ + store_bit(&(pcfg)->mode_dir, (idx) + 4, (dir)) + +struct bt_vs_set_hw_cfg_cmd_i2s { + __le16 opcode; + __u8 plen; + __u8 vp_type; + __u8 port_id; + __u8 half_period; + __u8 master_slave; +} __packed; + +/* Max length for allocating */ +#define CG2900_BT_LEN_VS_SET_HARDWARE_CONFIG \ + (sizeof(struct bt_vs_set_hw_cfg_cmd_pcm)) + +#define CG2900_BT_VS_SET_SESSION_CONFIG 0xFD55 +struct session_config_vport { + __u8 type; + union { + struct { + __le16 acl_handle; + __u8 reserved[10]; + } sco; + struct { + __u8 reserved[12]; + } fm; + struct { + __u8 index; + __u8 slots_used; + __u8 slot_start[4]; + __u8 reserved[6]; + } pcm; + struct { + __u8 index; + __u8 channel; + __u8 reserved[10]; + } i2s; + }; +} __packed; +#define SESSIONCFG_PCM_SET_USED(port, idx, use) \ + store_bit(&(port).pcm.slots_used, (idx), (use)) + +struct session_config_stream { + __u8 media_type; + __u8 csel_srate; + __u8 codec_type; + __u8 codec_mode; + __u8 codec_params[3]; + struct session_config_vport inport; + struct session_config_vport outport; +} __packed; +#define SESSIONCFG_SET_CHANNELS(pcfg, chnl) \ + set_low_nibble(&(pcfg)->csel_srate, (chnl)) +#define SESSIONCFG_I2S_SET_SRATE(pcfg, rate) \ + set_high_nibble(&(pcfg)->csel_srate, (rate)) + +struct bt_vs_session_config_cmd { + __le16 opcode; + __u8 plen; + __u8 n_streams; /* we only support one here */ + struct session_config_stream stream; +} __packed; + +#define CG2900_BT_SESSION_MEDIA_TYPE_AUDIO 0x00 + +#define CG2900_BT_SESSION_RATE_8K 0x01 +#define CG2900_BT_SESSION_RATE_16K 0x02 +#define CG2900_BT_SESSION_RATE_44_1K 0x04 +#define CG2900_BT_SESSION_RATE_48K 0x05 + +#define CG2900_BT_MEDIA_CONFIG_MONO 0x00 +#define CG2900_BT_MEDIA_CONFIG_STEREO 0x01 +#define CG2900_BT_MEDIA_CONFIG_JOINT_STEREO 0x02 +#define CG2900_BT_MEDIA_CONFIG_DUAL_CHANNEL 0x03 + +#define CG2900_BT_SESSION_I2S_INDEX_I2S 0x00 +#define CG2900_BT_SESSION_PCM_INDEX_PCM_I2S 0x00 + + +#define CG2900_BT_VS_SESSION_CTRL 0xFD57 +struct bt_vs_session_ctrl_cmd { + __le16 opcode; + __u8 plen; + __u8 id; + __u8 control; +} __packed; + +#define CG2900_BT_SESSION_START 0x00 +#define CG2900_BT_SESSION_STOP 0x01 +#define CG2900_BT_SESSION_PAUSE 0x02 +#define CG2900_BT_SESSION_RESUME 0x03 + +#define CG2900_BT_VS_RESET_SESSION_CONFIG 0xFD56 +struct bt_vs_reset_session_cfg_cmd { + __le16 opcode; + __u8 plen; + __u8 id; +} __packed; + +/* + * CG2900 PG2 multimedia API + */ + +#define CG2900_MC_PORT_PCM_I2S 0x00 +#define CG2900_MC_PORT_I2S 0x01 +#define CG2900_MC_PORT_BT_SCO 0x04 +#define CG2900_MC_PORT_FM_RX_0 0x07 +#define CG2900_MC_PORT_FM_RX_1 0x08 +#define CG2900_MC_PORT_FM_TX 0x09 + +#define CG2900_MC_VS_PORT_CONFIG 0xFD64 +struct mc_vs_port_cfg_cmd { + __le16 opcode; + __u8 plen; + __u8 type; + /* + * one of the following configuration structs should follow, but they + * have different lengths so a union will not work + */ +} __packed; + +struct mc_vs_port_cfg_pcm_i2s { + __u8 role_dir; + __u8 sco_a2dp_slots_used; + __u8 fm_slots_used; + __u8 ring_slots_used; + __u8 slot_start[4]; + __u8 ratio_mode; + __u8 frame_len; + __u8 bitclk_srate; +} __packed; +#define PORTCFG_PCM_SET_ROLE(cfg, role) \ + set_low_nibble(&(cfg).role_dir, (role)) +#define PORTCFG_PCM_SET_DIR(cfg, idx, dir) \ + store_bit(&(cfg).role_dir, (idx) + 4, (dir)) +static inline void portcfg_pcm_set_sco_used(struct mc_vs_port_cfg_pcm_i2s *cfg, + size_t index, __u8 use) +{ + if (use) { + /* clear corresponding slot in all cases */ + cfg->sco_a2dp_slots_used &= ~(0x11 << index); + cfg->fm_slots_used &= ~(0x11 << index); + cfg->ring_slots_used &= ~(0x11 << index); + /* set for sco */ + cfg->sco_a2dp_slots_used |= (1u << index); + } else { + /* only clear for sco */ + cfg->sco_a2dp_slots_used &= ~(1u << index); + } +} +#define PORTCFG_PCM_SET_SCO_USED(cfg, idx, use) \ + portcfg_pcm_set_sco_used(&cfg, idx, use) +#define PORTCFG_PCM_SET_RATIO(cfg, r) \ + set_low_nibble(&(cfg).ratio_mode, (r)) +#define PORTCFG_PCM_SET_MODE(cfg, mode) \ + set_high_nibble(&(cfg).ratio_mode, (mode)) +#define PORTCFG_PCM_SET_BITCLK(cfg, clk) \ + set_low_nibble(&(cfg).bitclk_srate, (clk)) +#define PORTCFG_PCM_SET_SRATE(cfg, rate) \ + set_high_nibble(&(cfg).bitclk_srate, (rate)) + +#define CG2900_MC_PCM_SAMPLE_RATE_8 1 +#define CG2900_MC_PCM_SAMPLE_RATE_16 2 +#define CG2900_MC_PCM_SAMPLE_RATE_44_1 4 +#define CG2900_MC_PCM_SAMPLE_RATE_48 6 + +struct mc_vs_port_cfg_i2s { + __u8 role_hper; + __u8 csel_srate; + __u8 wordlen; +}; +#define PORTCFG_I2S_SET_ROLE(cfg, role) \ + set_low_nibble(&(cfg).role_hper, (role)) +#define PORTCFG_I2S_SET_HALFPERIOD(cfg, hper) \ + set_high_nibble(&(cfg).role_hper, (hper)) +#define PORTCFG_I2S_SET_CHANNELS(cfg, chnl) \ + set_low_nibble(&(cfg).csel_srate, (chnl)) +#define PORTCFG_I2S_SET_SRATE(cfg, rate) \ + set_high_nibble(&(cfg).csel_srate, (rate)) +#define PORTCFG_I2S_SET_WORDLEN(cfg, len) \ + set_low_nibble(&(cfg).wordlen, len) + +#define CG2900_MC_I2S_RIGHT_CHANNEL 1 +#define CG2900_MC_I2S_LEFT_CHANNEL 2 +#define CG2900_MC_I2S_BOTH_CHANNELS 3 + +#define CG2900_MC_I2S_SAMPLE_RATE_8 0 +#define CG2900_MC_I2S_SAMPLE_RATE_16 1 +#define CG2900_MC_I2S_SAMPLE_RATE_44_1 2 +#define CG2900_MC_I2S_SAMPLE_RATE_48 4 + +#define CG2900_MC_I2S_WORD_16 1 +#define CG2900_MC_I2S_WORD_32 3 + +struct mc_vs_port_cfg_fm { + __u8 srate; /* NB: value goes in _upper_ nibble! */ +}; +#define PORTCFG_FM_SET_SRATE(cfg, rate) \ + set_high_nibble(&(cfg).srate, (rate)) + +struct mc_vs_port_cfg_sco { + __le16 acl_id; + __u8 wbs_codec; +} __packed; +#define PORTCFG_SCO_SET_CODEC(cfg, codec) \ + set_high_nibble(&(cfg).wbs_codec, (codec)) + +#define CG2900_MC_VS_CREATE_STREAM 0xFD66 +struct mc_vs_create_stream_cmd { + __le16 opcode; + __u8 plen; + __u8 id; + __u8 inport; + __u8 outport; + __u8 order; /* NB: not used by chip */ +} __packed; + +#define CG2900_MC_VS_DELETE_STREAM 0xFD67 +struct mc_vs_delete_stream_cmd { + __le16 opcode; + __u8 plen; + __u8 stream; +} __packed; + +#define CG2900_MC_VS_STREAM_CONTROL 0xFD68 +struct mc_vs_stream_ctrl_cmd { + __le16 opcode; + __u8 plen; + __u8 command; + __u8 n_streams; + __u8 stream[]; +} __packed; + +#define CG2900_MC_STREAM_START 0x00 +#define CG2900_MC_STREAM_STOP 0x01 +#define CG2900_MC_STREAM_STOP_FLUSH 0x02 + +#define CG2900_MC_VS_SET_FM_START_MODE 0xFD69 + +/* + * FM + */ + +/* FM legacy command packet */ +struct fm_leg_cmd { + __u8 length; + __u8 opcode; + __u8 read_write; + __u8 fm_function; + union { /* Payload varies with function */ + __le16 irqmask; + struct fm_leg_fm_cmd { + __le16 head; + __le16 data[]; + } fm_cmd; + }; +} __packed; + +/* FM legacy command complete packet */ +struct fm_leg_cmd_cmpl { + __u8 param_length; + __u8 status; + __u8 opcode; + __u8 read_write; + __u8 cmd_status; + __u8 fm_function; + __le16 response_head; + __le16 data[]; +} __packed; + +/* FM legacy interrupt packet, PG2 style */ +struct fm_leg_irq_v2 { + __u8 param_length; + __u8 status; + __u8 opcode; + __u8 event_type; + __u8 event_id; + __le16 irq; +} __packed; + +/* FM legacy interrupt packet, PG1 style */ +struct fm_leg_irq_v1 { + __u8 param_length; + __u8 opcode; + __u8 event_id; + __le16 irq; +} __packed; + +union fm_leg_evt_or_irq { + __u8 param_length; + struct fm_leg_cmd_cmpl evt; + struct fm_leg_irq_v2 irq_v2; + struct fm_leg_irq_v1 irq_v1; +} __packed; + +/* FM Opcode generic*/ +#define CG2900_FM_GEN_ID_LEGACY 0xFE + +/* FM event*/ +#define CG2900_FM_EVENT_UNKNOWN 0 +#define CG2900_FM_EVENT_CMD_COMPLETE 1 +#define CG2900_FM_EVENT_INTERRUPT 2 + +/* FM do-command identifiers. */ +#define CG2900_FM_DO_AIP_FADE_START 0x0046 +#define CG2900_FM_DO_AUP_BT_FADE_START 0x01C2 +#define CG2900_FM_DO_AUP_EXT_FADE_START 0x0102 +#define CG2900_FM_DO_AUP_FADE_START 0x00A2 +#define CG2900_FM_DO_FMR_SETANTENNA 0x0663 +#define CG2900_FM_DO_FMR_SP_AFSWITCH_START 0x04A3 +#define CG2900_FM_DO_FMR_SP_AFUPDATE_START 0x0463 +#define CG2900_FM_DO_FMR_SP_BLOCKSCAN_START 0x0683 +#define CG2900_FM_DO_FMR_SP_PRESETPI_START 0x0443 +#define CG2900_FM_DO_FMR_SP_SCAN_START 0x0403 +#define CG2900_FM_DO_FMR_SP_SEARCH_START 0x03E3 +#define CG2900_FM_DO_FMR_SP_SEARCHPI_START 0x0703 +#define CG2900_FM_DO_FMR_SP_TUNE_SETCHANNEL 0x03C3 +#define CG2900_FM_DO_FMR_SP_TUNE_STEPCHANNEL 0x04C3 +#define CG2900_FM_DO_FMT_PA_SETCTRL 0x01A4 +#define CG2900_FM_DO_FMT_PA_SETMODE 0x01E4 +#define CG2900_FM_DO_FMT_SP_TUNE_SETCHANNEL 0x0064 +#define CG2900_FM_DO_GEN_ANTENNACHECK_START 0x02A1 +#define CG2900_FM_DO_GEN_GOTOMODE 0x0041 +#define CG2900_FM_DO_GEN_POWERSUPPLY_SETMODE 0x0221 +#define CG2900_FM_DO_GEN_SELECTREFERENCECLOCK 0x0201 +#define CG2900_FM_DO_GEN_SETPROCESSINGCLOCK 0x0241 +#define CG2900_FM_DO_GEN_SETREFERENCECLOCKPLL 0x01A1 +#define CG2900_FM_DO_TST_TX_RAMP_START 0x0147 +#define CG2900_FM_CMD_NONE 0xFFFF +#define CG2900_FM_CMD_ID_GEN_GOTO_POWER_DOWN 0x0081 +#define CG2900_FM_CMD_ID_GEN_GOTO_STANDBY 0x0061 + +/* FM Command IDs */ +#define CG2900_FM_CMD_ID_AUP_EXT_SET_MODE 0x0162 +#define CG2900_FM_CMD_ID_AUP_EXT_SET_CTRL 0x0182 +#define CG2900_FM_CMD_ID_AIP_SET_MODE 0x01C6 +#define CG2900_FM_CMD_ID_AIP_BT_SET_CTRL 0x01A6 +#define CG2900_FM_CMD_ID_AIP_BT_SET_MODE 0x01E6 + +/* FM Command Parameters. */ +#define CG2900_FM_CMD_PARAM_ENABLE 0x00 +#define CG2900_FM_CMD_PARAM_DISABLE 0x01 +#define CG2900_FM_CMD_PARAM_RESET 0x02 +#define CG2900_FM_CMD_PARAM_WRITECOMMAND 0x10 +#define CG2900_FM_CMD_PARAM_SET_INT_MASK_ALL 0x20 +#define CG2900_FM_CMD_PARAM_GET_INT_MASK_ALL 0x21 +#define CG2900_FM_CMD_PARAM_SET_INT_MASK 0x22 +#define CG2900_FM_CMD_PARAM_GET_INT_MASK 0x23 +#define CG2900_FM_CMD_PARAM_FM_FW_DOWNLOAD 0x30 +#define CG2900_FM_CMD_PARAM_NONE 0xFF + +/* FM Legacy Command Parameters */ +#define CG2900_FM_CMD_LEG_PARAM_WRITE 0x00 +#define CG2900_FM_CMD_LEG_PARAM_IRQ 0x01 + +/* FM Command Status. */ +#define CG2900_FM_CMD_STATUS_COMMAND_SUCCEEDED 0x00 +#define CG2900_FM_CMD_STATUS_HW_FAILURE 0x03 +#define CG2900_FM_CMD_STATUS_INVALID_PARAMS 0x12 +#define CG2900_FM_CMD_STATUS_UNINITILIZED 0x15 +#define CG2900_FM_CMD_STATUS_UNSPECIFIED_ERROR 0x1F +#define CG2900_FM_CMD_STATUS_COMMAND_DISALLOWED 0x0C +#define CG2900_FM_CMD_STATUS_FW_WRONG_SEQUENCE_NR 0xF1 +#define CG2900_FM_CMD_STATUS_FW_UNKNOWN_FILE 0xF2 +#define CG2900_FM_CMD_STATUS_FW_FILE_VER_MISMATCH 0xF3 + +/* FM Interrupts. */ +#define CG2900_FM_IRPT_FIQ 0x0000 +#define CG2900_FM_IRPT_OPERATION_SUCCEEDED 0x0001 +#define CG2900_FM_IRPT_OPERATION_FAILED 0x0002 +#define CG2900_FM_IRPT_BUFFER_FULL 0x0008 +#define CG2900_FM_IRPT_BUFFER_EMPTY 0x0008 +#define CG2900_FM_IRPT_SIGNAL_QUALITY_LOW 0x0010 +#define CG2900_FM_IRPT_MUTE_STATUS_CHANGED 0x0010 +#define CG2900_FM_IRPT_MONO_STEREO_TRANSITION 0x0020 +#define CG2900_FM_IRPT_OVER_MODULATION 0x0020 +#define CG2900_FM_IRPT_RDS_SYNC_FOUND 0x0040 +#define CG2900_FM_IRPT_INPUT_OVERDRIVE 0x0040 +#define CG2900_FM_IRPT_RDS_SYNC_LOST 0x0080 +#define CG2900_FM_IRPT_PI_CODE_CHANGED 0x0100 +#define CG2900_FM_IRPT_REQUEST_BLOCK_AVALIBLE 0x0200 +#define CG2900_FM_IRPT_BUFFER_CLEARED 0x2000 +#define CG2900_FM_IRPT_WARM_BOOT_READY 0x4000 +#define CG2900_FM_IRPT_COLD_BOOT_READY 0x8000 + +/* FM Legacy Function Command Parameters */ + +/* AUP_EXT_SetMode Output enum */ +#define CG2900_FM_CMD_AUP_EXT_SET_MODE_DISABLED 0x0000 +#define CG2900_FM_CMD_AUP_EXT_SET_MODE_I2S 0x0001 +#define CG2900_FM_CMD_AUP_EXT_SET_MODE_PARALLEL 0x0002 + +/* SetControl Conversion enum */ +#define CG2900_FM_CMD_SET_CTRL_CONV_UP 0x0000 +#define CG2900_FM_CMD_SET_CTRL_CONV_DOWN 0x0001 + +/* AIP_SetMode Input enum */ +#define CG2900_FM_CMD_AIP_SET_MODE_INPUT_ANA 0x0000 +#define CG2900_FM_CMD_AIP_SET_MODE_INPUT_DIG 0x0001 + +/* AIP_BT_SetMode Input enum */ +#define CG2900_FM_CMD_AIP_BT_SET_MODE_INPUT_RESERVED 0x0000 +#define CG2900_FM_CMD_AIP_BT_SET_MODE_INPUT_I2S 0x0001 +#define CG2900_FM_CMD_AIP_BT_SET_MODE_INPUT_PAR 0x0002 +#define CG2900_FM_CMD_AIP_BT_SET_MODE_INPUT_FIFO 0x0003 + +/* FM Parameter Lengths = FM command length - length field (1 byte) */ +#define CG2900_FM_CMD_PARAM_LEN(len) (len - 1) + +/* + * FM Command ID mapped per byte and shifted 3 bits left + * Also adds number of parameters at first 3 bits of LSB. + */ +static inline __u16 cg2900_get_fm_cmd_id(__u16 opcode) +{ + return opcode >> 3; +} + +static inline __u16 cg2900_make_fm_cmd_id(__u16 id, __u8 num_params) +{ + return (id << 3) | num_params; +} + +/* + * GNSS + */ + +struct gnss_hci_hdr { + __u8 op_code; + __le16 plen; +} __packed; + +#endif /* _CG2900_CHIP_H_ */ diff --git a/drivers/staging/cg2900/mfd/cg2900_core.c b/drivers/staging/cg2900/mfd/cg2900_core.c new file mode 100644 index 00000000000..fe28ab6b7ae --- /dev/null +++ b/drivers/staging/cg2900/mfd/cg2900_core.c @@ -0,0 +1,719 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson. + * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson. + * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson. + * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Linux Bluetooth HCI H:4 Driver for ST-Ericsson CG2900 GPS/BT/FM controller. + */ +#define NAME "cg2900_core" +#define pr_fmt(fmt) NAME ": " fmt "\n" + +#include <asm/byteorder.h> +#include <linux/firmware.h> +#include <linux/fs.h> +#include <linux/gfp.h> +#include <linux/init.h> +#include <linux/jiffies.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/stat.h> +#include <linux/time.h> +#include <linux/timer.h> +#include <linux/types.h> +#include <linux/wait.h> +#include <linux/workqueue.h> +#include <linux/mfd/core.h> +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci.h> + +#include "cg2900.h" +#include "cg2900_core.h" + +/* Device names */ +#define CG2900_CDEV_NAME "cg2900_core_test" +#define CG2900_CLASS_NAME "cg2900_class" +#define CG2900_DEVICE_NAME "cg2900_driver" +#define CORE_WQ_NAME "cg2900_core_wq" + +#define LOGGER_DIRECTION_TX 0 +#define LOGGER_DIRECTION_RX 1 + +/* + * Timeout values + */ +#define CHIP_READY_TIMEOUT (100) /* ms */ +#define REVISION_READOUT_TIMEOUT (500) /* ms */ +#define SLEEP_TIMEOUT_MS (150) /* ms */ +/* Timeout value to check CTS line for low power */ +#define READY_FOR_SLEEP_TIMEOUT_MS (50) /* ms */ + +/** + * enum boot_state - BOOT-state for CG2900 Core. + * @BOOT_RESET: HCI Reset has been sent. + * @BOOT_READ_LOCAL_VERSION_INFORMATION: ReadLocalVersionInformation + * command has been sent. + * @BOOT_READY: CG2900 Core boot is ready. + * @BOOT_FAILED: CG2900 Core boot failed. + */ +enum boot_state { + BOOT_RESET, + BOOT_READ_LOCAL_VERSION_INFORMATION, + BOOT_READY, + BOOT_FAILED +}; + +/** + * struct chip_handler_item - Structure to store chip handler cb. + * @list: list_head struct. + * @cb: Chip handler callback struct. + */ +struct chip_handler_item { + struct list_head list; + struct cg2900_id_callbacks cb; +}; + +/** + * struct core_info - Main info structure for CG2900 Core. + * @boot_state: Current BOOT-state of CG2900 Core. + * @wq: CG2900 Core workqueue. + * @chip_dev: Device structure for chip driver. + * @work: Work structure. + */ +struct core_info { + enum boot_state boot_state; + struct workqueue_struct *wq; + struct cg2900_chip_dev *chip_dev; + struct work_struct work; +}; + +/** + * struct main_info - Main info structure for CG2900 Core. + * @dev: Device structure for STE Connectivity driver. + * @man_mutex: Management mutex. + * @chip_handlers: List of the register handlers for different chips. + * @wq: Wait queue. + */ +struct main_info { + struct device *dev; + struct mutex man_mutex; + struct list_head chip_handlers; + wait_queue_head_t wq; +}; + +/* core_info - Main information object for CG2900 Core. */ +static struct main_info *main_info; + +/* Module parameters */ +u8 bd_address[] = {0x00, 0xBE, 0xAD, 0xDE, 0x80, 0x00}; +EXPORT_SYMBOL_GPL(bd_address); +int bd_addr_count = BT_BDADDR_SIZE; + +static int sleep_timeout_ms = SLEEP_TIMEOUT_MS; + +/** + * send_bt_cmd() - Copy and send sk_buffer with no assigned user. + * @dev: Current chip to transmit to. + * @data: Data to send. + * @length: Length in bytes of data. + * + * The send_bt_cmd() function allocate sk_buffer, copy supplied + * data to it, and send the sk_buffer to controller. + */ +void send_bt_cmd(struct cg2900_chip_dev *dev, void *data, int length) +{ + struct sk_buff *skb; + int err; + + skb = alloc_skb(length + HCI_H4_SIZE, GFP_ATOMIC); + if (!skb) { + dev_err(dev->dev, "send_bt_cmd: Couldn't alloc sk_buff with " + "length %d\n", length); + return; + } + + skb_reserve(skb, HCI_H4_SIZE); + memcpy(skb_put(skb, length), data, length); + skb_push(skb, HCI_H4_SIZE); + skb->data[0] = HCI_BT_CMD_H4_CHANNEL; + + err = dev->t_cb.write(dev, skb); + if (err) { + dev_err(dev->dev, "send_bt_cmd: Transport write failed (%d)\n", + err); + kfree_skb(skb); + } +} + +/** + * handle_reset_cmd_complete_evt() - Handle a received HCI Command Complete event for a Reset command. + * @dev: Current device. + * @data: Pointer to received HCI data packet. + * + * Returns: + * True, if packet was handled internally, + * False, otherwise. + */ +static bool handle_reset_cmd_complete_evt(struct cg2900_chip_dev *dev, u8 *data) +{ + bool pkt_handled = false; + u8 status = data[0]; + struct hci_command_hdr cmd; + struct core_info *info = dev->prv_data; + + dev_dbg(dev->dev, "Received Reset complete event with status 0x%X\n", + status); + + if (info->boot_state == BOOT_RESET) { + /* Transmit HCI Read Local Version Information command */ + dev_dbg(dev->dev, "New boot_state: " + "BOOT_READ_LOCAL_VERSION_INFORMATION\n"); + info->boot_state = BOOT_READ_LOCAL_VERSION_INFORMATION; + cmd.opcode = cpu_to_le16(HCI_OP_READ_LOCAL_VERSION); + cmd.plen = 0; /* No parameters for HCI reset */ + send_bt_cmd(dev, &cmd, sizeof(cmd)); + + pkt_handled = true; + } + + return pkt_handled; +} + +/** + * handle_read_local_version_info_cmd_complete_evt() - Handle a received HCI Command Complete event for a ReadLocalVersionInformation command. + * @dev: Current device. + * @data: Pointer to received HCI data packet. + * + * Returns: + * True, if packet was handled internally, + * False, otherwise. + */ +static bool +handle_read_local_version_info_cmd_complete_evt(struct cg2900_chip_dev *dev, + u8 *data) +{ + struct hci_rp_read_local_version *evt; + struct core_info *info = dev->prv_data; + + /* Check we're in the right state */ + if (info->boot_state != BOOT_READ_LOCAL_VERSION_INFORMATION) + return false; + + /* We got an answer for our HCI command. Extract data */ + evt = (struct hci_rp_read_local_version *)data; + + /* We will handle the packet */ + if (HCI_BT_ERROR_NO_ERROR != evt->status) { + dev_err(dev->dev, "Received Read Local Version Information " + "with status 0x%X\n", evt->status); + dev_dbg(dev->dev, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + wake_up_all(&main_info->wq); + return true; + } + + /* The command worked. Store the data */ + dev->chip.hci_version = evt->hci_ver; + dev->chip.hci_revision = le16_to_cpu(evt->hci_rev); + dev->chip.lmp_pal_version = evt->lmp_ver; + dev->chip.manufacturer = le16_to_cpu(evt->manufacturer); + dev->chip.hci_sub_version = le16_to_cpu(evt->lmp_subver); + dev_info(dev->dev, "Received Read Local Version Information with:\n" + "\thci_version: 0x%02X\n" + "\thci_revision: 0x%04X\n" + "\tlmp_pal_version: 0x%02X\n" + "\tmanufacturer: 0x%04X\n" + "\thci_sub_version: 0x%04X\n", + dev->chip.hci_version, dev->chip.hci_revision, + dev->chip.lmp_pal_version, dev->chip.manufacturer, + dev->chip.hci_sub_version); + + dev_dbg(dev->dev, "New boot_state: BOOT_READY\n"); + info->boot_state = BOOT_READY; + wake_up_all(&main_info->wq); + + return true; +} + +/** + * handle_rx_data_bt_evt() - Check if data should be handled in CG2900 Core. + * @dev: Current chip + * @skb: Data packet + * + * The handle_rx_data_bt_evt() function checks if received data should be + * handled in CG2900 Core. If so handle it correctly. + * Received data is always HCI BT Event. + * + * Returns: + * True, if packet was handled internally, + * False, otherwise. + */ +static bool handle_rx_data_bt_evt(struct cg2900_chip_dev *dev, + struct sk_buff *skb) +{ + bool pkt_handled = false; + u8 *data = &skb->data[CG2900_SKB_RESERVE]; + struct hci_event_hdr *evt; + struct hci_ev_cmd_complete *cmd_complete; + u16 op_code; + + evt = (struct hci_event_hdr *)data; + + /* First check the event code */ + if (HCI_EV_CMD_COMPLETE != evt->evt) + return false; + + data += sizeof(*evt); + cmd_complete = (struct hci_ev_cmd_complete *)data; + + op_code = le16_to_cpu(cmd_complete->opcode); + + dev_dbg(dev->dev, "Received Command Complete: op_code = 0x%04X\n", + op_code); + data += sizeof(*cmd_complete); /* Move to first byte after OCF */ + + if (op_code == HCI_OP_RESET) + pkt_handled = handle_reset_cmd_complete_evt(dev, data); + else if (op_code == HCI_OP_READ_LOCAL_VERSION) + pkt_handled = handle_read_local_version_info_cmd_complete_evt + (dev, data); + + if (pkt_handled) + kfree_skb(skb); + + return pkt_handled; +} + +static void cg2900_data_from_chip(struct cg2900_chip_dev *dev, + struct sk_buff *skb) +{ + u8 h4_channel; + + dev_dbg(dev->dev, "cg2900_data_from_chip\n"); + + if (!skb) { + dev_err(dev->dev, "No data supplied\n"); + return; + } + + h4_channel = skb->data[0]; + + /* + * First check if this is the response for something + * we have sent internally. + */ + if (HCI_BT_EVT_H4_CHANNEL == h4_channel && + handle_rx_data_bt_evt(dev, skb)) { + dev_dbg(dev->dev, "Received packet handled internally\n"); + } else { + dev_err(dev->dev, + "cg2900_data_from_chip: Received unexpected packet\n"); + kfree_skb(skb); + } +} + +/** + * work_hw_registered() - Called when the interface to HW has been established. + * @work: Reference to work data. + * + * Since there now is a transport identify the connected chip and decide which + * chip handler to use. + */ +static void work_hw_registered(struct work_struct *work) +{ + struct hci_command_hdr cmd; + struct cg2900_chip_dev *dev; + struct core_info *info; + bool chip_handled = false; + struct list_head *cursor; + struct chip_handler_item *tmp; + + dev_dbg(main_info->dev, "work_hw_registered\n"); + + if (!work) { + dev_err(main_info->dev, "work_hw_registered: work == NULL\n"); + return; + } + + info = container_of(work, struct core_info, work); + dev = info->chip_dev; + + /* + * This might look strange, but we need to read out + * the revision info in order to be able to shutdown the chip properly. + */ + if (dev->t_cb.set_chip_power) + dev->t_cb.set_chip_power(dev, true); + + /* Set our function to receive data from chip */ + dev->c_cb.data_from_chip = cg2900_data_from_chip; + + /* + * Transmit HCI reset command to ensure the chip is using + * the correct transport + */ + dev_dbg(dev->dev, "New boot_state: BOOT_RESET\n"); + info->boot_state = BOOT_RESET; + cmd.opcode = cpu_to_le16(HCI_OP_RESET); + cmd.plen = 0; /* No parameters for HCI reset */ + send_bt_cmd(dev, &cmd, sizeof(cmd)); + + dev_dbg(dev->dev, + "Wait up to 500 milliseconds for revision to be read\n"); + wait_event_timeout(main_info->wq, + (BOOT_READY == info->boot_state || + BOOT_FAILED == info->boot_state), + msecs_to_jiffies(REVISION_READOUT_TIMEOUT)); + + if (BOOT_READY != info->boot_state) { + dev_err(dev->dev, + "Could not read out revision from the chip\n"); + info->boot_state = BOOT_FAILED; + dev->t_cb.set_chip_power(dev, false); + return; + } + + dev->c_cb.data_from_chip = NULL; + + mutex_lock(&main_info->man_mutex); + list_for_each(cursor, &main_info->chip_handlers) { + tmp = list_entry(cursor, struct chip_handler_item, list); + chip_handled = tmp->cb.check_chip_support(dev); + if (chip_handled) { + dev_info(dev->dev, "Chip handler found\n"); + break; + } + } + mutex_unlock(&main_info->man_mutex); + + if (!chip_handled) + dev_info(dev->dev, "No chip handler found\n"); +} + +/** + * cg2900_register_chip_driver() - Register a chip handler. + * @cb: Callbacks to call when chip is connected. + * + * Returns: + * 0 if there is no error. + * -EINVAL if NULL is supplied as @cb. + * -ENOMEM if allocation fails or work queue can't be created. + */ +int cg2900_register_chip_driver(struct cg2900_id_callbacks *cb) +{ + struct chip_handler_item *item; + + dev_dbg(main_info->dev, "cg2900_register_chip_driver\n"); + + if (!cb) { + dev_err(main_info->dev, "NULL supplied as cb\n"); + return -EINVAL; + } + + item = kzalloc(sizeof(*item), GFP_KERNEL); + if (!item) { + dev_err(main_info->dev, + "cg2900_register_chip_driver: " + "Failed to alloc memory\n"); + return -ENOMEM; + } + + memcpy(&item->cb, cb, sizeof(cb)); + mutex_lock(&main_info->man_mutex); + list_add_tail(&item->list, &main_info->chip_handlers); + mutex_unlock(&main_info->man_mutex); + return 0; +} +EXPORT_SYMBOL_GPL(cg2900_register_chip_driver); + +/** + * cg2900_deregister_chip_driver() - Deregister a chip handler. + * @cb: Callbacks to call when chip is connected. + */ +void cg2900_deregister_chip_driver(struct cg2900_id_callbacks *cb) +{ + struct chip_handler_item *tmp; + struct list_head *cursor, *next; + + dev_dbg(main_info->dev, "cg2900_deregister_chip_driver\n"); + + if (!cb) { + dev_err(main_info->dev, "NULL supplied as cb\n"); + return; + } + mutex_lock(&main_info->man_mutex); + list_for_each_safe(cursor, next, &main_info->chip_handlers) { + tmp = list_entry(cursor, struct chip_handler_item, list); + if (tmp->cb.check_chip_support == cb->check_chip_support) { + list_del(cursor); + kfree(tmp); + break; + } + } + mutex_unlock(&main_info->man_mutex); +} +EXPORT_SYMBOL_GPL(cg2900_deregister_chip_driver); + +/** + * cg2900_register_trans_driver() - Register a transport driver. + * @dev: Transport device. + * + * Returns: + * 0 if there is no error. + * -EINVAL if NULL is supplied as @cb. + * -ENOMEM if allocation fails or work queue can't be created. + * -EACCES if work can't be queued. + */ +int cg2900_register_trans_driver(struct cg2900_chip_dev *dev) +{ + int err; + struct cg2900_platform_data *pf_data; + struct core_info *info; + + BUG_ON(!main_info); + + if (!dev || !dev->dev) { + dev_err(main_info->dev, "cg2900_register_trans_driver: " + "Received NULL pointer\n"); + return -EINVAL; + } + + dev_dbg(dev->dev, "cg2900_register_trans_driver\n"); + + if (!dev->t_cb.write) { + dev_err(dev->dev, "cg2900_register_trans_driver: Write function" + " missing\n"); + return -EINVAL; + } + + pf_data = dev_get_platdata(dev->dev); + if (!pf_data) { + dev_err(dev->dev, "cg2900_register_trans_driver: Missing " + "platform data\n"); + return -EINVAL; + } + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + dev_err(dev->dev, "Couldn't allocate info\n"); + return -ENOMEM; + } + + if (pf_data->init) { + err = pf_data->init(dev); + if (err) { + dev_err(dev->dev, "Platform init failed (%d)\n", err); + goto error_handling; + } + } + + info->chip_dev = dev; + dev->prv_data = info; + + info->wq = create_singlethread_workqueue(CORE_WQ_NAME); + if (!info->wq) { + dev_err(dev->dev, "Could not create workqueue\n"); + err = -ENOMEM; + goto error_handling_exit; + } + + dev_info(dev->dev, "Transport connected\n"); + + INIT_WORK(&info->work, work_hw_registered); + if (!queue_work(info->wq, &info->work)) { + dev_err(dev->dev, "Failed to queue work_hw_registered because " + "it's already in the queue\n"); + err = -EACCES; + goto error_handling_wq; + } + + return 0; + +error_handling_wq: + destroy_workqueue(info->wq); +error_handling_exit: + if (pf_data->exit) + pf_data->exit(dev); +error_handling: + kfree(info); + return err; +} +EXPORT_SYMBOL_GPL(cg2900_register_trans_driver); + +/** + * cg2900_deregister_trans_driver() - Deregister a transport driver. + * @dev: Transport device. + * + * Returns: + * 0 if there is no error. + * -EINVAL if NULL is supplied as @cb. + * -ENOMEM if allocation fails or work queue can't be created. + */ +int cg2900_deregister_trans_driver(struct cg2900_chip_dev *dev) +{ + struct cg2900_platform_data *pf_data; + struct core_info *info = dev->prv_data; + + BUG_ON(!main_info); + + dev_dbg(dev->dev, "cg2900_deregister_trans_driver\n"); + + if (dev->c_cb.chip_removed) + dev->c_cb.chip_removed(dev); + + destroy_workqueue(info->wq); + + dev->prv_data = NULL; + kfree(info); + + dev_info(dev->dev, "Transport disconnected\n"); + + pf_data = dev_get_platdata(dev->dev); + if (!pf_data) { + dev_err(dev->dev, "Missing platform data\n"); + return -EINVAL; + } + + if (pf_data->exit) + pf_data->exit(dev); + + return 0; +} +EXPORT_SYMBOL_GPL(cg2900_deregister_trans_driver); + +/** + * cg2900_get_sleep_timeout() - Return sleep timeout in jiffies. + * @check_sleep: If we want to check whether chip has gone + * to sleep then use lesser timeout value + * + * Returns: + * Sleep timeout in jiffies. 0 means that sleep timeout shall not be used. + */ +unsigned long cg2900_get_sleep_timeout(bool check_sleep) +{ + if (!sleep_timeout_ms) + return 0; + + if (check_sleep) + return msecs_to_jiffies(READY_FOR_SLEEP_TIMEOUT_MS); + else + return msecs_to_jiffies(sleep_timeout_ms); +} +EXPORT_SYMBOL_GPL(cg2900_get_sleep_timeout); + +/** + * cg2900_probe() - Initialize module. + * + * @pdev: Platform device. + * + * This function initialize the transport and CG2900 Core, then + * register to the transport framework. + * + * Returns: + * 0 if success. + * -ENOMEM for failed alloc or structure creation. + */ +static int __devinit cg2900_probe(struct platform_device *pdev) +{ + dev_dbg(&pdev->dev, "cg2900_probe\n"); + + main_info = kzalloc(sizeof(*main_info), GFP_KERNEL); + if (!main_info) { + dev_err(&pdev->dev, "Couldn't allocate main_info\n"); + return -ENOMEM; + } + + main_info->dev = &pdev->dev; + mutex_init(&main_info->man_mutex); + INIT_LIST_HEAD(&main_info->chip_handlers); + init_waitqueue_head(&main_info->wq); + + dev_info(&pdev->dev, "CG2900 Core driver started\n"); + + return 0; +} + +/** + * cg2900_remove() - Remove module. + * + * @pdev: Platform device. + * + * Returns: + * 0 if success. + * -ENOMEM if core_info does not exist. + * -EINVAL if platform data does not exist in the device. + */ +static int __devexit cg2900_remove(struct platform_device *pdev) +{ + dev_dbg(&pdev->dev, "cg2900_remove\n"); + + kfree(main_info); + main_info = NULL; + + dev_info(&pdev->dev, "CG2900 Core driver removed\n"); + + return 0; +} + +static struct platform_driver cg2900_driver = { + .driver = { + .name = "cg2900", + .owner = THIS_MODULE, + }, + .probe = cg2900_probe, + .remove = __devexit_p(cg2900_remove), +}; + +/** + * cg2900_init() - Initialize module. + * + * Registers platform driver. + */ +static int __init cg2900_init(void) +{ + pr_debug("cg2900_init"); + return platform_driver_register(&cg2900_driver); +} + +/** + * cg2900_exit() - Remove module. + * + * Unregisters platform driver. + */ +static void __exit cg2900_exit(void) +{ + pr_debug("cg2900_exit"); + platform_driver_unregister(&cg2900_driver); +} + +module_init(cg2900_init); +module_exit(cg2900_exit); + +module_param(sleep_timeout_ms, int, S_IRUGO | S_IWUSR | S_IWGRP); +MODULE_PARM_DESC(sleep_timeout_ms, + "Sleep timeout for data transmissions:\n" + "\tDefault 10000 ms\n" + "\t0 = disable\n" + "\t>0 = sleep timeout in milliseconds"); + +module_param_array(bd_address, byte, &bd_addr_count, + S_IRUGO | S_IWUSR | S_IWGRP); +MODULE_PARM_DESC(bd_address, + "Bluetooth Device address. " + "Default 0x00 0x80 0xDE 0xAD 0xBE 0xEF. " + "Enter as comma separated value."); + +MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Linux Bluetooth HCI H:4 CG2900 Connectivity Device Driver"); diff --git a/drivers/staging/cg2900/mfd/cg2900_core.h b/drivers/staging/cg2900/mfd/cg2900_core.h new file mode 100644 index 00000000000..a0615fa0e46 --- /dev/null +++ b/drivers/staging/cg2900/mfd/cg2900_core.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson. + * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson. + * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson. + * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Linux Bluetooth HCI H:4 Driver for ST-Ericsson CG2900 GPS/BT/FM controller. + */ + +#ifndef _CG2900_CORE_H_ +#define _CG2900_CORE_H_ + +#include <linux/device.h> +#include <linux/skbuff.h> + +/* Reserve 1 byte for the HCI H:4 header */ +#define HCI_H4_SIZE 1 +#define CG2900_SKB_RESERVE HCI_H4_SIZE + +/* Number of bytes to reserve at start of sk_buffer when receiving packet */ +#define RX_SKB_RESERVE 8 + +#define BT_BDADDR_SIZE 6 + +/* Standardized Bluetooth H:4 channels */ +#define HCI_BT_CMD_H4_CHANNEL 0x01 +#define HCI_BT_ACL_H4_CHANNEL 0x02 +#define HCI_BT_SCO_H4_CHANNEL 0x03 +#define HCI_BT_EVT_H4_CHANNEL 0x04 + +/* Default H4 channels which may change depending on connected controller */ +#define HCI_FM_RADIO_H4_CHANNEL 0x08 +#define HCI_GNSS_H4_CHANNEL 0x09 + +/* Bluetooth error codes */ +#define HCI_BT_ERROR_NO_ERROR 0x00 +#define HCI_BT_WRONG_SEQ_ERROR 0xF1 + +/* Bluetooth lengths */ +#define HCI_BT_SEND_FILE_MAX_CHUNK_SIZE 254 + +#define LOGGER_DIRECTION_TX 0 +#define LOGGER_DIRECTION_RX 1 + +/* module_param declared in cg2900_core.c */ +extern u8 bd_address[BT_BDADDR_SIZE]; + +#endif /* _CG2900_CORE_H_ */ diff --git a/drivers/staging/cg2900/mfd/cg2900_lib.c b/drivers/staging/cg2900/mfd/cg2900_lib.c new file mode 100644 index 00000000000..84f7eb5eb62 --- /dev/null +++ b/drivers/staging/cg2900/mfd/cg2900_lib.c @@ -0,0 +1,284 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Linux Bluetooth HCI H:4 Driver for ST-Ericsson CG2900 GPS/BT/FM controller. + */ +#define NAME "cg2900_lib" +#define pr_fmt(fmt) NAME ": " fmt "\n" + +#include <linux/kernel.h> +#include <linux/limits.h> +#include <linux/module.h> +#include <linux/skbuff.h> +#include <linux/types.h> + +#include "cg2900.h" +#include "cg2900_chip.h" +#include "cg2900_core.h" +#include "cg2900_lib.h" + +/* + * Max length in bytes for line buffer used to parse settings and patch file. + * Must be max length of name plus characters used to define chip version. + */ +#define LINE_BUFFER_LENGTH (NAME_MAX + 30) +#define LOGGER_HEADER_SIZE 1 +/** + * cg2900_tx_to_chip() - Transmit buffer to the transport. + * @user: User data for BT command channel. + * @logger: User data for logger channel. + * @skb: Data packet. + * + * The transmit_skb_to_chip() function transmit buffer to the transport. + * If enabled, copy the transmitted data to the HCI logger as well. + */ +void cg2900_tx_to_chip(struct cg2900_user_data *user, + struct cg2900_user_data *logger, struct sk_buff *skb) +{ + int err; + struct cg2900_chip_dev *chip_dev; + + dev_dbg(user->dev, "cg2900_tx_to_chip %d bytes.\n", skb->len); + + if (logger) + cg2900_send_to_hci_logger(logger, skb, LOGGER_DIRECTION_TX); + + chip_dev = cg2900_get_prv(user); + err = chip_dev->t_cb.write(chip_dev, skb); + if (err) { + dev_err(user->dev, "cg2900_tx_to_chip: Transport write failed " + "(%d)\n", err); + kfree_skb(skb); + } +} +EXPORT_SYMBOL_GPL(cg2900_tx_to_chip); + +/** + * cg2900_tx_no_user() - Transmit buffer to the transport. + * @dev: Current chip to transmit to. + * @skb: Data packet. + * + * This function transmits buffer to the transport when no user exist (system + * startup for example). + */ +void cg2900_tx_no_user(struct cg2900_chip_dev *dev, struct sk_buff *skb) +{ + int err; + + dev_dbg(dev->dev, "cg2900_tx_no_user %d bytes.\n", skb->len); + + err = dev->t_cb.write(dev, skb); + if (err) { + dev_err(dev->dev, "cg2900_tx_no_user: Transport write failed " + "(%d)\n", err); + kfree_skb(skb); + } +} +EXPORT_SYMBOL_GPL(cg2900_tx_no_user); + +/** + * create_and_send_bt_cmd() - Copy and send sk_buffer. + * @user: User data for current channel. + * @logger: User data for logger channel. + * @data: Data to send. + * @length: Length in bytes of data. + * + * The create_and_send_bt_cmd() function allocate sk_buffer, copy supplied data + * to it, and send the sk_buffer to controller. + */ +void cg2900_send_bt_cmd(struct cg2900_user_data *user, + struct cg2900_user_data *logger, + void *data, int length) +{ + struct sk_buff *skb; + + skb = user->alloc_skb(length, GFP_ATOMIC); + if (!skb) { + dev_err(user->dev, "cg2900_send_bt_cmd: Couldn't alloc " + "sk_buff with length %d\n", length); + return; + } + + memcpy(skb_put(skb, length), data, length); + skb_push(skb, HCI_H4_SIZE); + skb->data[0] = HCI_BT_CMD_H4_CHANNEL; + + cg2900_tx_to_chip(user, logger, skb); +} +EXPORT_SYMBOL_GPL(cg2900_send_bt_cmd); + +/** + * cg2900_send_bt_cmd_no_user() - Copy and send sk_buffer with no assigned user. + * @dev: Current chip to transmit to. + * @data: Data to send. + * @length: Length in bytes of data. + * + * The cg2900_send_bt_cmd_no_user() function allocate sk_buffer, copy supplied + * data to it, and send the sk_buffer to controller. + */ +void cg2900_send_bt_cmd_no_user(struct cg2900_chip_dev *dev, void *data, + int length) +{ + struct sk_buff *skb; + + skb = alloc_skb(length + HCI_H4_SIZE, GFP_KERNEL); + if (!skb) { + dev_err(dev->dev, "cg2900_send_bt_cmd_no_user: Couldn't alloc " + "sk_buff with length %d\n", length); + return; + } + + skb_reserve(skb, HCI_H4_SIZE); + memcpy(skb_put(skb, length), data, length); + skb_push(skb, HCI_H4_SIZE); + skb->data[0] = HCI_BT_CMD_H4_CHANNEL; + + cg2900_tx_no_user(dev, skb); +} +EXPORT_SYMBOL_GPL(cg2900_send_bt_cmd_no_user); + +/** + * create_work_item() - Create work item and add it to the work queue. + * @wq: Work queue. + * @work_func: Work function. + * @user_data: Arbitrary data set by user. + * + * The create_work_item() function creates work item and add it to + * the work queue. + * Note that work is allocated by kmalloc and work must be freed when work + * function is started. + */ +void cg2900_create_work_item(struct workqueue_struct *wq, work_func_t work_func, + void *user_data) +{ + struct cg2900_work *new_work; + int err; + + new_work = kmalloc(sizeof(*new_work), GFP_ATOMIC); + if (!new_work) { + pr_err("Failed to alloc memory for new_work"); + return; + } + + INIT_WORK(&new_work->work, work_func); + new_work->user_data = user_data; + + err = queue_work(wq, &new_work->work); + if (!err) { + pr_err("Failed to queue work_struct because it's already " + "in the queue"); + kfree(new_work); + } +} +EXPORT_SYMBOL_GPL(cg2900_create_work_item); + +/** + * read_and_send_file_part() - Transmit a part of the supplied file. + * @user: User data for current channel. + * @logger: User data for logger channel. + * @info: File information. + * + * The cg2900_read_and_send_file_part() function transmit a part of the supplied + * file to the controller. + * + * Returns: + * 0 if there is no more data in the file. + * >0 for number of bytes sent. + * -ENOMEM if skb allocation failed. + */ +int cg2900_read_and_send_file_part(struct cg2900_user_data *user, + struct cg2900_user_data *logger, + struct cg2900_file_info *info) +{ + int bytes_to_copy; + struct sk_buff *skb; + struct bt_vs_write_file_block_cmd *cmd; + int plen; + + /* + * Calculate number of bytes to copy; + * either max bytes for HCI packet or number of bytes left in file + */ + bytes_to_copy = min((int)HCI_BT_SEND_FILE_MAX_CHUNK_SIZE, + (int)(info->fw_file->size - info->file_offset)); + + if (bytes_to_copy <= 0) { + /* Nothing more to read in file. */ + dev_dbg(user->dev, "File download finished\n"); + info->chunk_id = 0; + info->file_offset = 0; + return 0; + } + + /* There is more data to send */ + plen = sizeof(*cmd) + bytes_to_copy; + skb = user->alloc_skb(plen, GFP_KERNEL); + if (!skb) { + dev_err(user->dev, "Couldn't allocate sk_buffer\n"); + return -ENOMEM; + } + + skb_put(skb, plen); + + cmd = (struct bt_vs_write_file_block_cmd *)skb->data; + cmd->opcode = cpu_to_le16(CG2900_BT_OP_VS_WRITE_FILE_BLOCK); + cmd->plen = BT_PARAM_LEN(plen); + cmd->id = info->chunk_id; + info->chunk_id++; + + /* Copy the data from offset position */ + memcpy(cmd->data, + &(info->fw_file->data[info->file_offset]), + bytes_to_copy); + + /* Increase offset with number of bytes copied */ + info->file_offset += bytes_to_copy; + + skb_push(skb, CG2900_SKB_RESERVE); + skb->data[0] = HCI_BT_CMD_H4_CHANNEL; + + cg2900_tx_to_chip(user, logger, skb); + + return bytes_to_copy; +} +EXPORT_SYMBOL_GPL(cg2900_read_and_send_file_part); + +void cg2900_send_to_hci_logger(struct cg2900_user_data *logger, + struct sk_buff *skb, + u8 direction) +{ + struct sk_buff *skb_log; + u8 *p; + + /* + * Alloc a new sk_buff and copy the data into it. Then send it to + * the HCI logger. + */ + skb_log = alloc_skb(skb->len + LOGGER_HEADER_SIZE, GFP_NOWAIT); + if (!skb_log) { + pr_err("cg2900_send_to_hci_logger:\ + Couldn't allocate skb_log\n"); + return; + } + /* Reserve 1 byte for direction.*/ + skb_reserve(skb_log, LOGGER_HEADER_SIZE); + + memcpy(skb_put(skb_log, skb->len), skb->data, skb->len); + p = skb_push(skb_log, LOGGER_HEADER_SIZE); + *p = (u8) direction; + + if (logger->read_cb) + logger->read_cb(logger, skb_log); + else + kfree_skb(skb_log); + + return; +} +EXPORT_SYMBOL_GPL(cg2900_send_to_hci_logger); + +MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Linux CG2900 Library functions"); diff --git a/drivers/staging/cg2900/mfd/cg2900_lib.h b/drivers/staging/cg2900/mfd/cg2900_lib.h new file mode 100644 index 00000000000..99d5ce6cfdb --- /dev/null +++ b/drivers/staging/cg2900/mfd/cg2900_lib.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Linux Bluetooth HCI H:4 Driver for ST-Ericsson CG2900 GPS/BT/FM controller. + */ + +#ifndef _CG2900_LIB_H_ +#define _CG2900_LIB_H_ + +#include <linux/firmware.h> +#include <linux/skbuff.h> +#include <linux/workqueue.h> + +#include "cg2900.h" + +/** + * struct cg2900_work - Generic work structure. + * @work: Work structure. + * @user_data: Arbitrary data set by user. + */ +struct cg2900_work { + struct work_struct work; + void *user_data; +}; + +/** + * struct cg2900_file_info - Info structure for file to download. + * @fw_file: Stores firmware file. + * @file_offset: Current read offset in firmware file. + * @chunk_id: Stores current chunk ID of write file + * operations. + */ +struct cg2900_file_info { + const struct firmware *fw_file; + int file_offset; + u8 chunk_id; +}; + +extern void cg2900_tx_to_chip(struct cg2900_user_data *user, + struct cg2900_user_data *logger, + struct sk_buff *skb); +extern void cg2900_tx_no_user(struct cg2900_chip_dev *dev, struct sk_buff *skb); +extern void cg2900_send_bt_cmd(struct cg2900_user_data *user, + struct cg2900_user_data *logger, + void *data, int length); +extern void cg2900_send_bt_cmd_no_user(struct cg2900_chip_dev *dev, void *data, + int length); +extern void cg2900_create_work_item(struct workqueue_struct *wq, + work_func_t work_func, + void *user_data); +extern int cg2900_read_and_send_file_part(struct cg2900_user_data *user, + struct cg2900_user_data *logger, + struct cg2900_file_info *info); +extern void cg2900_send_to_hci_logger(struct cg2900_user_data *logger, + struct sk_buff *skb, + u8 direction); + +#endif /* _CG2900_LIB_H_ */ diff --git a/drivers/staging/cg2900/mfd/cg2900_test.c b/drivers/staging/cg2900/mfd/cg2900_test.c new file mode 100644 index 00000000000..58ac6166af6 --- /dev/null +++ b/drivers/staging/cg2900/mfd/cg2900_test.c @@ -0,0 +1,402 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Driver for ST-Ericsson CG2900 test character device. + */ +#define NAME "cg2900_test" +#define pr_fmt(fmt) NAME ": " fmt "\n" + +#include <asm/byteorder.h> +#include <linux/firmware.h> +#include <linux/fs.h> +#include <linux/gfp.h> +#include <linux/init.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/wait.h> + +#include "cg2900.h" +#include "cg2900_core.h" + +#define MISC_DEV (info->misc_dev.this_device) + +/* Device names */ +#define CG2900_CDEV_NAME "cg2900_core_test" + +/** + * struct test_info - Main info structure for CG2900 test char device. + * @misc_dev: Registered Misc Device. + * @rx_queue: RX data queue. + * @dev: Device structure for STE Connectivity driver. + * @pdev: Platform device structure for STE Connectivity driver. + */ +struct test_info { + struct miscdevice misc_dev; + struct sk_buff_head rx_queue; + struct device *dev; + struct platform_device *pdev; +}; + +static struct test_info *test_info; + +/* + * main_wait_queue - Char device Wait Queue in CG2900 Core. + */ +static DECLARE_WAIT_QUEUE_HEAD(char_wait_queue); + +/** + * tx_to_char_dev() - Handle data received from CG2900 Core. + * @dev: Current chip device information. + * @skb: Buffer with data coming form device. + */ +static int tx_to_char_dev(struct cg2900_chip_dev *dev, struct sk_buff *skb) +{ + struct test_info *info = dev->t_data; + skb_queue_tail(&info->rx_queue, skb); + wake_up_interruptible_all(&char_wait_queue); + return 0; +} + +/** + * cg2900_test_open() - User space char device has been opened. + * @inode: Device driver information. + * @filp: Pointer to the file struct. + * + * Returns: + * 0 if there is no error. + * -EACCES if transport already exists. + * -ENOMEM if allocation fails. + * Errors from create_work_item. + */ +static int cg2900_test_open(struct inode *inode, struct file *filp) +{ + struct test_info *info = test_info; + struct cg2900_chip_dev *dev; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) { + dev_err(MISC_DEV, "Cannot allocate test_dev\n"); + return -ENOMEM; + } + dev->dev = info->dev; + dev->pdev = info->pdev; + dev->t_data = info; + dev->t_cb.write = tx_to_char_dev; + filp->private_data = dev; + + dev_info(MISC_DEV, "CG2900 test char dev opened\n"); + return cg2900_register_trans_driver(dev); +} + +/** + * cg2900_test_release() - User space char device has been closed. + * @inode: Device driver information. + * @filp: Pointer to the file struct. + * + * Returns: + * 0 if there is no error. + */ +static int cg2900_test_release(struct inode *inode, struct file *filp) +{ + struct cg2900_chip_dev *dev = filp->private_data; + struct test_info *info = dev->t_data; + + dev_info(MISC_DEV, "CG2900 test char dev closed\n"); + skb_queue_purge(&info->rx_queue); + cg2900_deregister_trans_driver(dev); + kfree(dev); + + return 0; +} + +/** + * cg2900_test_read() - Queue and copy buffer to user space char device. + * @filp: Pointer to the file struct. + * @buf: Received buffer. + * @count: Count of received data in bytes. + * @f_pos: Position in buffer. + * + * Returns: + * >= 0 is number of bytes read. + * -EFAULT if copy_to_user fails. + */ +static ssize_t cg2900_test_read(struct file *filp, char __user *buf, + size_t count, loff_t *f_pos) +{ + struct sk_buff *skb; + int bytes_to_copy; + int err; + struct cg2900_chip_dev *dev = filp->private_data; + struct test_info *info = dev->t_data; + struct sk_buff_head *rx_queue = &info->rx_queue; + + dev_dbg(MISC_DEV, "cg2900_test_read count %d\n", count); + + if (skb_queue_empty(rx_queue)) + wait_event_interruptible(char_wait_queue, + !(skb_queue_empty(rx_queue))); + + skb = skb_dequeue(rx_queue); + if (!skb) { + dev_dbg(MISC_DEV, + "skb queue is empty - return with zero bytes\n"); + bytes_to_copy = 0; + goto finished; + } + + bytes_to_copy = min(count, skb->len); + err = copy_to_user(buf, skb->data, bytes_to_copy); + if (err) { + skb_queue_head(rx_queue, skb); + return -EFAULT; + } + + skb_pull(skb, bytes_to_copy); + + if (skb->len > 0) + skb_queue_head(rx_queue, skb); + else + kfree_skb(skb); + +finished: + return bytes_to_copy; +} + +/** + * cg2900_test_write() - Copy buffer from user and write to CG2900 Core. + * @filp: Pointer to the file struct. + * @buf: Read buffer. + * @count: Size of the buffer write. + * @f_pos: Position in buffer. + * + * Returns: + * >= 0 is number of bytes written. + * -EFAULT if copy_from_user fails. + */ +static ssize_t cg2900_test_write(struct file *filp, const char __user *buf, + size_t count, loff_t *f_pos) +{ + struct sk_buff *skb; + struct cg2900_chip_dev *dev = filp->private_data; + struct test_info *info = dev->t_data; + + dev_dbg(MISC_DEV, "cg2900_test_write count %d\n", count); + + /* Allocate the SKB and reserve space for the header */ + skb = alloc_skb(count + RX_SKB_RESERVE, GFP_KERNEL); + if (!skb) { + dev_err(MISC_DEV, "cg2900_test_write: Failed to alloc skb\n"); + return -ENOMEM; + } + skb_reserve(skb, RX_SKB_RESERVE); + + if (copy_from_user(skb_put(skb, count), buf, count)) { + kfree_skb(skb); + return -EFAULT; + } + + dev->c_cb.data_from_chip(dev, skb); + + return count; +} + +/** + * cg2900_test_poll() - Handle POLL call to the interface. + * @filp: Pointer to the file struct. + * @wait: Poll table supplied to caller. + * + * Returns: + * Mask of current set POLL values (0 or (POLLIN | POLLRDNORM)) + */ +static unsigned int cg2900_test_poll(struct file *filp, poll_table *wait) +{ + struct cg2900_chip_dev *dev = filp->private_data; + struct test_info *info = dev->t_data; + unsigned int mask = 0; + + poll_wait(filp, &char_wait_queue, wait); + + if (!(skb_queue_empty(&info->rx_queue))) + mask |= POLLIN | POLLRDNORM; + + return mask; +} + +static const struct file_operations test_char_dev_fops = { + .open = cg2900_test_open, + .release = cg2900_test_release, + .read = cg2900_test_read, + .write = cg2900_test_write, + .poll = cg2900_test_poll +}; + +/** + * test_char_dev_create() - Create a char device for testing. + * @info: Test device info. + * + * Creates a separate char device that will interact directly with userspace + * test application. + * + * Returns: + * 0 if there is no error. + * Error codes from misc_register. + */ +static int test_char_dev_create(struct test_info *info) +{ + int err; + + /* Initialize the RX queue */ + skb_queue_head_init(&info->rx_queue); + + /* Prepare miscdevice struct before registering the device */ + info->misc_dev.minor = MISC_DYNAMIC_MINOR; + info->misc_dev.name = CG2900_CDEV_NAME; + info->misc_dev.fops = &test_char_dev_fops; + info->misc_dev.parent = info->dev; + info->misc_dev.mode = S_IRUGO | S_IWUGO; + + err = misc_register(&info->misc_dev); + if (err) { + dev_err(info->dev, "Error %d registering misc dev", err); + return err; + } + + return 0; +} + +/** + * test_char_dev_destroy() - Clean up after test_char_dev_create(). + * @info: Test device info. + */ +static void test_char_dev_destroy(struct test_info *info) +{ + int err; + + err = misc_deregister(&info->misc_dev); + if (err) + dev_err(info->dev, "Error %d deregistering misc dev\n", err); + + /* Clean the message queue */ + skb_queue_purge(&info->rx_queue); +} + +/** + * cg2900_test_probe() - Initialize module. + * + * @pdev: Platform device. + * + * This function initializes and registers the test misc char device. + * + * Returns: + * 0 if success. + * -ENOMEM for failed alloc or structure creation. + * -EEXIST if device already exists. + * Error codes generated by test_char_dev_create. + */ +static int __devinit cg2900_test_probe(struct platform_device *pdev) +{ + int err; + + dev_dbg(&pdev->dev, "cg2900_test_probe\n"); + + if (test_info) { + dev_err(&pdev->dev, "test_info exists\n"); + return -EEXIST; + } + + test_info = kzalloc(sizeof(*test_info), GFP_KERNEL); + if (!test_info) { + dev_err(&pdev->dev, "Couldn't allocate test_info\n"); + return -ENOMEM; + } + + test_info->dev = &pdev->dev; + test_info->pdev = pdev; + + /* Create and add test char device. */ + err = test_char_dev_create(test_info); + if (err) { + kfree(test_info); + test_info = NULL; + return err; + } + + dev_set_drvdata(&pdev->dev, test_info); + + dev_info(&pdev->dev, "CG2900 test char device driver started\n"); + + return 0; +} + +/** + * cg2900_test_remove() - Remove module. + * + * @pdev: Platform device. + * + * Returns: + * 0 if success. + * -ENOMEM if core_info does not exist. + * -EINVAL if platform data does not exist in the device. + */ +static int __devexit cg2900_test_remove(struct platform_device *pdev) +{ + struct test_info *test_info; + + dev_dbg(&pdev->dev, "cg2900_test_remove\n"); + test_info = dev_get_drvdata(&pdev->dev); + test_char_dev_destroy(test_info); + dev_set_drvdata(&pdev->dev, NULL); + kfree(test_info); + test_info = NULL; + dev_info(&pdev->dev, "CG2900 Test char device driver removed\n"); + return 0; +} + +static struct platform_driver cg2900_test_driver = { + .driver = { + .name = "cg2900-test", + .owner = THIS_MODULE, + }, + .probe = cg2900_test_probe, + .remove = __devexit_p(cg2900_test_remove), +}; + +/** + * cg2900_test_init() - Initialize module. + * + * Registers platform driver. + */ +static int __init cg2900_test_init(void) +{ + pr_debug("cg2900_test_init"); + return platform_driver_register(&cg2900_test_driver); +} + +/** + * cg2900_test_exit() - Remove module. + * + * Unregisters platform driver. + */ +static void __exit cg2900_test_exit(void) +{ + pr_debug("cg2900_test_exit"); + platform_driver_unregister(&cg2900_test_driver); +} + +module_init(cg2900_test_init); +module_exit(cg2900_test_exit); + +MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Linux CG2900 Test Char Device Driver"); diff --git a/drivers/staging/cg2900/mfd/stlc2690_chip.c b/drivers/staging/cg2900/mfd/stlc2690_chip.c new file mode 100644 index 00000000000..84de0d7a976 --- /dev/null +++ b/drivers/staging/cg2900/mfd/stlc2690_chip.c @@ -0,0 +1,1671 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson. + * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson. + * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson. + * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Linux Bluetooth HCI H:4 Driver for ST-Ericsson STLC2690 BT/FM controller. + */ +#define NAME "stlc2690_chip" +#define pr_fmt(fmt) NAME ": " fmt "\n" + +#include <asm/byteorder.h> +#include <linux/device.h> +#include <linux/firmware.h> +#include <linux/gfp.h> +#include <linux/init.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/limits.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/sched.h> +#include <linux/skbuff.h> +#include <linux/stat.h> +#include <linux/time.h> +#include <linux/timer.h> +#include <linux/types.h> +#include <linux/wait.h> +#include <linux/workqueue.h> +#include <linux/mfd/core.h> +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci.h> + +#include "cg2900.h" +#include "cg2900_core.h" +#include "cg2900_lib.h" +#include "stlc2690_chip.h" + +#ifndef MAX +#define MAX(x, y) (((x) > (y)) ? (x) : (y)) +#endif + +#define MAIN_DEV (main_info->dev) +#define BOOT_DEV (info->user_in_charge->dev) + +#define WQ_NAME "stlc2690_chip_wq" + +#define LINE_TOGGLE_DETECT_TIMEOUT 50 /* ms */ +#define CHIP_READY_TIMEOUT 100 /* ms */ +#define CHIP_STARTUP_TIMEOUT 15000 /* ms */ +#define CHIP_SHUTDOWN_TIMEOUT 15000 /* ms */ + +/** CHANNEL_BT_CMD - Bluetooth HCI H:4 channel + * for Bluetooth commands in the ST-Ericsson connectivity controller. + */ +#define CHANNEL_BT_CMD 0x01 + +/** CHANNEL_BT_ACL - Bluetooth HCI H:4 channel + * for Bluetooth ACL data in the ST-Ericsson connectivity controller. + */ +#define CHANNEL_BT_ACL 0x02 + +/** CHANNEL_BT_EVT - Bluetooth HCI H:4 channel + * for Bluetooth events in the ST-Ericsson connectivity controller. + */ +#define CHANNEL_BT_EVT 0x04 + +/** CHANNEL_HCI_LOGGER - Bluetooth HCI H:4 channel + * for logging all transmitted H4 packets (on all channels). + */ +#define CHANNEL_HCI_LOGGER 0xFA + +/** CHANNEL_CORE - Bluetooth HCI H:4 channel + * for user space control of the ST-Ericsson connectivity controller. + */ +#define CHANNEL_CORE 0xFD + +/* + * For the char dev names we keep the same names in order to be able to reuse + * the users and to keep a consistent interface. + */ + +/** STLC2690_BT_CMD - Bluetooth HCI H4 channel for Bluetooth commands. + */ +#define STLC2690_BT_CMD "cg2900_bt_cmd" + +/** STLC2690_BT_ACL - Bluetooth HCI H4 channel for Bluetooth ACL data. + */ +#define STLC2690_BT_ACL "cg2900_bt_acl" + +/** STLC2690_BT_EVT - Bluetooth HCI H4 channel for Bluetooth events. + */ +#define STLC2690_BT_EVT "cg2900_bt_evt" + +/** STLC2690_HCI_LOGGER - BT channel for logging all transmitted H4 packets. + * Data read is copy of all data transferred on the other channels. + * Only write allowed is configuration of the HCI Logger. + */ +#define STLC2690_HCI_LOGGER "cg2900_hci_logger" + +/** STLC2690_CORE- Channel for keeping ST-Ericsson STLC2690 enabled. + * Opening this channel forces the chip to stay powered. + * No data can be written to or read from this channel. + */ +#define STLC2690_CORE "cg2900_core" + +/** + * enum main_state - Main-state for STLC2690 driver. + * @STLC2690_INIT: STLC2690 initializing. + * @STLC2690_IDLE: No user registered to STLC2690 driver. + * @STLC2690_BOOTING: STLC2690 booting after first user is registered. + * @STLC2690_CLOSING: STLC2690 closing after last user has deregistered. + * @STLC2690_RESETING: STLC2690 reset requested. + * @STLC2690_ACTIVE: STLC2690 up and running with at least one user. + */ +enum main_state { + STLC2690_INIT, + STLC2690_IDLE, + STLC2690_BOOTING, + STLC2690_CLOSING, + STLC2690_RESETING, + STLC2690_ACTIVE +}; + +/** + * enum boot_state - BOOT-state for STLC2690 chip driver. + * @BOOT_RESET: HCI Reset has been sent. + * @BOOT_SEND_BD_ADDRESS: VS Store In FS command with BD address + * has been sent. + * @BOOT_GET_FILES_TO_LOAD: STLC2690 chip driver is retrieving file + * to load. + * @BOOT_DOWNLOAD_PATCH: STLC2690 chip driver is downloading + * patches. + * @BOOT_ACTIVATE_PATCHES_AND_SETTINGS: STLC2690 chip driver is activating + * patches and settings. + * @BOOT_READY: STLC2690 chip driver boot is ready. + * @BOOT_FAILED: STLC2690 chip driver boot failed. + */ +enum boot_state { + BOOT_RESET, + BOOT_SEND_BD_ADDRESS, + BOOT_GET_FILES_TO_LOAD, + BOOT_DOWNLOAD_PATCH, + BOOT_ACTIVATE_PATCHES_AND_SETTINGS, + BOOT_READY, + BOOT_FAILED +}; + +/** + * enum file_load_state - BOOT_FILE_LOAD-state for STLC2690 chip driver. + * @FILE_LOAD_GET_PATCH: Loading patches. + * @FILE_LOAD_GET_STATIC_SETTINGS: Loading static settings. + * @FILE_LOAD_NO_MORE_FILES: No more files to load. + * @FILE_LOAD_FAILED: File loading failed. + */ +enum file_load_state { + FILE_LOAD_GET_PATCH, + FILE_LOAD_GET_STATIC_SETTINGS, + FILE_LOAD_NO_MORE_FILES, + FILE_LOAD_FAILED +}; + +/** + * enum download_state - BOOT_DOWNLOAD state. + * @DOWNLOAD_PENDING: Download in progress. + * @DOWNLOAD_SUCCESS: Download successfully finished. + * @DOWNLOAD_FAILED: Downloading failed. + */ +enum download_state { + DOWNLOAD_PENDING, + DOWNLOAD_SUCCESS, + DOWNLOAD_FAILED +}; + + +/** + * struct stlc2690_channel_item - List object for channel. + * @list: list_head struct. + * @user: User for this channel. + */ +struct stlc2690_channel_item { + struct list_head list; + struct cg2900_user_data *user; +}; + +/** + * struct stlc2690_skb_data - Structure for storing private data in an sk_buffer. + * @dev: STLC2690 device for this sk_buffer. + */ +struct stlc2690_skb_data { + struct cg2900_user_data *user; +}; +#define stlc2690_skb_data(__skb) ((struct stlc2690_skb_data *)((__skb)->cb)) + +/** + * struct stlc2690_chip_info - Main info structure for STLC2690 chip driver. + * @patch_file_name: Stores patch file name. + * @settings_file_name: Stores settings file name. + * @file_info: Firmware file info (patch or settings). + * @main_state: Current MAIN-state of STLC2690 chip driver. + * @boot_state: Current BOOT-state of STLC2690 chip driver. + * @file_load_state: Current BOOT_FILE_LOAD-state of STLC2690 chip + * driver. + * @download_state: Current BOOT_DOWNLOAD-state of STLC2690 chip + * driver. + * @wq: STLC2690 chip driver workqueue. + * @chip_dev: Chip handler info. + * @user_in_charge: User currently operating. Normally used at + * channel open and close. + * @last_user: Last user of this chip. + * @logger: Logger user of this chip. + * @startup: True if system is starting up. + * @mfd_size: Number of MFD cells. + * @mfd_char_size: Number of MFD char device cells. + */ +struct stlc2690_chip_info { + char *patch_file_name; + char *settings_file_name; + struct cg2900_file_info file_info; + enum main_state main_state; + enum boot_state boot_state; + enum file_load_state file_load_state; + enum download_state download_state; + struct workqueue_struct *wq; + struct cg2900_chip_dev *chip_dev; + spinlock_t rw_lock; + struct list_head open_channels; + struct cg2900_user_data *user_in_charge; + struct cg2900_user_data *last_user; + struct cg2900_user_data *logger; + bool startup; + int mfd_size; + int mfd_char_size; +}; + +/** + * struct main_info - Main info structure for STLC2690 chip driver. + * @dev: Device structure. + * @cell_base_id: Base ID for MFD cells. + * @man_mutex: Management mutex. + */ +struct main_info { + struct device *dev; + int cell_base_id; + struct mutex man_mutex; +}; + +static struct main_info *main_info; + +/* + * main_wait_queue - Main Wait Queue in STLC2690 driver. + */ +static DECLARE_WAIT_QUEUE_HEAD(main_wait_queue); + +static struct mfd_cell stlc2690_devs[]; +static struct mfd_cell stlc2690_char_devs[]; + +static void chip_startup_finished(struct stlc2690_chip_info *info, int err); + +/** + * send_bd_address() - Send HCI VS command with BD address to the chip. + */ +static void send_bd_address(struct stlc2690_chip_info *info) +{ + struct bt_vs_store_in_fs_cmd *cmd; + u8 plen = sizeof(*cmd) + BT_BDADDR_SIZE; + + cmd = kmalloc(plen, GFP_KERNEL); + if (!cmd) + return; + + cmd->opcode = cpu_to_le16(STLC2690_BT_OP_VS_STORE_IN_FS); + cmd->plen = BT_PARAM_LEN(plen); + cmd->user_id = STLC2690_VS_STORE_IN_FS_USR_ID_BD_ADDR; + cmd->len = BT_BDADDR_SIZE; + /* Now copy the BD address received from user space control app. */ + memcpy(cmd->data, bd_address, BT_BDADDR_SIZE); + + dev_dbg(BOOT_DEV, "New boot_state: BOOT_SEND_BD_ADDRESS\n"); + info->boot_state = BOOT_SEND_BD_ADDRESS; + + cg2900_send_bt_cmd(info->user_in_charge, info->logger, cmd, plen); + + kfree(cmd); +} + +/** + * send_settings_file() - Transmit settings file. + * + * The send_settings_file() function transmit settings file. + * The file is read in parts to fit in HCI packets. When finished, + * close the settings file and send HCI reset to activate settings and patches. + */ +static void send_settings_file(struct stlc2690_chip_info *info) +{ + int bytes_sent; + + bytes_sent = cg2900_read_and_send_file_part(info->user_in_charge, + info->logger, + &info->file_info); + if (bytes_sent > 0) { + /* Data sent. Wait for CmdComplete */ + return; + } else if (bytes_sent < 0) { + dev_err(BOOT_DEV, "send_settings_file: Error %d occurred\n", + bytes_sent); + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + chip_startup_finished(info, bytes_sent); + return; + } + + /* No data was sent. This file is finished */ + info->download_state = DOWNLOAD_SUCCESS; + + /* Settings file finished. Release used resources */ + dev_dbg(BOOT_DEV, "Settings file finished, release used resources\n"); + release_firmware(info->file_info.fw_file); + info->file_info.fw_file = NULL; + + dev_dbg(BOOT_DEV, "New file_load_state: FILE_LOAD_NO_MORE_FILES\n"); + info->file_load_state = FILE_LOAD_NO_MORE_FILES; + + /* Create and send HCI VS Store In FS command with bd address. */ + send_bd_address(info); +} + +/** + * send_patch_file - Transmit patch file. + * + * The send_patch_file() function transmit patch file. + * The file is read in parts to fit in HCI packets. When the complete file is + * transmitted, the file is closed. + * When finished, continue with settings file. + */ +static void send_patch_file(struct cg2900_chip_dev *dev) +{ + int err; + int bytes_sent; + struct stlc2690_chip_info *info = dev->c_data; + int file_name_size = strlen("STLC2690_XXXX_XXXX_settings.fw"); + + bytes_sent = cg2900_read_and_send_file_part(info->user_in_charge, + info->logger, + &info->file_info); + if (bytes_sent > 0) { + /* Data sent. Wait for CmdComplete */ + return; + } else if (bytes_sent < 0) { + dev_err(BOOT_DEV, "send_patch_file: Error %d occurred\n", + bytes_sent); + err = bytes_sent; + goto error_handling; + } + + /* No data was sent. This file is finished */ + info->download_state = DOWNLOAD_SUCCESS; + + dev_dbg(BOOT_DEV, "Patch file finished, release used resources\n"); + release_firmware(info->file_info.fw_file); + info->file_info.fw_file = NULL; + + /* + * Create the settings file name from HCI revision and sub_version. + * file_name_size does not include terminating NULL character + * so add 1. + */ + err = snprintf(info->settings_file_name, file_name_size + 1, + "STLC2690_%04X_%04X_settings.fw", + dev->chip.hci_revision, dev->chip.hci_sub_version); + if (err == file_name_size) { + dev_dbg(BOOT_DEV, "Downloading settings file %s\n", + info->settings_file_name); + } else { + dev_err(BOOT_DEV, "Settings file name failed! err=%d\n", err); + goto error_handling; + } + + /* Retrieve the settings file */ + err = request_firmware(&info->file_info.fw_file, + info->settings_file_name, + info->chip_dev->dev); + if (err) { + dev_err(BOOT_DEV, "Couldn't get settings file (%d)\n", err); + goto error_handling; + } + /* Now send the settings file */ + dev_dbg(BOOT_DEV, + "New file_load_state: FILE_LOAD_GET_STATIC_SETTINGS\n"); + info->file_load_state = FILE_LOAD_GET_STATIC_SETTINGS; + dev_dbg(BOOT_DEV, "New download_state: DOWNLOAD_PENDING\n"); + info->download_state = DOWNLOAD_PENDING; + send_settings_file(info); + return; + +error_handling: + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + chip_startup_finished(info, err); +} + +/** + * work_reset_after_error() - Handle reset. + * @work: Reference to work data. + * + * Handle a reset after received Command Complete event. + */ +static void work_reset_after_error(struct work_struct *work) +{ + struct cg2900_work *my_work; + struct cg2900_chip_dev *dev; + struct stlc2690_chip_info *info; + + if (!work) { + dev_err(MAIN_DEV, "work_reset_after_error: work == NULL\n"); + return; + } + + my_work = container_of(work, struct cg2900_work, work); + dev = my_work->user_data; + info = dev->c_data; + + chip_startup_finished(info, -EIO); + + kfree(my_work); +} + +/** + * work_load_patch_and_settings() - Start loading patches and settings. + * @work: Reference to work data. + */ +static void work_load_patch_and_settings(struct work_struct *work) +{ + int err = 0; + struct cg2900_work *my_work; + struct cg2900_chip_dev *dev; + struct stlc2690_chip_info *info; + int file_name_size = strlen("STLC2690_XXXX_XXXX_patch.fw"); + + if (!work) { + dev_err(MAIN_DEV, + "work_load_patch_and_settings: work == NULL\n"); + return; + } + + my_work = container_of(work, struct cg2900_work, work); + dev = my_work->user_data; + info = dev->c_data; + + /* Check that we are in the right state */ + if (info->boot_state != BOOT_GET_FILES_TO_LOAD) + goto finished; + + /* + * Create the patch file name from HCI revision and sub_version. + * file_name_size does not include terminating NULL character + * so add 1. + */ + err = snprintf(info->patch_file_name, file_name_size + 1, + "STLC2690_%04X_%04X_patch.fw", dev->chip.hci_revision, + dev->chip.hci_sub_version); + if (err == file_name_size) { + dev_dbg(BOOT_DEV, "Downloading patch file %s\n", + info->patch_file_name); + } else { + dev_err(BOOT_DEV, "Patch file name failed! err=%d\n", err); + goto error_handling; + } + + /* We now all info needed */ + dev_dbg(BOOT_DEV, "New boot_state: BOOT_DOWNLOAD_PATCH\n"); + info->boot_state = BOOT_DOWNLOAD_PATCH; + dev_dbg(BOOT_DEV, "New download_state: DOWNLOAD_PENDING\n"); + info->download_state = DOWNLOAD_PENDING; + dev_dbg(BOOT_DEV, "New file_load_state: FILE_LOAD_GET_PATCH\n"); + info->file_load_state = FILE_LOAD_GET_PATCH; + info->file_info.chunk_id = 0; + info->file_info.file_offset = 0; + info->file_info.fw_file = NULL; + + /* OK. Now it is time to download the patches */ + err = request_firmware(&(info->file_info.fw_file), + info->patch_file_name, + dev->dev); + if (err < 0) { + dev_err(BOOT_DEV, "Couldn't get patch file (%d)\n", err); + goto error_handling; + } + send_patch_file(dev); + + goto finished; + +error_handling: + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + chip_startup_finished(info, -EIO); +finished: + kfree(my_work); +} + +/** + * work_cont_file_download() - A file block has been written. + * @work: Reference to work data. + * + * Handle a received HCI VS Write File Block Complete event. + * Normally this means continue to send files to the controller. + */ +static void work_cont_file_download(struct work_struct *work) +{ + struct cg2900_work *my_work; + struct cg2900_chip_dev *dev; + struct stlc2690_chip_info *info; + + if (!work) { + dev_err(MAIN_DEV, "work_cont_file_download: work == NULL\n"); + return; + } + + my_work = container_of(work, struct cg2900_work, work); + dev = my_work->user_data; + info = dev->c_data; + + /* Continue to send patches or settings to the controller */ + if (info->file_load_state == FILE_LOAD_GET_PATCH) + send_patch_file(dev); + else if (info->file_load_state == FILE_LOAD_GET_STATIC_SETTINGS) + send_settings_file(info); + else + dev_dbg(BOOT_DEV, "No more files to load\n"); + + kfree(my_work); +} + +/** + * handle_reset_cmd_complete() - Handles HCI Reset Command Complete event. + * @data: Pointer to received HCI data packet. + * + * Returns: + * true, if packet was handled internally, + * false, otherwise. + */ +static bool handle_reset_cmd_complete(struct cg2900_chip_dev *dev, u8 *data) +{ + u8 status = data[0]; + struct stlc2690_chip_info *info = dev->c_data; + + dev_dbg(BOOT_DEV, "Received Reset complete event with status 0x%X\n", + status); + + if (BOOT_RESET != info->boot_state && + BOOT_ACTIVATE_PATCHES_AND_SETTINGS != info->boot_state) + return false; + + if (HCI_BT_ERROR_NO_ERROR != status) { + dev_err(BOOT_DEV, "Command complete for HciReset received with " + "error 0x%X\n", status); + cg2900_create_work_item(info->wq, work_reset_after_error, dev); + return true; + } + + if (BOOT_RESET == info->boot_state) { + info->boot_state = BOOT_GET_FILES_TO_LOAD; + cg2900_create_work_item(info->wq, work_load_patch_and_settings, + dev); + } else { + /* + * The boot sequence is now finished successfully. + * Set states and signal to waiting thread. + */ + dev_dbg(BOOT_DEV, "New boot_state: BOOT_READY\n"); + info->boot_state = BOOT_READY; + chip_startup_finished(info, 0); + } + + return true; +} + + +/** + * handle_vs_store_in_fs_cmd_complete() - Handles HCI VS StoreInFS Command Complete event. + * @data: Pointer to received HCI data packet. + * + * Returns: + * true, if packet was handled internally, + * false, otherwise. + */ +static bool handle_vs_store_in_fs_cmd_complete(struct cg2900_chip_dev *dev, + u8 *data) +{ + u8 status = data[0]; + struct stlc2690_chip_info *info = dev->c_data; + + dev_dbg(BOOT_DEV, + "Received Store_in_FS complete event with status 0x%X\n", + status); + + if (info->boot_state != BOOT_SEND_BD_ADDRESS) + return false; + + if (HCI_BT_ERROR_NO_ERROR == status) { + struct hci_command_hdr cmd; + + /* Send HCI Reset command to activate patches */ + dev_dbg(BOOT_DEV, + "New boot_state: BOOT_ACTIVATE_PATCHES_AND_SETTINGS\n"); + info->boot_state = BOOT_ACTIVATE_PATCHES_AND_SETTINGS; + + cmd.opcode = cpu_to_le16(HCI_OP_RESET); + cmd.plen = 0; /* No parameters for Reset */ + cg2900_send_bt_cmd(info->user_in_charge, info->logger, &cmd, + sizeof(cmd)); + } else { + dev_err(BOOT_DEV, + "Command complete for StoreInFS received with error " + "0x%X\n", status); + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + cg2900_create_work_item(info->wq, work_reset_after_error, dev); + } + + return true; +} + +/** + * handle_vs_write_file_block_cmd_complete() - Handles HCI VS WriteFileBlock Command Complete event. + * @data: Pointer to received HCI data packet. + * + * Returns: + * true, if packet was handled internally, + * false, otherwise. + */ +static bool handle_vs_write_file_block_cmd_complete(struct cg2900_chip_dev *dev, + u8 *data) +{ + u8 status = data[0]; + struct stlc2690_chip_info *info = dev->c_data; + + if (info->boot_state != BOOT_DOWNLOAD_PATCH || + info->download_state != DOWNLOAD_PENDING) + return false; + + if (HCI_BT_ERROR_NO_ERROR == status) + cg2900_create_work_item(info->wq, work_cont_file_download, dev); + else { + dev_err(BOOT_DEV, + "Command complete for WriteFileBlock received with" + " error 0x%X\n", status); + dev_dbg(BOOT_DEV, "New download_state: DOWNLOAD_FAILED\n"); + info->download_state = DOWNLOAD_FAILED; + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + if (info->file_info.fw_file) { + release_firmware(info->file_info.fw_file); + info->file_info.fw_file = NULL; + } + cg2900_create_work_item(info->wq, work_reset_after_error, dev); + } + + return true; +} + +/** + * handle_vs_write_file_block_cmd_status() - Handles HCI VS WriteFileBlock Command Status event. + * @status: Returned status of WriteFileBlock command. + * + * Returns: + * true, if packet was handled internally, + * false, otherwise. + */ +static bool handle_vs_write_file_block_cmd_status(struct cg2900_chip_dev *dev, + u8 status) +{ + struct stlc2690_chip_info *info = dev->c_data; + + if (info->boot_state != BOOT_DOWNLOAD_PATCH || + info->download_state != DOWNLOAD_PENDING) + return false; + + /* + * Only do something if there is an error. Otherwise we will wait for + * CmdComplete. + */ + if (HCI_BT_ERROR_NO_ERROR != status) { + dev_err(BOOT_DEV, + "Command status for WriteFileBlock received with" + " error 0x%X\n", status); + dev_dbg(BOOT_DEV, "New download_state: DOWNLOAD_FAILED\n"); + info->download_state = DOWNLOAD_FAILED; + dev_dbg(BOOT_DEV, "New boot_state: BOOT_FAILED\n"); + info->boot_state = BOOT_FAILED; + if (info->file_info.fw_file) { + release_firmware(info->file_info.fw_file); + info->file_info.fw_file = NULL; + } + cg2900_create_work_item(info->wq, work_reset_after_error, dev); + } + + return true; +} + +/** + * handle_rx_data_bt_evt() - Check if received data should be handled in STLC2690 chip driver. + * @skb: Data packet + * + * The handle_rx_data_bt_evt() function checks if received data should be + * handled in STLC2690 chip driver. If so handle it correctly. + * Received data is always HCI BT Event. + * + * Returns: + * True, if packet was handled internally, + * False, otherwise. + */ +static bool handle_rx_data_bt_evt(struct cg2900_chip_dev *dev, + struct sk_buff *skb) +{ + bool pkt_handled = false; + /* skb cannot be NULL here so it is safe to de-reference */ + u8 *data = skb->data; + struct hci_event_hdr *evt; + u16 op_code; + + evt = (struct hci_event_hdr *)data; + data += sizeof(*evt); + + /* First check the event code. */ + if (HCI_EV_CMD_COMPLETE == evt->evt) { + struct hci_ev_cmd_complete *cmd_complete; + + cmd_complete = (struct hci_ev_cmd_complete *)data; + op_code = le16_to_cpu(cmd_complete->opcode); + dev_dbg(dev->dev, + "Received Command Complete: op_code = 0x%04X\n", + op_code); + /* Move to first byte after OCF */ + data += sizeof(*cmd_complete); + + if (op_code == HCI_OP_RESET) + pkt_handled = handle_reset_cmd_complete(dev, data); + else if (op_code == STLC2690_BT_OP_VS_STORE_IN_FS) + pkt_handled = handle_vs_store_in_fs_cmd_complete(dev, + data); + else if (op_code == STLC2690_BT_OP_VS_WRITE_FILE_BLOCK) + pkt_handled = + handle_vs_write_file_block_cmd_complete(dev, + data); + } else if (HCI_EV_CMD_STATUS == evt->evt) { + struct hci_ev_cmd_status *cmd_status; + + cmd_status = (struct hci_ev_cmd_status *)data; + + op_code = le16_to_cpu(cmd_status->opcode); + + dev_dbg(dev->dev, "Received Command Status: op_code = 0x%04X\n", + op_code); + + if (op_code == STLC2690_BT_OP_VS_WRITE_FILE_BLOCK) + pkt_handled = handle_vs_write_file_block_cmd_status + (dev, cmd_status->status); + } else if (HCI_EV_HW_ERROR == evt->evt) { + struct hci_ev_hw_error *hw_error; + + hw_error = (struct hci_ev_hw_error *)data; + /* + * Only do a printout. There might be a receiving stack that can + * handle this event + */ + dev_err(dev->dev, "HW Error event received with error 0x%02X\n", + hw_error->hw_code); + return false; + } else + return false; + + if (pkt_handled) + kfree_skb(skb); + + return pkt_handled; +} + +/** + * data_from_chip() - Called when data is received from the chip. + * @dev: Chip info. + * @skb: Packet received. + * + * The data_from_chip() function checks if packet is a response for a packet it + * itself has transmitted. If not it finds the correct user and sends the packet + * to the user. + */ +static void data_from_chip(struct cg2900_chip_dev *dev, + struct sk_buff *skb) +{ + int h4_channel; + struct list_head *cursor; + struct stlc2690_channel_item *tmp; + struct stlc2690_chip_info *info = dev->c_data; + struct cg2900_user_data *user = NULL; + + h4_channel = skb->data[0]; + skb_pull(skb, HCI_H4_SIZE); + + /* Then check if this is a response to data we have sent */ + if (h4_channel == CHANNEL_BT_EVT && handle_rx_data_bt_evt(dev, skb)) + return; + + spin_lock_bh(&info->rw_lock); + + /* Let's see if this packet has the same user as the last one */ + if (info->last_user && info->last_user->h4_channel == h4_channel) { + user = info->last_user; + goto user_found; + } + + /* Search through the list of all open channels to find the user */ + list_for_each(cursor, &info->open_channels) { + tmp = list_entry(cursor, struct stlc2690_channel_item, list); + if (tmp->user->h4_channel == h4_channel) { + user = tmp->user; + goto user_found; + } + } + +user_found: + info->last_user = user; + spin_unlock_bh(&info->rw_lock); + + if (user) + user->read_cb(user, skb); + else { + dev_err(dev->dev, + "Could not find corresponding user to h4_channel %d\n", + h4_channel); + kfree_skb(skb); + } +} + +static void chip_removed(struct cg2900_chip_dev *dev) +{ + struct stlc2690_chip_info *info = dev->c_data; + + mfd_remove_devices(dev->dev); + kfree(info->settings_file_name); + kfree(info->patch_file_name); + destroy_workqueue(info->wq); + kfree(info); + dev->c_data = NULL; + dev->c_cb.chip_removed = NULL; + dev->c_cb.data_from_chip = NULL; +} + +/** + * chip_shutdown() - Reset and power the chip off. + */ +static void chip_shutdown(struct cg2900_user_data *user) +{ + struct cg2900_chip_dev *dev = cg2900_get_prv(user); + struct stlc2690_chip_info *info = dev->c_data; + + dev_dbg(user->dev, "chip_shutdown\n"); + + /* Close the transport, which will power off the chip */ + if (dev->t_cb.close) + dev->t_cb.close(dev); + + /* Chip shut-down finished, set correct state and wake up the chip. */ + dev_dbg(dev->dev, "New main_state: STLC2690_IDLE\n"); + info->main_state = STLC2690_IDLE; + wake_up_all(&main_wait_queue); +} + +static void chip_startup_finished(struct stlc2690_chip_info *info, int err) +{ + dev_dbg(BOOT_DEV, "chip_startup_finished (%d)\n", err); + + if (err) + /* Shutdown the chip */ + chip_shutdown(info->user_in_charge); + else { + dev_dbg(BOOT_DEV, "New main_state: CORE_ACTIVE\n"); + info->main_state = STLC2690_ACTIVE; + } + + wake_up_all(&main_wait_queue); + + if (err) + return; + + if (!info->chip_dev->t_cb.chip_startup_finished) + dev_err(BOOT_DEV, "chip_startup_finished callback not found\n"); + else + info->chip_dev->t_cb.chip_startup_finished(info->chip_dev); +} + +static int stlc2690_open(struct cg2900_user_data *user) +{ + int err; + struct cg2900_chip_dev *dev; + struct stlc2690_chip_info *info; + struct list_head *cursor; + struct stlc2690_channel_item *tmp; + struct hci_command_hdr cmd; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, "stlc2690_open: Calling with NULL pointer\n"); + return -EINVAL; + } + + dev_dbg(user->dev, "stlc2690_open\n"); + + dev = cg2900_get_prv(user); + info = dev->c_data; + + mutex_lock(&main_info->man_mutex); + + /* Add a minor wait in order to avoid CPU blocking, looping openings */ + err = wait_event_timeout(main_wait_queue, + (STLC2690_IDLE == info->main_state || + STLC2690_ACTIVE == info->main_state), + msecs_to_jiffies(LINE_TOGGLE_DETECT_TIMEOUT)); + if (err <= 0) { + if (STLC2690_INIT == info->main_state) + dev_err(user->dev, "Transport not opened\n"); + else + dev_err(user->dev, "stlc2690_open currently busy " + "(0x%X). Try again\n", info->main_state); + err = -EBUSY; + goto err_free_mutex; + } + + err = 0; + + list_for_each(cursor, &info->open_channels) { + tmp = list_entry(cursor, struct stlc2690_channel_item, list); + if (tmp->user->h4_channel == user->h4_channel) { + dev_err(user->dev, "Channel %d is already opened\n", + user->h4_channel); + err = -EACCES; + goto err_free_mutex; + } + } + + tmp = kzalloc(sizeof(*tmp), GFP_KERNEL); + if (!tmp) { + dev_err(user->dev, "Could not allocate tmp\n"); + err = -ENOMEM; + goto err_free_mutex; + } + tmp->user = user; + + if (STLC2690_ACTIVE != info->main_state && + !user->chip_independent) { + /* Open transport and start-up the chip */ + if (dev->t_cb.set_chip_power) + dev->t_cb.set_chip_power(dev, true); + + /* Wait to be sure that the chip is ready */ + schedule_timeout_killable( + msecs_to_jiffies(CHIP_READY_TIMEOUT)); + + if (dev->t_cb.open) + err = dev->t_cb.open(dev); + if (err) { + if (dev->t_cb.set_chip_power) + dev->t_cb.set_chip_power(dev, false); + goto err_free_list_item; + } + + /* Start the boot sequence */ + info->user_in_charge = user; + info->last_user = user; + dev_dbg(user->dev, "New boot_state: BOOT_RESET\n"); + info->boot_state = BOOT_RESET; + dev_dbg(user->dev, "New main_state: STLC2690_BOOTING\n"); + info->main_state = STLC2690_BOOTING; + cmd.opcode = cpu_to_le16(HCI_OP_RESET); + cmd.plen = 0; /* No parameters for HCI reset */ + cg2900_send_bt_cmd(user, info->logger, &cmd, sizeof(cmd)); + + dev_dbg(user->dev, "Wait up to 15 seconds for chip to start\n"); + wait_event_timeout(main_wait_queue, + (STLC2690_ACTIVE == info->main_state || + STLC2690_IDLE == info->main_state), + msecs_to_jiffies(CHIP_STARTUP_TIMEOUT)); + if (STLC2690_ACTIVE != info->main_state) { + dev_err(user->dev, "STLC2690 driver failed to start\n"); + + if (dev->t_cb.close) + dev->t_cb.close(dev); + + dev_dbg(user->dev, "New main_state: CORE_IDLE\n"); + info->main_state = STLC2690_IDLE; + err = -EIO; + goto err_free_list_item; + } + } + + list_add_tail(&tmp->list, &info->open_channels); + + user->opened = true; + + dev_dbg(user->dev, "H:4 channel opened\n"); + + mutex_unlock(&main_info->man_mutex); + return 0; +err_free_list_item: + kfree(tmp); +err_free_mutex: + mutex_unlock(&main_info->man_mutex); + return err; +} + +static int stlc2690_hci_log_open(struct cg2900_user_data *user) +{ + struct cg2900_chip_dev *dev; + struct stlc2690_chip_info *info; + int err; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, + "stlc2690_hci_log_open: Calling with NULL pointer\n"); + return -EINVAL; + } + + dev_dbg(user->dev, "stlc2690_hci_log_open\n"); + + dev = cg2900_get_prv(user); + info = dev->c_data; + + if (info->logger) { + dev_err(user->dev, "HCI Logger already stored\n"); + return -EACCES; + } + + info->logger = user; + err = stlc2690_open(user); + if (err) + info->logger = NULL; + return err; +} + +static void stlc2690_close(struct cg2900_user_data *user) +{ + bool keep_powered = false; + struct list_head *cursor, *next; + struct stlc2690_channel_item *tmp; + struct cg2900_chip_dev *dev; + struct stlc2690_chip_info *info; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, + "stlc2690_close: Calling with NULL pointer\n"); + return; + } + + dev_dbg(user->dev, "stlc2690_close\n"); + + dev = cg2900_get_prv(user); + info = dev->c_data; + + mutex_lock(&main_info->man_mutex); + + /* + * Go through each open channel. Remove our channel and check if there + * is any other channel that want to keep the chip running + */ + list_for_each_safe(cursor, next, &info->open_channels) { + tmp = list_entry(cursor, struct stlc2690_channel_item, list); + if (tmp->user == user) { + list_del(cursor); + kfree(tmp); + } else if (!tmp->user->chip_independent) + keep_powered = true; + } + + if (keep_powered) + /* This was not the last user, we're done. */ + goto finished; + + if (STLC2690_IDLE == info->main_state) + /* Chip has already been shut down. */ + goto finished; + + dev_dbg(user->dev, "New main_state: CORE_CLOSING\n"); + info->main_state = STLC2690_CLOSING; + chip_shutdown(user); + + dev_dbg(user->dev, "Wait up to 15 seconds for chip to shut-down\n"); + wait_event_timeout(main_wait_queue, + STLC2690_IDLE == info->main_state, + msecs_to_jiffies(CHIP_SHUTDOWN_TIMEOUT)); + + /* Force shutdown if we timed out */ + if (STLC2690_IDLE != info->main_state) { + dev_err(user->dev, + "ST-Ericsson STLC2690 Core Driver was shut-down with " + "problems\n"); + + if (dev->t_cb.close) + dev->t_cb.close(dev); + + dev_dbg(user->dev, "New main_state: CORE_IDLE\n"); + info->main_state = STLC2690_IDLE; + } + +finished: + mutex_unlock(&main_info->man_mutex); + user->opened = false; + dev_dbg(user->dev, "H:4 channel closed\n"); +} + +static void stlc2690_hci_log_close(struct cg2900_user_data *user) +{ + struct cg2900_chip_dev *dev; + struct stlc2690_chip_info *info; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, + "stlc2690_hci_log_close: Calling with NULL pointer\n"); + return; + } + + dev_dbg(user->dev, "stlc2690_hci_log_close\n"); + + dev = cg2900_get_prv(user); + info = dev->c_data; + + info->logger = NULL; + stlc2690_close(user); +} + +static int stlc2690_reset(struct cg2900_user_data *user) +{ + struct list_head *cursor, *next; + struct stlc2690_channel_item *tmp; + struct cg2900_chip_dev *dev; + struct stlc2690_chip_info *info; + + if (!user) { + dev_err(MAIN_DEV, + "stlc2690_reset: Calling with NULL pointer\n"); + return -EINVAL; + } + + dev = cg2900_get_prv(user); + info = dev->c_data; + + dev_info(user->dev, "stlc2690_reset\n"); + + BUG_ON(!main_info); + + mutex_lock(&main_info->man_mutex); + + dev_dbg(user->dev, "New main_state: CORE_RESETING\n"); + info->main_state = STLC2690_RESETING; + + chip_shutdown(user); + + /* + * Inform all opened channels about the reset and free the user devices + */ + list_for_each_safe(cursor, next, &info->open_channels) { + tmp = list_entry(cursor, struct stlc2690_channel_item, list); + list_del(cursor); + tmp->user->opened = false; + tmp->user->reset_cb(tmp->user); + kfree(tmp); + } + + /* Reset finished. We are now idle until first channel is opened */ + dev_dbg(user->dev, "New main_state: STLC2690_IDLE\n"); + info->main_state = STLC2690_IDLE; + + mutex_unlock(&main_info->man_mutex); + + /* + * Send wake-up since this might have been called from a failed boot. + * No harm done if it is a STLC2690 chip user who called. + */ + wake_up_all(&main_wait_queue); + + return 0; +} + +static struct sk_buff *stlc2690_alloc_skb(unsigned int size, gfp_t priority) +{ + struct sk_buff *skb; + + dev_dbg(MAIN_DEV, "stlc2690_alloc_skb size %d bytes\n", size); + + /* Allocate the SKB and reserve space for the header */ + skb = alloc_skb(size + CG2900_SKB_RESERVE, priority); + if (skb) + skb_reserve(skb, CG2900_SKB_RESERVE); + + return skb; +} + +static int stlc2690_write(struct cg2900_user_data *user, struct sk_buff *skb) +{ + int err = 0; + u8 *h4_header; + struct cg2900_chip_dev *dev; + struct stlc2690_chip_info *info; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, + "stlc2690_write: Calling with NULL pointer\n"); + return -EINVAL; + } + + if (!skb) { + dev_err(user->dev, "stlc2690_write with no sk_buffer\n"); + return -EINVAL; + } + + dev = cg2900_get_prv(user); + info = dev->c_data; + + dev_dbg(user->dev, "stlc2690_write length %d bytes\n", skb->len); + + if (!user->opened) { + dev_err(user->dev, + "Trying to transmit data on a closed channel\n"); + return -EACCES; + } + + /* + * Move the data pointer to the H:4 header position and + * store the H4 header. + */ + h4_header = skb_push(skb, CG2900_SKB_RESERVE); + *h4_header = (u8)user->h4_channel; + cg2900_tx_to_chip(user, info->logger, skb); + + return err; +} + +static int stlc2690_no_write(struct cg2900_user_data *user, + struct sk_buff *skb) +{ + dev_err(user->dev, "Not allowed to send on this channel\n"); + return -EPERM; +} + +static bool stlc2690_get_local_revision(struct cg2900_user_data *user, + struct cg2900_rev_data *rev_data) +{ + struct cg2900_chip_dev *dev; + + BUG_ON(!main_info); + + if (!user) { + dev_err(MAIN_DEV, "stlc2690_get_local_revision: Calling with " + "NULL pointer\n"); + return false; + } + + if (!rev_data) { + dev_err(user->dev, "Calling with rev_data NULL\n"); + return false; + } + + dev = cg2900_get_prv(user); + + rev_data->revision = dev->chip.hci_revision; + rev_data->sub_version = dev->chip.hci_sub_version; + + return true; +} + +static struct cg2900_user_data btcmd_data = { + .h4_channel = CHANNEL_BT_CMD, +}; +static struct cg2900_user_data btacl_data = { + .h4_channel = CHANNEL_BT_ACL, +}; +static struct cg2900_user_data btevt_data = { + .h4_channel = CHANNEL_BT_EVT, +}; +static struct cg2900_user_data hci_logger_data = { + .h4_channel = CHANNEL_HCI_LOGGER, + .chip_independent = true, + .write = stlc2690_no_write, + .open = stlc2690_hci_log_open, + .close = stlc2690_hci_log_close, +}; +static struct cg2900_user_data core_data = { + .h4_channel = CHANNEL_CORE, + .write = stlc2690_no_write, +}; + +static struct mfd_cell stlc2690_devs[] = { + { + .name = "cg2900-btcmd", + .platform_data = &btcmd_data, + .pdata_size = sizeof(btcmd_data), + }, + { + .name = "cg2900-btacl", + .platform_data = &btacl_data, + .pdata_size = sizeof(btacl_data), + }, + { + .name = "cg2900-btevt", + .platform_data = &btevt_data, + .pdata_size = sizeof(btevt_data), + }, + { + .name = "cg2900-hcilogger", + .platform_data = &hci_logger_data, + .pdata_size = sizeof(hci_logger_data), + }, + { + .name = "cg2900-core", + .platform_data = &core_data, + .pdata_size = sizeof(core_data), + }, +}; + +static struct cg2900_user_data char_btcmd_data = { + .channel_data = { + .char_dev_name = STLC2690_BT_CMD, + }, + .h4_channel = CHANNEL_BT_CMD, +}; +static struct cg2900_user_data char_btacl_data = { + .channel_data = { + .char_dev_name = STLC2690_BT_ACL, + }, + .h4_channel = CHANNEL_BT_ACL, +}; +static struct cg2900_user_data char_btevt_data = { + .channel_data = { + .char_dev_name = STLC2690_BT_EVT, + }, + .h4_channel = CHANNEL_BT_EVT, +}; +static struct cg2900_user_data char_hci_logger_data = { + .channel_data = { + .char_dev_name = STLC2690_HCI_LOGGER, + }, + .h4_channel = CHANNEL_HCI_LOGGER, + .chip_independent = true, + .write = stlc2690_no_write, + .open = stlc2690_hci_log_open, + .close = stlc2690_hci_log_close, +}; +static struct cg2900_user_data char_core_data = { + .channel_data = { + .char_dev_name = STLC2690_CORE, + }, + .h4_channel = CHANNEL_CORE, + .write = stlc2690_no_write, +}; + +static struct mfd_cell stlc2690_char_devs[] = { + { + .name = "cg2900-chardev", + .id = 0, + .platform_data = &char_btcmd_data, + .pdata_size = sizeof(char_btcmd_data), + }, + { + .name = "cg2900-chardev", + .id = 1, + .platform_data = &char_btacl_data, + .pdata_size = sizeof(char_btacl_data), + }, + { + .name = "cg2900-chardev", + .id = 2, + .platform_data = &char_btevt_data, + .pdata_size = sizeof(char_btevt_data), + }, + { + .name = "cg2900-chardev", + .id = 7, + .platform_data = &char_hci_logger_data, + .pdata_size = sizeof(char_hci_logger_data), + }, + { + .name = "cg2900-chardev", + .id = 8, + .platform_data = &char_core_data, + .pdata_size = sizeof(char_core_data), + }, +}; + +/** + * set_plat_data() - Initializes data for an MFD cell. + * @cell: MFD cell. + * @dev: Current chip. + * + * Sets each callback to default function unless already set. + */ +static void set_plat_data(struct mfd_cell *cell, struct cg2900_chip_dev *dev) +{ + struct cg2900_user_data *user = cell->platform_data; + + if (!user->open) + user->open = stlc2690_open; + if (!user->close) + user->close = stlc2690_close; + if (!user->reset) + user->reset = stlc2690_reset; + if (!user->alloc_skb) + user->alloc_skb = stlc2690_alloc_skb; + if (!user->write) + user->write = stlc2690_write; + if (!user->get_local_revision) + user->get_local_revision = stlc2690_get_local_revision; + + cg2900_set_prv(user, dev); +} + +/** + * check_chip_support() - Checks if connected chip is handled by this driver. + * @dev: Chip info structure. + * + * If supported return true and fill in @callbacks. + * + * Returns: + * true if chip is handled by this driver. + * false otherwise. + */ +static bool check_chip_support(struct cg2900_chip_dev *dev) +{ + struct cg2900_platform_data *pf_data; + struct stlc2690_chip_info *info; + int i; + int err; + + dev_dbg(dev->dev, "check_chip_support\n"); + + /* + * Check if this is a STLC2690 revision. + * We do not care about the sub-version at the moment. Change this if + * necessary. + */ + if (dev->chip.manufacturer != STLC2690_SUPP_MANUFACTURER || + dev->chip.hci_revision < STLC2690_SUPP_REVISION_MIN || + dev->chip.hci_revision > STLC2690_SUPP_REVISION_MAX) { + dev_dbg(dev->dev, "Chip not supported by STLC2690 driver\n" + "\tMan: 0x%02X\n" + "\tRev: 0x%04X\n" + "\tSub: 0x%04X\n", + dev->chip.manufacturer, dev->chip.hci_revision, + dev->chip.hci_sub_version); + return false; + } + + /* Store needed data */ + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + dev_err(dev->dev, "Couldn't allocate info struct\n"); + return false; + } + + /* Initialize all variables */ + INIT_LIST_HEAD(&info->open_channels); + spin_lock_init(&info->rw_lock); + info->chip_dev = dev; + + info->wq = create_singlethread_workqueue(WQ_NAME); + if (!info->wq) { + dev_err(dev->dev, "Could not create workqueue\n"); + goto err_handling_free_info; + } + + info->patch_file_name = kzalloc(NAME_MAX + 1, GFP_ATOMIC); + if (!info->patch_file_name) { + dev_err(dev->dev, + "Couldn't allocate name buffer for patch file\n"); + goto err_handling_destroy_wq; + } + + info->settings_file_name = kzalloc(NAME_MAX + 1, GFP_ATOMIC); + if (!info->settings_file_name) { + dev_err(dev->dev, + "Couldn't allocate name buffers settings file\n"); + goto err_handling_free_patch_name; + } + + dev->c_data = info; + /* Set the callbacks */ + dev->c_cb.data_from_chip = data_from_chip; + dev->c_cb.chip_removed = chip_removed, + info->chip_dev = dev; + + mutex_lock(&main_info->man_mutex); + + pf_data = dev_get_platdata(dev->dev); + btcmd_data.channel_data.bt_bus = pf_data->bus; + btacl_data.channel_data.bt_bus = pf_data->bus; + btevt_data.channel_data.bt_bus = pf_data->bus; + + for (i = 0; i < ARRAY_SIZE(stlc2690_devs); i++) + set_plat_data(&stlc2690_devs[i], dev); + for (i = 0; i < ARRAY_SIZE(stlc2690_char_devs); i++) + set_plat_data(&stlc2690_char_devs[i], dev); + mutex_unlock(&main_info->man_mutex); + + dev_info(dev->dev, "Chip supported by the STLC2690 chip driver\n"); + + /* Close the transport, which will power off the chip */ + if (dev->t_cb.close) + dev->t_cb.close(dev); + + dev_dbg(dev->dev, "New main_state: STLC2690_IDLE\n"); + info->main_state = STLC2690_IDLE; + + err = mfd_add_devices(dev->dev, main_info->cell_base_id, stlc2690_devs, + ARRAY_SIZE(stlc2690_devs), NULL, 0); + if (err) { + dev_err(dev->dev, "Failed to add stlc2690_devs (%d)\n", err); + goto err_handling_free_settings_name; + } + + err = mfd_add_devices(dev->dev, main_info->cell_base_id, + stlc2690_char_devs, + ARRAY_SIZE(stlc2690_char_devs), NULL, 0); + if (err) { + dev_err(dev->dev, "Failed to add stlc2690_char_devs (%d)\n", + err); + goto err_handling_remove_devs; + } + + /* + * Increase base ID so next connected transport will not get the + * same device IDs. + */ + main_info->cell_base_id += MAX(ARRAY_SIZE(stlc2690_devs), + ARRAY_SIZE(stlc2690_char_devs)); + + return true; + +err_handling_remove_devs: + mfd_remove_devices(dev->dev); +err_handling_free_settings_name: + kfree(info->settings_file_name); +err_handling_free_patch_name: + kfree(info->patch_file_name); +err_handling_destroy_wq: + destroy_workqueue(info->wq); +err_handling_free_info: + kfree(info); + return false; +} + +static struct cg2900_id_callbacks chip_support_callbacks = { + .check_chip_support = check_chip_support, +}; + +/** + * stlc2690_chip_probe() - Initialize STLC2690 chip handler resources. + * @pdev: Platform device. + * + * This function initializes the STLC2690 driver, then registers to + * the CG2900 Core. + * + * Returns: + * 0 if success. + * -ENOMEM for failed alloc or structure creation. + * Error codes generated by cg2900_register_chip_driver. + */ +static int __devinit stlc2690_chip_probe(struct platform_device *pdev) +{ + int err; + + dev_dbg(&pdev->dev, "stlc2690_chip_probe\n"); + + main_info = kzalloc(sizeof(*main_info), GFP_ATOMIC); + if (!main_info) { + dev_err(&pdev->dev, "Couldn't allocate main_info\n"); + return -ENOMEM; + } + + main_info->dev = &pdev->dev; + mutex_init(&main_info->man_mutex); + + err = cg2900_register_chip_driver(&chip_support_callbacks); + if (err) { + dev_err(&pdev->dev, + "Couldn't register chip driver (%d)\n", err); + goto error_handling; + } + + dev_info(&pdev->dev, "STLC2690 chip driver started\n"); + + return 0; + +error_handling: + mutex_destroy(&main_info->man_mutex); + kfree(main_info); + main_info = NULL; + return err; +} + +/** + * stlc2690_chip_remove() - Release STLC2690 chip handler resources. + * @pdev: Platform device. + * + * Returns: + * 0 if success (always success). + */ +static int __devexit stlc2690_chip_remove(struct platform_device *pdev) +{ + dev_info(&pdev->dev, "STLC2690 chip driver removed\n"); + + cg2900_deregister_chip_driver(&chip_support_callbacks); + + if (!main_info) + return 0; + + mutex_destroy(&main_info->man_mutex); + kfree(main_info); + main_info = NULL; + return 0; +} + +static struct platform_driver stlc2690_chip_driver = { + .driver = { + .name = "stlc2690-chip", + .owner = THIS_MODULE, + }, + .probe = stlc2690_chip_probe, + .remove = __devexit_p(stlc2690_chip_remove), +}; + +/** + * stlc2690_chip_init() - Initialize module. + * + * Registers platform driver. + */ +static int __init stlc2690_chip_init(void) +{ + pr_debug("stlc2690_chip_init"); + return platform_driver_register(&stlc2690_chip_driver); +} + +/** + * stlc2690_chip_exit() - Remove module. + * + * Unregisters platform driver. + */ +static void __exit stlc2690_chip_exit(void) +{ + pr_debug("stlc2690_chip_exit"); + platform_driver_unregister(&stlc2690_chip_driver); +} + +module_init(stlc2690_chip_init); +module_exit(stlc2690_chip_exit); + +MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Linux STLC2690 Connectivity Device Driver"); diff --git a/drivers/staging/cg2900/mfd/stlc2690_chip.h b/drivers/staging/cg2900/mfd/stlc2690_chip.h new file mode 100644 index 00000000000..d14e7737636 --- /dev/null +++ b/drivers/staging/cg2900/mfd/stlc2690_chip.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson. + * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson. + * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson. + * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Linux Bluetooth HCI H:4 Driver for ST-Ericsson STLC2690 BT/FM controller. + */ + +#ifndef _STLC2690_CHIP_H_ +#define _STLC2690_CHIP_H_ + +/* Supported chips */ +#define STLC2690_SUPP_MANUFACTURER 0x30 +#define STLC2690_SUPP_REVISION_MIN 0x0500 +#define STLC2690_SUPP_REVISION_MAX 0x06FF + +#define BT_SIZE_OF_HDR (sizeof(__le16) + sizeof(__u8)) +#define BT_PARAM_LEN(__pkt_len) (__pkt_len - BT_SIZE_OF_HDR) + +/* BT VS Store In FS command */ +#define STLC2690_BT_OP_VS_STORE_IN_FS 0xFC22 +struct bt_vs_store_in_fs_cmd { + __le16 opcode; + __u8 plen; + __u8 user_id; + __u8 len; + __u8 data[]; +} __packed; + +/* BT VS Write File Block command */ +#define STLC2690_BT_OP_VS_WRITE_FILE_BLOCK 0xFC2E +struct bt_vs_write_file_block_cmd { + __le16 opcode; + __u8 plen; + __u8 id; + __u8 data[]; +} __packed; + +/* User ID for storing BD address in chip using Store_In_FS command */ +#define STLC2690_VS_STORE_IN_FS_USR_ID_BD_ADDR 0xFE + +#endif /* _STLC2690_CHIP_H_ */ |