[PATCH 3/9] MFD: Add chip handler for the ST-Ericsson CG2900.



This patch adds a chip handler for the ST-Ericsson CG2900 Connectivity
Combo controller.
This patch adds all functionality needed towards the CG2900, including
patch downloading, chip startup, and chip specific functionality.

Signed-off-by: Par-Gunnar Hjalmdahl <par-gunnar.p.hjalmdahl@xxxxxxxxxxxxxx>
---
drivers/mfd/Kconfig | 6 +
drivers/mfd/cg2900/Makefile | 2 +
drivers/mfd/cg2900/cg2900_chip.c | 2238 ++++++++++++++++++++++++++++++++++++++
drivers/mfd/cg2900/cg2900_chip.h | 588 ++++++++++
4 files changed, 2834 insertions(+), 0 deletions(-)
create mode 100644 drivers/mfd/cg2900/cg2900_chip.c
create mode 100644 drivers/mfd/cg2900/cg2900_chip.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 3ee9c66..fca7e29 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -270,6 +270,12 @@ config MFD_CG2900
Supports multiple functionalities muxed over a Bluetooth HCI H:4 interface.
CG2900 support Bluetooth, FM radio, and GPS.

+config MFD_CG2900_CHIP
+ tristate "Support CG2900 Connectivity controller"
+ depends on MFD_CG2900
+ help
+ Support for ST-Ericsson CG2900 Connectivity Controller
+
config PMIC_DA903X
bool "Dialog Semiconductor DA9030/DA9034 PMIC Support"
depends on I2C=y
diff --git a/drivers/mfd/cg2900/Makefile b/drivers/mfd/cg2900/Makefile
index 0ac9bc6..c4aabf3 100644
--- a/drivers/mfd/cg2900/Makefile
+++ b/drivers/mfd/cg2900/Makefile
@@ -7,3 +7,5 @@ export-objs := cg2900_core.o

obj-$(CONFIG_MFD_CG2900) += cg2900_char_devices.o

+obj-$(CONFIG_MFD_CG2900_CHIP) += cg2900_chip.o
+
diff --git a/drivers/mfd/cg2900/cg2900_chip.c b/drivers/mfd/cg2900/cg2900_chip.c
new file mode 100644
index 0000000..2e3c167
--- /dev/null
+++ b/drivers/mfd/cg2900/cg2900_chip.c
@@ -0,0 +1,2238 @@
+/*
+ * drivers/mfd/cg2900/cg2900_chip.c
+ *
+ * Copyright (C) ST-Ericsson SA 2010
+ * Authors:
+ * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@xxxxxxxxxxxxxx) for
ST-Ericsson.
+ * Henrik Possung (henrik.possung@xxxxxxxxxxxxxx) for ST-Ericsson.
+ * Josef Kindberg (josef.kindberg@xxxxxxxxxxxxxx) for ST-Ericsson.
+ * Dariusz Szymszak (dariusz.xd.szymczak@xxxxxxxxxxxxxx) for ST-Ericsson.
+ * Kjell Andersson (kjell.k.andersson@xxxxxxxxxxxxxx) 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.
+ */
+
+#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/cg2900.h>
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci.h>
+
+#include "cg2900_chip.h"
+#include "cg2900_core.h"
+#include "cg2900_debug.h"
+#include "hci_defines.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 WQ_NAME "cg2900_chip_wq"
+#define PATCH_INFO_FILE "cg2900_patch_info.fw"
+#define FACTORY_SETTINGS_INFO_FILE "cg2900_settings_info.fw"
+
+/* Size of file chunk ID */
+#define FILE_CHUNK_ID_SIZE 1
+#define FILE_CHUNK_ID_POS 4
+
+/* Times in milliseconds */
+#define POWER_SW_OFF_WAIT 500
+
+/* State setting macros */
+#define SET_BOOT_STATE(__cg2900_new_state) \
+ CG2900_SET_STATE("boot_state", cg2900_info->boot_state, \
+ __cg2900_new_state)
+#define SET_CLOSING_STATE(__cg2900_new_state) \
+ CG2900_SET_STATE("closing_state", cg2900_info->closing_state, \
+ __cg2900_new_state)
+#define SET_FILE_LOAD_STATE(__cg2900_new_state) \
+ CG2900_SET_STATE("file_load_state", cg2900_info->file_load_state, \
+ __cg2900_new_state)
+#define SET_DOWNLOAD_STATE(__cg2900_new_state) \
+ CG2900_SET_STATE("download_state", cg2900_info->download_state, \
+ __cg2900_new_state)
+
+/** 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_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_US_CTRL - Bluetooth HCI H:4 channel
+ * for user space control of the ST-Ericsson connectivity controller.
+ */
+#define CHANNEL_US_CTRL 0xFC
+
+/** CHANNEL_CORE - Bluetooth HCI H:4 channel
+ * for user space control of the ST-Ericsson connectivity controller.
+ */
+#define CHANNEL_CORE 0xFD
+
+/**
+ * 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_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_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_device_id - Structure for connecting H4 channel to named user.
+ * @name: Name of device.
+ * @h4_channel: HCI H:4 channel used by this device.
+ */
+struct cg2900_device_id {
+ char *name;
+ int h4_channel;
+};
+
+/**
+ * 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_device *dev;
+};
+#define cg2900_skb_data(__skb) ((struct cg2900_skb_data *)((__skb)->cb))
+
+/**
+ * struct cg2900_info - Main info structure for CG2900 chip driver.
+ * @dev: Device structure.
+ * @patch_file_name: Stores patch file name.
+ * @settings_file_name: Stores settings file name.
+ * @fw_file: Stores firmware file (patch or settings).
+ * @file_offset: Current read offset in firmware file.
+ * @chunk_id: Stores current chunk ID of write file
+ * operations.
+ * @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).
+ */
+struct cg2900_info {
+ struct device *dev;
+ char *patch_file_name;
+ char *settings_file_name;
+ const struct firmware *fw_file;
+ int file_offset;
+ u8 chunk_id;
+ 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;
+ 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;
+};
+
+static struct cg2900_info *cg2900_info;
+
+/*
+ * cg2900_channels() - Array containing available H4 channels for the CG2900
+ * ST-Ericsson Connectivity controller.
+ */
+struct cg2900_device_id cg2900_channels[] = {
+ {CG2900_BT_CMD, CHANNEL_BT_CMD},
+ {CG2900_BT_ACL, CHANNEL_BT_ACL},
+ {CG2900_BT_EVT, CHANNEL_BT_EVT},
+ {CG2900_GNSS, CHANNEL_GNSS},
+ {CG2900_FM_RADIO, CHANNEL_FM_RADIO},
+ {CG2900_DEBUG, CHANNEL_DEBUG},
+ {CG2900_STE_TOOLS, CHANNEL_STE_TOOLS},
+ {CG2900_HCI_LOGGER, CHANNEL_HCI_LOGGER},
+ {CG2900_US_CTRL, CHANNEL_US_CTRL},
+ {CG2900_BT_AUDIO, CHANNEL_BT_CMD},
+ {CG2900_FM_RADIO_AUDIO, CHANNEL_FM_RADIO},
+ {CG2900_CORE, CHANNEL_CORE}
+};
+
+/*
+ * Internal function
+ */
+
+/**
+ * create_and_send_bt_cmd() - Copy and send sk_buffer.
+ * @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.
+ */
+static void create_and_send_bt_cmd(void *data, int length)
+{
+ struct sk_buff *skb;
+ struct cg2900_hci_logger_config *logger_config;
+ int err;
+
+ skb = cg2900_alloc_skb(length, GFP_ATOMIC);
+ if (!skb) {
+ CG2900_ERR("Couldn't alloc sk_buff with length %d", length);
+ return;
+ }
+
+ memcpy(skb_put(skb, length), data, length);
+ skb_push(skb, CG2900_SKB_RESERVE);
+ skb->data[0] = CHANNEL_BT_CMD;
+
+ logger_config = cg2900_get_hci_logger_config();
+ if (logger_config)
+ err = cg2900_send_to_chip(skb, logger_config->bt_cmd_enable);
+ else
+ err = cg2900_send_to_chip(skb, false);
+
+ if (err) {
+ CG2900_ERR("Failed to transmit to chip (%d)", err);
+ kfree_skb(skb);
+ }
+}
+
+/**
+ * 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(u16 cmd_id)
+{
+ bool retval = false;
+
+ switch (cmd_id) {
+ case CG2900_FM_DO_AIP_FADE_START:
+ if (cg2900_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 (cg2900_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)
+ CG2900_INFO("Following interrupt event expected for this "
+ "Cmd complete evt, cmd_id = 0x%x.", 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)) {
+ CG2900_INFO("Irpt evt for FM do-command found, "
+ "irpt_val = 0x%x.", irpt_val);
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * create_work_item() - Create work item and add it to the work queue.
+ * @work_func: Work function.
+ *
+ * The create_work_item() function creates work item and add it to
+ * the work queue.
+ */
+static void create_work_item(work_func_t work_func)
+{
+ struct work_struct *new_work;
+ int wq_err;
+
+ new_work = kmalloc(sizeof(*new_work), GFP_ATOMIC);
+ if (!new_work) {
+ CG2900_ERR("Failed to alloc memory for work_struct!");
+ return;
+ }
+
+ INIT_WORK(new_work, work_func);
+
+ wq_err = queue_work(cg2900_info->wq, new_work);
+ if (!wq_err) {
+ CG2900_ERR("Failed to queue work_struct because it's already "
+ "in the queue!");
+ kfree(new_work);
+ }
+}
+
+/**
+ * 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(void)
+{
+ CG2900_INFO("fm_reset_flow_ctrl");
+
+ skb_queue_purge(&cg2900_info->tx_queue_fm);
+
+ /* Reset the fm_cmd_id. */
+ cg2900_info->audio_fm_cmd_id = CG2900_FM_CMD_NONE;
+ cg2900_info->hci_fm_cmd_func = CG2900_FM_CMD_PARAM_NONE;
+
+ cg2900_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) {
+ CG2900_ERR("Not an FM legacy command 0x%X", pkt->opcode);
+ return;
+ }
+
+ *cmd_func = pkt->fm_function;
+ CG2900_DBG("cmd_func 0x%X", *cmd_func);
+ if (*cmd_func == CG2900_FM_CMD_PARAM_WRITECOMMAND) {
+ *cmd_id = cg2900_get_fm_cmd_id(le16_to_cpu(pkt->fm_cmd.head));
+ CG2900_DBG("cmd_id 0x%X", *cmd_id);
+ }
+}
+
+
+/**
+ * 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 + HCI_H4_SIZE);
+
+ *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;
+ CG2900_DBG("cmd_func 0x%X", *cmd_func);
+ if (*cmd_func == CG2900_FM_CMD_PARAM_WRITECOMMAND) {
+ *cmd_id = cg2900_get_fm_cmd_id(
+ le16_to_cpu(pkt->evt.response_head));
+ CG2900_DBG("cmd_id 0x%X", *cmd_id);
+ }
+ } 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);
+ CG2900_DBG("intr_val 0x%X", *intr_val);
+ } 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);
+ CG2900_DBG("intr_val 0x%X", *intr_val);
+ } else {
+ CG2900_ERR("Not an FM legacy command 0x%X %X %X %X ...",
+ 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(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);
+
+ cg2900_info->fm_radio_mode = le16_to_cpu(pkt->fm_cmd.data[0]);
+ CG2900_INFO("FM Radio mode changed to 0x%x",
+ cg2900_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(void)
+{
+ struct cg2900_device *dev;
+ struct sk_buff *skb;
+
+ CG2900_INFO("transmit_skb_from_tx_queue_bt");
+
+ /* Dequeue an skb from the head of the list */
+ skb = skb_dequeue(&cg2900_info->tx_queue_bt);
+ while (skb) {
+ if ((cg2900_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(&cg2900_info->tx_queue_bt, skb);
+ return;
+ }
+
+ (cg2900_info->tx_nr_pkts_allowed_bt)--;
+ CG2900_DBG("tx_nr_pkts_allowed_bt = %d",
+ cg2900_info->tx_nr_pkts_allowed_bt);
+
+ dev = cg2900_skb_data(skb)->dev; /* dev 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 (cg2900_get_bt_audio_dev() == dev) {
+ struct hci_command_hdr *hdr = (struct hci_command_hdr *)
+ (skb->data + HCI_H4_SIZE);
+
+ cg2900_info->audio_bt_cmd_op = le16_to_cpu(hdr->opcode);
+ CG2900_DBG("Sending cmd from audio driver, saving "
+ "OpCode = 0x%X",
+ cg2900_info->audio_bt_cmd_op);
+ }
+
+ cg2900_send_to_chip(skb, dev->logger_enabled);
+
+ /* Dequeue an skb from the head of the list */
+ skb = skb_dequeue(&cg2900_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(void)
+{
+ struct cg2900_device *dev;
+ struct sk_buff *skb;
+
+ CG2900_INFO("transmit_skb_from_tx_queue_fm");
+
+ /* Dequeue an skb from the head of the list */
+ skb = skb_dequeue(&cg2900_info->tx_queue_fm);
+ while (skb) {
+ u16 cmd_id;
+ u8 cmd_func;
+ bool do_transmit = false;
+
+ if (cg2900_info->audio_fm_cmd_id != CG2900_FM_CMD_NONE ||
+ cg2900_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(&cg2900_info->tx_queue_bt, skb);
+ return;
+ }
+
+ dev = cg2900_skb_data(skb)->dev; /* dev is never NULL */
+
+ 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 (cg2900_get_fm_audio_dev() == dev) {
+ cg2900_info->audio_fm_cmd_id = cmd_id;
+ CG2900_DBG("audio_fm_cmd_id 0x%X",
+ cg2900_info->audio_fm_cmd_id);
+ do_transmit = true;
+ }
+ if (cg2900_get_fm_radio_dev() == dev) {
+ cg2900_info->hci_fm_cmd_func = cmd_func;
+ fm_update_mode(&(skb->data[0]));
+ CG2900_DBG("hci_fm_cmd_func 0x%X",
+ cg2900_info->hci_fm_cmd_func);
+ do_transmit = true;
+ }
+
+ if (do_transmit) {
+ /*
+ * We have only one ticket on FM. Just return after
+ * sending the skb.
+ */
+ cg2900_send_to_chip(skb, dev->logger_enabled);
+ return;
+ }
+
+ /*
+ * This packet was neither FM or FM audio. 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(&cg2900_info->tx_queue_fm);
+ }
+}
+
+/**
+ * update_flow_ctrl_bt() - Update number of outstanding commands for BT CMD.
+ * @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(const struct sk_buff * const skb)
+{
+ u8 *data = &(skb->data[CG2900_SKB_RESERVE]);
+ u8 event_code = data[0];
+
+ if (HCI_BT_EVT_CMD_COMPLETE == event_code) {
+ /*
+ * 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(&(cg2900_info->tx_bt_lock));
+ cg2900_info->tx_nr_pkts_allowed_bt =
+ data[HCI_BT_EVT_CMD_COMPL_NR_OF_PKTS_POS];
+ CG2900_DBG("New tx_nr_pkts_allowed_bt = %d",
+ cg2900_info->tx_nr_pkts_allowed_bt);
+
+ if (!skb_queue_empty(&cg2900_info->tx_queue_bt))
+ transmit_skb_from_tx_queue_bt();
+ spin_unlock_bh(&(cg2900_info->tx_bt_lock));
+ } else if (HCI_BT_EVT_CMD_STATUS == event_code) {
+ /*
+ * 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(&(cg2900_info->tx_bt_lock));
+ cg2900_info->tx_nr_pkts_allowed_bt =
+ data[HCI_BT_EVT_CMD_STATUS_NR_OF_PKTS_POS];
+ CG2900_DBG("New tx_nr_pkts_allowed_bt = %d",
+ cg2900_info->tx_nr_pkts_allowed_bt);
+
+ if (!skb_queue_empty(&cg2900_info->tx_queue_bt))
+ transmit_skb_from_tx_queue_bt();
+ spin_unlock_bh(&(cg2900_info->tx_bt_lock));
+ }
+}
+
+/**
+ * update_flow_ctrl_fm() - Update packets allowed for FM channel.
+ * @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(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;
+
+ 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(&(cg2900_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(cmd_id)) {
+ cg2900_info->hci_fm_cmd_func = CG2900_FM_CMD_PARAM_NONE;
+ cg2900_info->audio_fm_cmd_id = CG2900_FM_CMD_NONE;
+ CG2900_DBG("FM cmd outstanding cmd func 0x%x",
+ cg2900_info->hci_fm_cmd_func);
+ CG2900_DBG("FM cmd Audio outstanding cmd id 0x%x",
+ cg2900_info->audio_fm_cmd_id);
+ transmit_skb_from_tx_queue_fm();
+
+ /*
+ * 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 == cg2900_info->audio_fm_cmd_id) {
+ cg2900_info->tx_fm_audio_awaiting_irpt = true;
+ CG2900_DBG("FM Audio waiting for interrupt = true.");
+ }
+ spin_unlock_bh(&(cg2900_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(&(cg2900_info->tx_fm_lock));
+ cg2900_info->hci_fm_cmd_func = CG2900_FM_CMD_PARAM_NONE;
+ cg2900_info->audio_fm_cmd_id = CG2900_FM_CMD_NONE;
+ CG2900_DBG("FM cmd outstanding cmd func 0x%x",
+ cg2900_info->hci_fm_cmd_func);
+ CG2900_DBG("FM cmd Audio outstanding cmd id 0x%x",
+ cg2900_info->audio_fm_cmd_id);
+ cg2900_info->tx_fm_audio_awaiting_irpt = false;
+ CG2900_DBG("FM Audio waiting for interrupt = false.");
+ transmit_skb_from_tx_queue_fm();
+ spin_unlock_bh(&(cg2900_info->tx_fm_lock));
+ }
+ }
+}
+
+/**
+ * send_bd_address() - Send HCI VS command with BD address to the chip.
+ */
+static void send_bd_address(void)
+{
+ struct bt_vs_store_in_fs_cmd *cmd;
+ /*
+ * The '-1' is for the first byte of the data field that's already
+ * there.
+ */
+ u8 plen = sizeof(*cmd) + BT_BDADDR_SIZE - 1;
+
+ cmd = kmalloc(plen, GFP_KERNEL);
+ if (!cmd)
+ 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);
+
+ SET_BOOT_STATE(BOOT_SEND_BD_ADDRESS);
+
+ create_and_send_bt_cmd(cmd, plen);
+
+ kfree(cmd);
+}
+
+/**
+ * get_text_line()- Replacement function for stdio function fgets.
+ * @wr_buffer: Buffer to copy text to.
+ * @max_nbr_of_bytes: Max number of bytes to read, i.e. size of rd_buffer.
+ * @rd_buffer: Data to parse.
+ * @bytes_copied: Number of bytes copied to wr_buffer.
+ *
+ * The get_text_line() function extracts one line of text from input file.
+ *
+ * Returns:
+ * Pointer to next data to read.
+ */
+static char *get_text_line(char *wr_buffer, int max_nbr_of_bytes,
+ char *rd_buffer, int *bytes_copied)
+{
+ char *curr_wr = wr_buffer;
+ char *curr_rd = rd_buffer;
+ char in_byte;
+
+ *bytes_copied = 0;
+
+ do {
+ *curr_wr = *curr_rd;
+ in_byte = *curr_wr;
+ curr_wr++;
+ curr_rd++;
+ (*bytes_copied)++;
+ } while ((*bytes_copied <= max_nbr_of_bytes) && (in_byte != '\0') &&
+ (in_byte != '\n'));
+ *curr_wr = '\0';
+ return curr_rd;
+}
+
+/**
+ * get_file_to_load() - Parse info file and find correct target file.
+ * @fw: Firmware structure containing file data.
+ * @file_name: (out) Pointer to name of requested file.
+ *
+ * Returns:
+ * true, if target file was found,
+ * false, otherwise.
+ */
+static bool get_file_to_load(const struct firmware *fw, char **file_name)
+{
+ char *line_buffer;
+ char *curr_file_buffer;
+ int bytes_left_to_parse = fw->size;
+ int bytes_read = 0;
+ bool file_found = false;
+ u32 hci_rev;
+ u32 lmp_sub;
+
+ curr_file_buffer = (char *)&(fw->data[0]);
+
+ line_buffer = kzalloc(LINE_BUFFER_LENGTH, GFP_ATOMIC);
+ if (!line_buffer) {
+ CG2900_ERR("Failed to allocate line_buffer");
+ return false;
+ }
+
+ while (!file_found) {
+ /* Get one line of text from the file to parse */
+ curr_file_buffer = get_text_line(line_buffer,
+ min(LINE_BUFFER_LENGTH,
+ (int)(fw->size - bytes_read)),
+ curr_file_buffer,
+ &bytes_read);
+
+ bytes_left_to_parse -= bytes_read;
+ if (bytes_left_to_parse <= 0) {
+ /* End of file => Leave while loop */
+ CG2900_ERR("Reached end of file. No file found!");
+ break;
+ }
+
+ /*
+ * Check if the line of text is a comment or not, comments begin
+ * with '#'
+ */
+ if (*line_buffer == '#')
+ continue;
+
+ hci_rev = 0;
+ lmp_sub = 0;
+
+ CG2900_DBG("Found a valid line <%s>", line_buffer);
+
+ /*
+ * Check if we can find the correct HCI revision and
+ * LMP subversion as well as a file name in
+ * the text line.
+ */
+ if (sscanf(line_buffer, "%x%x%s", &hci_rev, &lmp_sub,
+ *file_name) == 3
+ && hci_rev == cg2900_info->chip_dev.chip.hci_revision
+ && lmp_sub == cg2900_info->chip_dev.chip.hci_sub_version) {
+ CG2900_DBG("File found for chip\n"
+ "\tFile name = %s\n"
+ "\tHCI Revision = 0x%X\n"
+ "\tLMP PAL Subversion = 0x%X",
+ *file_name, hci_rev, lmp_sub);
+
+ /*
+ * Name has already been stored above. Nothing more to
+ * do.
+ */
+ file_found = true;
+ } else
+ /* Zero the name buffer so it is clear to next read */
+ memset(*file_name, 0x00, NAME_MAX + 1);
+ }
+ kfree(line_buffer);
+
+ return file_found;
+}
+
+/**
+ * read_and_send_file_part() - Transmit a part of the supplied file.
+ *
+ * The read_and_send_file_part() function transmit a part of the supplied file
+ * to the controller.
+ * If nothing more to read, set the correct states.
+ */
+static void read_and_send_file_part(void)
+{
+ int bytes_to_copy;
+ struct sk_buff *skb;
+ struct cg2900_hci_logger_config *logger_config;
+ 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)(cg2900_info->fw_file->size -
+ cg2900_info->file_offset));
+
+ if (bytes_to_copy <= 0) {
+ /* Nothing more to read in file. */
+ SET_DOWNLOAD_STATE(DOWNLOAD_SUCCESS);
+ cg2900_info->chunk_id = 0;
+ cg2900_info->file_offset = 0;
+ return;
+ }
+
+ /* There is more data to send */
+ logger_config = cg2900_get_hci_logger_config();
+
+ /*
+ * There are bytes to transmit. Allocate a sk_buffer.
+ * When calculating length to alloc the '-1' is because of the first
+ * byte of the data field that is already defined in the struct.
+ */
+ plen = sizeof(*cmd) - 1 + bytes_to_copy;
+ skb = cg2900_alloc_skb(plen, GFP_ATOMIC);
+ if (!skb) {
+ CG2900_ERR("Couldn't allocate sk_buffer");
+ SET_BOOT_STATE(BOOT_FAILED);
+ cg2900_chip_startup_finished(-EIO);
+ return;
+ }
+
+ 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 = cg2900_info->chunk_id;
+ cg2900_info->chunk_id++;
+
+ /* Copy the data from offset position */
+ memcpy(&(cmd->data),
+ &(cg2900_info->fw_file->data[cg2900_info->file_offset]),
+ bytes_to_copy);
+
+ /* Increase offset with number of bytes copied */
+ cg2900_info->file_offset += bytes_to_copy;
+
+ skb_push(skb, CG2900_SKB_RESERVE);
+ skb->data[0] = CHANNEL_BT_CMD;
+
+ if (logger_config)
+ cg2900_send_to_chip(skb, logger_config->bt_cmd_enable);
+ else
+ cg2900_send_to_chip(skb, false);
+}
+
+/**
+ * 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(void)
+{
+ /* Transmit a file part */
+ read_and_send_file_part();
+
+ if (cg2900_info->download_state != DOWNLOAD_SUCCESS)
+ return;
+
+ /* Settings file finished. Release used resources */
+ CG2900_DBG("Settings file finished, release used resources");
+ if (cg2900_info->fw_file) {
+ release_firmware(cg2900_info->fw_file);
+ cg2900_info->fw_file = NULL;
+ }
+
+ SET_FILE_LOAD_STATE(FILE_LOAD_NO_MORE_FILES);
+
+ /* Create and send HCI VS Store In FS command with bd address. */
+ send_bd_address();
+}
+
+/**
+ * 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(void)
+{
+ int err;
+
+ /*
+ * Transmit a part of the supplied file to the controller.
+ * When nothing more to read, continue to close the patch file.
+ */
+ read_and_send_file_part();
+
+ if (cg2900_info->download_state != DOWNLOAD_SUCCESS)
+ return;
+
+ /* Patch file finished. Release used resources */
+ CG2900_DBG("Patch file finished, release used resources");
+ if (cg2900_info->fw_file) {
+ release_firmware(cg2900_info->fw_file);
+ cg2900_info->fw_file = NULL;
+ }
+ /* Retrieve the settings file */
+ err = request_firmware(&(cg2900_info->fw_file),
+ cg2900_info->settings_file_name,
+ cg2900_info->dev);
+ if (err < 0) {
+ CG2900_ERR("Couldn't get settings file (%d)", err);
+ goto error_handling;
+ }
+ /* Now send the settings file */
+ SET_FILE_LOAD_STATE(FILE_LOAD_GET_STATIC_SETTINGS);
+ SET_DOWNLOAD_STATE(DOWNLOAD_PENDING);
+ send_settings_file();
+ return;
+
+error_handling:
+ SET_BOOT_STATE(BOOT_FAILED);
+ cg2900_chip_startup_finished(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_hci_logger_config *logger_config;
+ struct cg2900_platform_data *pf_data;
+
+ if (!work) {
+ CG2900_ERR("work == NULL");
+ return;
+ }
+
+ /*
+ * Get the VS Power Switch Off command to use based on connected
+ * connectivity controller
+ */
+ pf_data = (struct cg2900_platform_data *)
+ cg2900_info->dev->parent->platform_data;
+ if (pf_data->get_power_switch_off_cmd)
+ skb = pf_data->get_power_switch_off_cmd(NULL);
+
+ /*
+ * Transmit the received command.
+ * If no command found for the device, just continue
+ */
+ if (!skb) {
+ CG2900_ERR("Could not retrieve PowerSwitchOff command");
+ goto shut_down_chip;
+ }
+
+ logger_config = cg2900_get_hci_logger_config();
+
+ CG2900_DBG("Got power_switch_off command. Add H4 header and transmit");
+
+ /*
+ * 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;
+
+ SET_CLOSING_STATE(CLOSING_POWER_SWITCH_OFF);
+
+ if (logger_config)
+ cg2900_send_to_chip(skb, logger_config->bt_cmd_enable);
+ else
+ cg2900_send_to_chip(skb, false);
+
+ /*
+ * 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_interruptible(msecs_to_jiffies(POWER_SW_OFF_WAIT));
+
+shut_down_chip:
+ SET_CLOSING_STATE(CLOSING_SHUT_DOWN);
+
+ (void)cg2900_chip_shutdown_finished(0);
+
+ kfree(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)
+{
+ if (!work) {
+ CG2900_ERR("work == NULL");
+ return;
+ }
+
+ cg2900_chip_startup_finished(-EIO);
+
+ kfree(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;
+ bool file_found;
+ const struct firmware *patch_info;
+ const struct firmware *settings_info;
+
+ if (!work) {
+ CG2900_ERR("work == NULL");
+ return;
+ }
+
+ /* Check that we are in the right state */
+ if (cg2900_info->boot_state != BOOT_GET_FILES_TO_LOAD)
+ goto finished;
+
+ /* Open patch info file. */
+ err = request_firmware(&patch_info, PATCH_INFO_FILE,
+ cg2900_info->dev);
+ if (err) {
+ CG2900_ERR("Couldn't get patch info file (%d)", err);
+ goto error_handling;
+ }
+
+ /*
+ * Now we have the patch info file.
+ * See if we can find the right patch file as well
+ */
+ file_found = get_file_to_load(patch_info,
+ &(cg2900_info->patch_file_name));
+
+ /* Now we are finished with the patch info file */
+ release_firmware(patch_info);
+
+ if (!file_found) {
+ CG2900_ERR("Couldn't find patch file! Major error!");
+ goto error_handling;
+ }
+
+ /* Open settings info file. */
+ err = request_firmware(&settings_info,
+ FACTORY_SETTINGS_INFO_FILE,
+ cg2900_info->dev);
+ if (err) {
+ CG2900_ERR("Couldn't get settings info file (%d)", err);
+ goto error_handling;
+ }
+
+ /*
+ * Now we have the settings info file.
+ * See if we can find the right settings file as well.
+ */
+ file_found = get_file_to_load(settings_info,
+ &(cg2900_info->settings_file_name));
+
+ /* Now we are finished with the patch info file */
+ release_firmware(settings_info);
+
+ if (!file_found) {
+ CG2900_ERR("Couldn't find settings file! Major error!");
+ goto error_handling;
+ }
+
+ /* We now all info needed */
+ SET_BOOT_STATE(BOOT_DOWNLOAD_PATCH);
+ SET_DOWNLOAD_STATE(DOWNLOAD_PENDING);
+ SET_FILE_LOAD_STATE(FILE_LOAD_GET_PATCH);
+ cg2900_info->chunk_id = 0;
+ cg2900_info->file_offset = 0;
+ cg2900_info->fw_file = NULL;
+
+ /* OK. Now it is time to download the patches */
+ err = request_firmware(&(cg2900_info->fw_file),
+ cg2900_info->patch_file_name,
+ cg2900_info->dev);
+ if (err < 0) {
+ CG2900_ERR("Couldn't get patch file (%d)", err);
+ goto error_handling;
+ }
+ send_patch_file();
+
+ goto finished;
+
+error_handling:
+ SET_BOOT_STATE(BOOT_FAILED);
+ cg2900_chip_startup_finished(-EIO);
+finished:
+ kfree(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)
+{
+ if (!work) {
+ CG2900_ERR("work == NULL");
+ return;
+ }
+
+ /* Continue to send patches or settings to the controller */
+ if (cg2900_info->file_load_state == FILE_LOAD_GET_PATCH)
+ send_patch_file();
+ else if (cg2900_info->file_load_state == FILE_LOAD_GET_STATIC_SETTINGS)
+ send_settings_file();
+ else
+ CG2900_INFO("No more files to load");
+
+ kfree(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(u8 *data)
+{
+ u8 status = data[0];
+
+ CG2900_INFO("Received Reset complete event with status 0x%X", status);
+
+ if (CLOSING_RESET != cg2900_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.
+ */
+ CG2900_ERR("Command complete for HciReset received with "
+ "error 0x%X !", status);
+ }
+
+ create_work_item(work_power_off_chip);
+
+ 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(u8 *data)
+{
+ u8 status = data[0];
+
+ CG2900_INFO("Received Store_in_FS complete event with status 0x%X",
+ status);
+
+ if (cg2900_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 */
+ SET_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 */
+ create_and_send_bt_cmd(&cmd, sizeof(cmd));
+ } else {
+ CG2900_ERR("Command complete for StoreInFS received with error "
+ "0x%X", status);
+ SET_BOOT_STATE(BOOT_FAILED);
+ create_work_item(work_reset_after_error);
+ }
+
+ 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(u8 *data)
+{
+ u8 status = data[0];
+
+ if ((cg2900_info->boot_state != BOOT_DOWNLOAD_PATCH) ||
+ (cg2900_info->download_state != DOWNLOAD_PENDING))
+ return false;
+
+ if (HCI_BT_ERROR_NO_ERROR == status)
+ create_work_item(work_cont_file_download);
+ else {
+ CG2900_ERR("Command complete for WriteFileBlock received with"
+ " error 0x%X", status);
+ SET_DOWNLOAD_STATE(DOWNLOAD_FAILED);
+ SET_BOOT_STATE(BOOT_FAILED);
+ if (cg2900_info->fw_file) {
+ release_firmware(cg2900_info->fw_file);
+ cg2900_info->fw_file = NULL;
+ }
+ create_work_item(work_reset_after_error);
+ }
+
+ 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(u8 status)
+{
+ if ((cg2900_info->boot_state != BOOT_DOWNLOAD_PATCH) ||
+ (cg2900_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) {
+ CG2900_ERR("Command status for WriteFileBlock received with"
+ " error 0x%X", status);
+ SET_DOWNLOAD_STATE(DOWNLOAD_FAILED);
+ SET_BOOT_STATE(BOOT_FAILED);
+ if (cg2900_info->fw_file) {
+ release_firmware(cg2900_info->fw_file);
+ cg2900_info->fw_file = NULL;
+ }
+ create_work_item(work_reset_after_error);
+ }
+
+ 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(u8 *data)
+{
+ u8 status = data[0];
+
+ if (CLOSING_POWER_SWITCH_OFF != cg2900_info->closing_state)
+ return false;
+
+ CG2900_INFO("handle_vs_power_switch_off_cmd_complete");
+
+ /*
+ * 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)
+ CG2900_ERR("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(u8 *data)
+{
+ u8 status = data[0];
+ struct bt_vs_bt_enable_cmd cmd;
+
+ if (cg2900_info->boot_state != BOOT_ACTIVATE_PATCHES_AND_SETTINGS)
+ return false;
+
+ CG2900_INFO("handle_vs_system_reset_cmd_complete");
+
+ if (HCI_BT_ERROR_NO_ERROR == status) {
+ /*
+ * We are now almost finished. Shut off BT Core. It will be
+ * re-enabled by the Bluetooth driver when needed.
+ */
+ SET_BOOT_STATE(BOOT_DISABLE_BT);
+ cmd.op_code = cpu_to_le16(CG2900_BT_OP_VS_BT_ENABLE);
+ cmd.plen = BT_PARAM_LEN(sizeof(cmd));
+ cmd.enable = CG2900_BT_DISABLE;
+ create_and_send_bt_cmd(&cmd, sizeof(cmd));
+ } else {
+ CG2900_ERR("Received Reset complete event with status 0x%X",
+ status);
+ SET_BOOT_STATE(BOOT_FAILED);
+ cg2900_chip_startup_finished(-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(u8 status)
+{
+ if (cg2900_info->boot_state != BOOT_DISABLE_BT)
+ return false;
+
+ CG2900_INFO("handle_vs_bt_enable_cmd_status");
+
+ /*
+ * Only do something if there is an error. Otherwise we will wait for
+ * CmdComplete.
+ */
+ if (HCI_BT_ERROR_NO_ERROR != status) {
+ CG2900_ERR("Received BtEnable status event with status 0x%X",
+ status);
+ SET_BOOT_STATE(BOOT_FAILED);
+ cg2900_chip_startup_finished(-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(u8 *data)
+{
+ u8 status = data[0];
+
+ if (cg2900_info->boot_state != BOOT_DISABLE_BT)
+ return false;
+
+ CG2900_INFO("handle_vs_bt_enable_cmd_complete");
+
+ if (HCI_BT_ERROR_NO_ERROR == status) {
+ /*
+ * The boot sequence is now finished successfully.
+ * Set states and signal to waiting thread.
+ */
+ SET_BOOT_STATE(BOOT_READY);
+ cg2900_chip_startup_finished(0);
+ } else {
+ CG2900_ERR("Received BtEnable complete event with status 0x%X",
+ status);
+ SET_BOOT_STATE(BOOT_FAILED);
+ cg2900_chip_startup_finished(-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 sk_buff *skb)
+{
+ bool pkt_handled = false;
+ /* skb cannot be NULL here so it is safe to de-reference */
+ u8 *data = &(skb->data[CG2900_SKB_RESERVE]);
+ 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);
+
+ CG2900_DBG_DATA("Received Command Complete: op_code = 0x%04X",
+ op_code);
+ /* Move to first byte after OCF */
+ data += sizeof(*cmd_complete);
+
+ if (op_code == HCI_OP_RESET)
+ pkt_handled = handle_reset_cmd_complete(data);
+ else if (op_code == CG2900_BT_OP_VS_STORE_IN_FS)
+ pkt_handled = handle_vs_store_in_fs_cmd_complete(data);
+ else if (op_code == CG2900_BT_OP_VS_WRITE_FILE_BLOCK)
+ pkt_handled =
+ handle_vs_write_file_block_cmd_complete(data);
+ else if (op_code == CG2900_BT_OP_VS_POWER_SWITCH_OFF)
+ pkt_handled =
+ handle_vs_power_switch_off_cmd_complete(data);
+ else if (op_code == CG2900_BT_OP_VS_SYSTEM_RESET)
+ pkt_handled = handle_vs_system_reset_cmd_complete(data);
+ else if (op_code == CG2900_BT_OP_VS_BT_ENABLE)
+ pkt_handled = handle_vs_bt_enable_cmd_complete(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);
+
+ CG2900_DBG_DATA("Received Command Status: op_code = 0x%04X",
+ op_code);
+
+ if (op_code == CG2900_BT_OP_VS_WRITE_FILE_BLOCK)
+ pkt_handled = handle_vs_write_file_block_cmd_status
+ (cmd_status->status);
+ else if (op_code == CG2900_BT_OP_VS_BT_ENABLE)
+ pkt_handled = handle_vs_bt_enable_cmd_status
+ (cmd_status->status);
+ } 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.
+ * @skb: Data packet.
+ * @dev: Pointer to cg2900_device struct.
+ *
+ * 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 sk_buff *skb,
+ struct cg2900_device *dev)
+{
+ /*
+ * 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(&(cg2900_info->tx_bt_lock));
+
+ if ((cg2900_info->tx_nr_pkts_allowed_bt) > 0) {
+ (cg2900_info->tx_nr_pkts_allowed_bt)--;
+ CG2900_DBG("New tx_nr_pkts_allowed_bt = %d",
+ cg2900_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 (cg2900_get_bt_audio_dev() == dev) {
+ struct hci_command_hdr *hdr = (struct hci_command_hdr *)
+ (skb->data + HCI_H4_SIZE);
+
+ cg2900_info->audio_bt_cmd_op = le16_to_cpu(hdr->opcode);
+ CG2900_DBG("Sending cmd from audio driver, saving "
+ "OpCode = 0x%x",
+ cg2900_info->audio_bt_cmd_op);
+ }
+
+ cg2900_send_to_chip(skb, dev->logger_enabled);
+ } else {
+ CG2900_DBG("Not allowed to send cmd to controller, "
+ "storing in TX queue.");
+
+ cg2900_skb_data(skb)->dev = dev;
+ skb_queue_tail(&cg2900_info->tx_queue_bt, skb);
+ }
+ spin_unlock_bh(&(cg2900_info->tx_bt_lock));
+}
+
+/**
+ * transmit_skb_with_flow_ctrl_fm() - Send the FM skb to the
controller if it is allowed or queue it.
+ * @skb: Data packet.
+ * @dev: Pointer to cg2900_device struct.
+ *
+ * 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 sk_buff *skb,
+ struct cg2900_device *dev)
+{
+ u8 cmd_func = CG2900_FM_CMD_PARAM_NONE;
+ u16 cmd_id = CG2900_FM_CMD_NONE;
+
+ fm_parse_cmd(&(skb->data[0]), &cmd_func, &cmd_id);
+
+ /*
+ * If there 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(&cg2900_info->tx_fm_lock);
+ fm_reset_flow_ctrl();
+ spin_unlock_bh(&cg2900_info->tx_fm_lock);
+ cg2900_send_to_chip(skb, dev->logger_enabled);
+ return;
+ }
+
+ /*
+ * If there 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(&cg2900_info->tx_fm_lock);
+ if (cg2900_get_fm_radio_dev() == dev &&
+ cg2900_info->audio_fm_cmd_id == CG2900_FM_CMD_NONE) {
+ cg2900_info->hci_fm_cmd_func = cmd_func;
+ CG2900_DBG("hci_fm_cmd_func 0x%X",
+ cg2900_info->hci_fm_cmd_func);
+ /* If a GotoMode command update FM mode */
+ fm_update_mode(&(skb->data[0]));
+ cg2900_send_to_chip(skb, dev->logger_enabled);
+ } else if (cg2900_get_fm_audio_dev() == dev &&
+ cg2900_info->hci_fm_cmd_func == CG2900_FM_CMD_PARAM_NONE &&
+ cg2900_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.
+ */
+ cg2900_info->audio_fm_cmd_id = cmd_id;
+ CG2900_DBG("audio_fm_cmd_id 0x%X",
+ cg2900_info->audio_fm_cmd_id);
+ cg2900_send_to_chip(skb, dev->logger_enabled);
+ } else {
+ CG2900_DBG("Not allowed to send cmd to controller, storing in "
+ "TX queue");
+
+ cg2900_skb_data(skb)->dev = dev;
+ skb_queue_tail(&cg2900_info->tx_queue_fm, skb);
+ }
+ spin_unlock_bh(&(cg2900_info->tx_fm_lock));
+}
+
+/**
+ * chip_startup() - Start the chip.
+ * @dev: Chip info.
+ *
+ * The chip_startup() function downloads patches and other needed start
+ * procedures.
+ *
+ * Returns:
+ * 0 if there is no error.
+ */
+static int chip_startup(struct cg2900_chip_dev *dev)
+{
+ /* Start the boot sequence */
+ SET_BOOT_STATE(BOOT_GET_FILES_TO_LOAD);
+ create_work_item(work_load_patch_and_settings);
+
+ return 0;
+}
+
+/**
+ * chip_shutdown() - Shut down the chip.
+ * @dev: Chip info.
+ *
+ * The chip_shutdown() function shuts down the chip by sending PowerSwitchOff
+ * command.
+ *
+ * Returns:
+ * 0 if there is no error.
+ */
+static int chip_shutdown(struct cg2900_chip_dev *dev)
+{
+ struct hci_command_hdr cmd;
+
+ /*
+ * Transmit HCI reset command to ensure the chip is using
+ * the correct transport and to put BT part in reset.
+ */
+ SET_CLOSING_STATE(CLOSING_RESET);
+ cmd.opcode = cpu_to_le16(HCI_OP_RESET);
+ cmd.plen = 0; /* No parameters for HCI reset */
+ create_and_send_bt_cmd(&cmd, sizeof(cmd));
+
+ return 0;
+}
+
+/**
+ * data_to_chip() - Called when data shall be sent to the chip.
+ * @dev: Chip info.
+ * @cg2900_dev: CG2900 user for this packet.
+ * @skb: Packet to transmit.
+ *
+ * The data_to_chip() function updates flow control and itself
+ * transmits packet to controller if packet is BT command or FM radio.
+ *
+ * Returns:
+ * true if packet is handled by this driver.
+ * false otherwise.
+ */
+static bool data_to_chip(struct cg2900_chip_dev *dev,
+ struct cg2900_device *cg2900_dev,
+ struct sk_buff *skb)
+{
+ bool packet_handled = false;
+
+ if (cg2900_dev->h4_channel == CHANNEL_BT_CMD) {
+ transmit_skb_with_flow_ctrl_bt(skb, cg2900_dev);
+ packet_handled = true;
+ } else if (cg2900_dev->h4_channel == CHANNEL_FM_RADIO) {
+ transmit_skb_with_flow_ctrl_fm(skb, cg2900_dev);
+ packet_handled = true;
+ }
+
+ return packet_handled;
+}
+
+/**
+ * data_from_chip() - Called when data shall be sent to 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.
+ *
+ * Returns:
+ * true if packet is handled by this driver.
+ * false otherwise.
+ */
+static bool data_from_chip(struct cg2900_chip_dev *dev,
+ struct cg2900_device *cg2900_dev,
+ struct sk_buff *skb)
+{
+ bool packet_handled;
+ int h4_channel;
+
+ h4_channel = skb->data[0];
+
+ /* First check if we should update flow control */
+ if (h4_channel == CHANNEL_BT_EVT)
+ update_flow_ctrl_bt(skb);
+ else if (h4_channel == CHANNEL_FM_RADIO)
+ update_flow_ctrl_fm(skb);
+
+ /* Then check if this is a response to data we have sent */
+ packet_handled = handle_rx_data_bt_evt(skb);
+
+ return packet_handled;
+}
+
+/**
+ * get_h4_channel() - Returns H:4 channel for the name.
+ * @name: Chip info.
+ * @h4_channel: CG2900 user for this packet.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -ENXIO if channel is not found.
+ */
+static int get_h4_channel(char *name, int *h4_channel)
+{
+ int i;
+ int err = -ENXIO;
+
+ *h4_channel = -1;
+
+ for (i = 0; *h4_channel == -1 && i < ARRAY_SIZE(cg2900_channels); i++) {
+ if (0 == strncmp(name, cg2900_channels[i].name,
+ CG2900_MAX_NAME_SIZE)) {
+ /* Device found. Return H4 channel */
+ *h4_channel = cg2900_channels[i].h4_channel;
+ err = 0;
+ }
+ }
+
+ return err;
+}
+
+/**
+ * is_bt_audio_user() - Checks if this packet is for the BT audio user.
+ * @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(int h4_channel, const struct sk_buff * const skb)
+{
+ struct hci_event_hdr *hdr = (struct hci_event_hdr *)
+ &(skb->data[CG2900_SKB_RESERVE]);
+ u8 *payload = (u8 *)(hdr + 1); /* follows header */
+ u16 opcode = 0;
+
+ if (h4_channel != CHANNEL_BT_EVT)
+ return false;
+
+ if (HCI_BT_EVT_CMD_COMPLETE == hdr->evt)
+ opcode = le16_to_cpu(
+ ((struct hci_ev_cmd_complete *)payload)->opcode);
+ else if (HCI_BT_EVT_CMD_STATUS == hdr->evt)
+ opcode = le16_to_cpu(
+ ((struct hci_ev_cmd_status *)payload)->opcode);
+
+ if (opcode != 0 && opcode == cg2900_info->audio_bt_cmd_op) {
+ CG2900_DBG("BT OpCode match = 0x%04X", opcode);
+ cg2900_info->audio_bt_cmd_op = CG2900_BT_OPCODE_NONE;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+/**
+ * is_fm_audio_user() - Checks if this packet is for the FM audio user.
+ * @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(int h4_channel, 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;
+ bool bt_audio = false;
+
+ fm_parse_event(&(skb->data[0]), &event, &cmd_func, &cmd_id, &irpt_val);
+
+ if (h4_channel == CHANNEL_FM_RADIO) {
+ /* Check if command complete event FM legacy interface. */
+ if ((event == CG2900_FM_EVENT_CMD_COMPLETE) &&
+ (cmd_func == CG2900_FM_CMD_PARAM_WRITECOMMAND) &&
+ (cmd_id == cg2900_info->audio_fm_cmd_id)) {
+ CG2900_DBG("FM Audio Function Code match = 0x%04X",
+ cmd_id);
+ bt_audio = true;
+ goto finished;
+ }
+
+ /* Check if Interrupt legacy interface. */
+ if ((event == CG2900_FM_EVENT_INTERRUPT) &&
+ (fm_is_do_cmd_irpt(irpt_val)) &&
+ (cg2900_info->tx_fm_audio_awaiting_irpt))
+ bt_audio = true;
+ }
+
+finished:
+ return bt_audio;
+}
+
+/**
+ * last_bt_user_removed() - Called when last BT user is removed.
+ * @dev: Chip handler info.
+ *
+ * Clears out TX queue for BT.
+ */
+static void last_bt_user_removed(struct cg2900_chip_dev *dev)
+{
+ spin_lock_bh(&cg2900_info->tx_bt_lock);
+
+ skb_queue_purge(&cg2900_info->tx_queue_bt);
+
+ /*
+ * Reset number of packets allowed and number of outstanding
+ * BT commands.
+ */
+ cg2900_info->tx_nr_pkts_allowed_bt = 1;
+ /* Reset the audio_bt_cmd_op. */
+ cg2900_info->audio_bt_cmd_op = CG2900_BT_OPCODE_NONE;
+ spin_unlock_bh(&cg2900_info->tx_bt_lock);
+}
+
+/**
+ * last_fm_user_removed() - Called when last FM user is removed.
+ * @dev: Chip handler info.
+ *
+ * Clears out TX queue for BT.
+ */
+static void last_fm_user_removed(struct cg2900_chip_dev *dev)
+{
+ spin_lock_bh(&cg2900_info->tx_fm_lock);
+ fm_reset_flow_ctrl();
+ spin_unlock_bh(&cg2900_info->tx_fm_lock);
+}
+
+/**
+ * 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)
+{
+ CG2900_INFO("CG2900: check_chip_support");
+
+ /*
+ * Check if this is a CG2900 revision.
+ * We do not care about the sub-version at the moment. Change this if
+ * necessary.
+ */
+ if ((dev->chip.manufacturer != CG2900_SUPP_MANUFACTURER) ||
+ (dev->chip.hci_revision != CG2900_PG1_SPECIAL_REV &&
+ (dev->chip.hci_revision < CG2900_SUPP_REVISION_MIN ||
+ dev->chip.hci_revision > CG2900_SUPP_REVISION_MAX))) {
+ CG2900_DBG("Chip not supported by CG2900 driver\n"
+ "\tMan: 0x%02X\n\tRev: 0x%04X\n\tSub: 0x%04X",
+ dev->chip.manufacturer, dev->chip.hci_revision,
+ dev->chip.hci_sub_version);
+ return false;
+ }
+
+ CG2900_INFO("Chip supported by the CG2900 driver");
+ /* Store needed data */
+ dev->user_data = cg2900_info;
+ memcpy(&(cg2900_info->chip_dev), dev, sizeof(*dev));
+ /* Set the callbacks */
+ dev->cb.chip_shutdown = chip_shutdown;
+ dev->cb.chip_startup = chip_startup;
+ dev->cb.data_from_chip = data_from_chip;
+ dev->cb.data_to_chip = data_to_chip;
+ dev->cb.get_h4_channel = get_h4_channel;
+ dev->cb.is_bt_audio_user = is_bt_audio_user;
+ dev->cb.is_fm_audio_user = is_fm_audio_user;
+ dev->cb.last_bt_user_removed = last_bt_user_removed;
+ dev->cb.last_fm_user_removed = last_fm_user_removed;
+
+ return true;
+}
+
+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 = 0;
+
+ CG2900_INFO("cg2900_chip_probe");
+
+ cg2900_info = kzalloc(sizeof(*cg2900_info), GFP_ATOMIC);
+ if (!cg2900_info) {
+ CG2900_ERR("Couldn't allocate cg2900_info");
+ err = -ENOMEM;
+ goto finished;
+ }
+
+ /*
+ * Initialize linked lists for HCI BT and FM commands
+ * that can't be sent due to internal CG2900 flow control.
+ */
+ skb_queue_head_init(&cg2900_info->tx_queue_bt);
+ skb_queue_head_init(&cg2900_info->tx_queue_fm);
+
+ /* Initialize the spin locks */
+ spin_lock_init(&(cg2900_info->tx_bt_lock));
+ spin_lock_init(&(cg2900_info->tx_fm_lock));
+
+ cg2900_info->tx_nr_pkts_allowed_bt = 1;
+ cg2900_info->audio_bt_cmd_op = CG2900_BT_OPCODE_NONE;
+ cg2900_info->audio_fm_cmd_id = CG2900_FM_CMD_NONE;
+ cg2900_info->hci_fm_cmd_func = CG2900_FM_CMD_PARAM_NONE;
+ cg2900_info->fm_radio_mode = FM_RADIO_MODE_IDLE;
+ cg2900_info->dev = &(pdev->dev);
+
+ cg2900_info->wq = create_singlethread_workqueue(WQ_NAME);
+ if (!cg2900_info->wq) {
+ CG2900_ERR("Could not create workqueue");
+ err = -ENOMEM;
+ goto err_handling_free_info;
+ }
+
+ /* Allocate file names that will be used, deallocated in cg2900_exit */
+ cg2900_info->patch_file_name = kzalloc(NAME_MAX + 1, GFP_ATOMIC);
+ if (!cg2900_info->patch_file_name) {
+ CG2900_ERR("Couldn't allocate name buffer for patch file.");
+ err = -ENOMEM;
+ goto err_handling_destroy_wq;
+ }
+ /* Allocate file names that will be used, deallocated in cg2900_exit */
+ cg2900_info->settings_file_name = kzalloc(NAME_MAX + 1, GFP_ATOMIC);
+ if (!cg2900_info->settings_file_name) {
+ CG2900_ERR("Couldn't allocate name buffers settings file.");
+ err = -ENOMEM;
+ goto err_handling_free_patch_name;
+ }
+
+ err = cg2900_register_chip_driver(&chip_support_callbacks);
+ if (err) {
+ CG2900_ERR("Couldn't register chip driver (%d)", err);
+ goto err_handling_free_settings_name;
+ }
+
+ goto finished;
+
+err_handling_free_settings_name:
+ kfree(cg2900_info->settings_file_name);
+err_handling_free_patch_name:
+ kfree(cg2900_info->patch_file_name);
+err_handling_destroy_wq:
+ destroy_workqueue(cg2900_info->wq);
+err_handling_free_info:
+ kfree(cg2900_info);
+ cg2900_info = NULL;
+finished:
+ 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)
+{
+ CG2900_INFO("cg2900_chip_remove");
+
+ if (!cg2900_info)
+ return 0;
+
+ kfree(cg2900_info->settings_file_name);
+ kfree(cg2900_info->patch_file_name);
+ destroy_workqueue(cg2900_info->wq);
+ kfree(cg2900_info);
+ cg2900_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)
+{
+ CG2900_INFO("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)
+{
+ CG2900_INFO("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/mfd/cg2900/cg2900_chip.h b/drivers/mfd/cg2900/cg2900_chip.h
new file mode 100644
index 0000000..5f1fe7a
--- /dev/null
+++ b/drivers/mfd/cg2900/cg2900_chip.h
@@ -0,0 +1,588 @@
+/*
+ * drivers/mfd/cg2900/cg2900_chip.h
+ *
+ * Copyright (C) ST-Ericsson SA 2010
+ * Authors:
+ * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@xxxxxxxxxxxxxx) for
ST-Ericsson.
+ * Henrik Possung (henrik.possung@xxxxxxxxxxxxxx) for ST-Ericsson.
+ * Josef Kindberg (josef.kindberg@xxxxxxxxxxxxxx) for ST-Ericsson.
+ * Dariusz Szymszak (dariusz.xd.szymczak@xxxxxxxxxxxxxx) for ST-Ericsson.
+ * Kjell Andersson (kjell.k.andersson@xxxxxxxxxxxxxx) 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_
+
+#include "hci_defines.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
+#define CG2900_SUPP_REVISION_MIN 0x0100
+#define CG2900_SUPP_REVISION_MAX 0x0200
+
+/* Specific chip version data */
+#define CG2900_PG1_REV 0x0101
+#define CG2900_PG2_REV 0x0200
+#define CG2900_PG1_SPECIAL_REV 0x0700
+
+/*
+ * 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[];
+} __attribute__((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; /* Really a data array of variable size */
+} __attribute__((packed));
+
+#define CG2900_VS_STORE_IN_FS_USR_ID_BD_ADDR 0xFE
+
+/* 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; /* Really a data array of variable size */
+} __attribute__((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;
+} __attribute__((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;
+} __attribute__((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;
+} __attribute__((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;
+ };
+} __attribute__((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;
+} __attribute__((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;
+} __attribute__((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;
+} __attribute__((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;
+} __attribute__((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
+ */
+} __attribute__((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;
+} __attribute__((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;
+ __u8 sbc_params[3]; /* replace when we actually enable WBS... */
+} __attribute__((packed));
+#define PORTCFG_SCO_SET_WBS(cfg, wbs) \
+ set_low_nibble(&(cfg).wbs_codec, (wbs))
+#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 */
+} __attribute__((packed));
+
+#define CG2900_MC_VS_DELETE_STREAM 0xFD67
+struct mc_vs_delete_stream_cmd {
+ __le16 opcode;
+ __u8 plen;
+ __u8 stream;
+} __attribute__((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[];
+} __attribute__((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;
+ };
+} __attribute__((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[];
+} __attribute__((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;
+} __attribute__((packed));
+
+/* FM legacy interrupt packet, PG1 style */
+struct fm_leg_irq_v1 {
+ __u8 param_length;
+ __u8 opcode;
+ __u8 event_id;
+ __le16 irq;
+} __attribute__((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;
+} __attribute__((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;
+} __attribute__((packed));
+
+#endif /* _CG2900_CHIP_H_ */
--
1.6.3.3
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/



Relevant Pages

  • [PATCH 4/9] mfd: Add chip handler for the ST-Ericsson STLC2690.
    ... This patch adds a chip handler for the ST-Ericsson STLC2690 Connectivity ... This patch adds all functionality needed towards the STLC2690, ... Support for ST-Ericsson CG2900 Connectivity Controller ... * for Bluetooth commands in the ST-Ericsson connectivity controller. ...
    (Linux-Kernel)
  • [PATCH 2/6] This patch adds support for the ST-Ericsson CG2900
    ... * for Bluetooth commands in the ST-Ericsson connectivity controller. ... * true if the command will generate an interrupt. ... * Parses a FM command packet and updates the FM mode state machine. ...
    (Linux-Kernel)
  • [PATCH 3/6] This patch adds support for the ST-Ericsson STLC2690
    ... Support for ST-Ericsson CG2900 Connectivity Controller ... * for Bluetooth commands in the ST-Ericsson connectivity controller. ... Complete event for a Reset command. ... * True, if packet was handled internally, ...
    (Linux-Kernel)
  • PC8477 Demo Program
    ... The PC8477 Demo Program is designed to allow access to all software commands and registers of National Semiconductor's PC8477 Advanced Floppy Disk Controller. ... The left center indicates the number of bytes transferred during the last command issued. ...
    (comp.sys.ibm.ps2.hardware)
  • Re: How to Define a global const class variable in MFC?
    ... selecting a specific embedded device the corresponding file will be loaded ... I also need to create many dialog controls for each command set to get ... *simplest* controller ... I've learned here but my original problem was to store those const variables ...
    (microsoft.public.vc.mfc)