[PATCH 04/11] mfd: Add support for STLC2690 controller

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This patch adds support for the ST-Ericsson STLC2690
framework. The STLC2690 is a chip supporting Bluetooth and FM radio.
Only Bluetooth however supports the H:4 channels where first byte
identifies current channel.
This patch adds support for allocating H:4 channels for both
Kernel and User space (using char devs) using MFD framework.

Signed-off-by: Par-Gunnar Hjalmdahl <par-gunnar.p.hjalmdahl@xxxxxxxxxxxxxx>
---
 drivers/mfd/Kconfig                |    8 +
 drivers/mfd/cg2900/Makefile        |    1 +
 drivers/mfd/cg2900/stlc2690_chip.c | 1673 ++++++++++++++++++++++++++++++++++++
 drivers/mfd/cg2900/stlc2690_chip.h |   47 +
 4 files changed, 1729 insertions(+), 0 deletions(-)
 create mode 100644 drivers/mfd/cg2900/stlc2690_chip.c
 create mode 100644 drivers/mfd/cg2900/stlc2690_chip.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 1328b5d..931cb58 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -282,6 +282,14 @@ config MFD_CG2900_CHIP
 	  H:4 interface where first byte determines channel used.
 	  CG2900 support Bluetooth, FM radio, and GPS.
 
+config MFD_STLC2690_CHIP
+	tristate "Support STLC2690 Connectivity controller"
+	depends on MFD_CG2900
+	help
+	  Support for ST-Ericsson STLC2690 Connectivity Controller.
+	  STLC2690 support Bluetooth and FM radio, however FM is not supported
+	  over H:4 interface.
+
 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 9a5e228..c4f4fc8 100644
--- a/drivers/mfd/cg2900/Makefile
+++ b/drivers/mfd/cg2900/Makefile
@@ -8,4 +8,5 @@ export-objs			:= cg2900_core.o cg2900_lib.o
 obj-$(CONFIG_MFD_CG2900)	+= cg2900_char_devices.o
 
 obj-$(CONFIG_MFD_CG2900_CHIP)	+= cg2900_chip.o
+obj-$(CONFIG_MFD_STLC2690_CHIP)	+= stlc2690_chip.o
 
diff --git a/drivers/mfd/cg2900/stlc2690_chip.c b/drivers/mfd/cg2900/stlc2690_chip.c
new file mode 100644
index 0000000..3507217
--- /dev/null
+++ b/drivers/mfd/cg2900/stlc2690_chip.c
@@ -0,0 +1,1673 @@
+/*
+ * 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 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/cg2900.h>
+#include <linux/mfd/core.h>
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci.h>
+
+#include "cg2900_core.h"
+#include "cg2900_lib.h"
+#include "stlc2690_chip.h"
+
+#define MAIN_DEV				(main_info->dev)
+#define BOOT_DEV				(info->user_in_charge->dev)
+
+#define WQ_NAME					"stlc2690_chip_wq"
+#define PATCH_INFO_FILE				"cg2900_patch_info.fw"
+#define FACTORY_SETTINGS_INFO_FILE		"cg2900_settings_info.fw"
+
+#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.
+ */
+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;
+};
+
+/**
+ * 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 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 stlc2690_chip_info *info)
+{
+	int err;
+	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_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;
+	/* 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;
+	bool file_found;
+	const struct firmware *patch_info;
+	const struct firmware *settings_info;
+	struct cg2900_work *my_work;
+	struct cg2900_chip_dev *dev;
+	struct stlc2690_chip_info *info;
+
+	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;
+
+	/* Open patch info file. */
+	err = request_firmware(&patch_info, PATCH_INFO_FILE,
+			       dev->dev);
+	if (err) {
+		dev_err(BOOT_DEV, "Couldn't get patch info file (%d)\n", err);
+		goto error_handling;
+	}
+
+	/*
+	 * Now we have the patch info file.
+	 * See if we can find the right patch file as well
+	 */
+	file_found = cg2900_get_file_name(patch_info, &info->patch_file_name,
+					  info->chip_dev->chip.hci_revision,
+					  info->chip_dev->chip.hci_sub_version);
+
+	/* Now we are finished with the patch info file */
+	release_firmware(patch_info);
+
+	if (!file_found) {
+		dev_err(BOOT_DEV, "Couldn't find patch file! Major error\n");
+		goto error_handling;
+	}
+
+	/* Open settings info file. */
+	err = request_firmware(&settings_info,
+			       FACTORY_SETTINGS_INFO_FILE,
+			       dev->dev);
+	if (err) {
+		dev_err(BOOT_DEV, "Couldn't get settings info file (%d)\n",
+			err);
+		goto error_handling;
+	}
+
+	/*
+	 * Now we have the settings info file.
+	 * See if we can find the right settings file as well.
+	 */
+	file_found = cg2900_get_file_name(settings_info,
+					  &info->settings_file_name,
+					  info->chip_dev->chip.hci_revision,
+					  info->chip_dev->chip.hci_sub_version);
+
+	/* Now we are finished with the patch info file */
+	release_firmware(settings_info);
+
+	if (!file_found) {
+		dev_err(BOOT_DEV, "Couldn't find settings file! Major error\n");
+		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(info);
+
+	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(info);
+	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_interruptible_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_interruptible_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_interruptible_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_interruptible(
+				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_interruptible_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_interruptible_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_interruptible_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,
+		.data_size = sizeof(btcmd_data),
+	},
+	{
+		.name = "cg2900-btacl",
+		.platform_data = &btacl_data,
+		.data_size = sizeof(btacl_data),
+	},
+	{
+		.name = "cg2900-btevt",
+		.platform_data = &btevt_data,
+		.data_size = sizeof(btevt_data),
+	},
+	{
+		.name = "cg2900-hcilogger",
+		.platform_data = &hci_logger_data,
+		.data_size = sizeof(hci_logger_data),
+	},
+	{
+		.name = "cg2900-core",
+		.platform_data = &core_data,
+		.data_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,
+		.data_size = sizeof(char_btcmd_data),
+	},
+	{
+		.name = "cg2900-chardev",
+		.id = 1,
+		.platform_data = &char_btacl_data,
+		.data_size = sizeof(char_btacl_data),
+	},
+	{
+		.name = "cg2900-chardev",
+		.id = 2,
+		.platform_data = &char_btevt_data,
+		.data_size = sizeof(char_btevt_data),
+	},
+	{
+		.name = "cg2900-chardev",
+		.id = 7,
+		.platform_data = &char_hci_logger_data,
+		.data_size = sizeof(char_hci_logger_data),
+	},
+	{
+		.name = "cg2900-chardev",
+		.id = 8,
+		.platform_data = &char_core_data,
+		.data_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);
+
+	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;
+	}
+
+	main_info->cell_base_id += 30;
+	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;
+
+	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/mfd/cg2900/stlc2690_chip.h b/drivers/mfd/cg2900/stlc2690_chip.h
new file mode 100644
index 0000000..63670c1
--- /dev/null
+++ b/drivers/mfd/cg2900/stlc2690_chip.h
@@ -0,0 +1,47 @@
+/*
+ * 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 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[];
+} __attribute__((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[];
+} __attribute__((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_ */
-- 
1.7.3.2

--
To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Bluez Devel]     [Linux Wireless Networking]     [Linux Wireless Personal Area Networking]     [Linux ATH6KL]     [Linux USB Devel]     [Linux Media Drivers]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Big List of Linux Books]

  Powered by Linux