Linux bluetooth development
 help / color / mirror / Atom feed
* [PATCH 8/9] Bluetooth: Add support for the ST-Ericsson CG2900.
From: Par-Gunnar Hjalmdahl @ 2010-10-22 10:40 UTC (permalink / raw)
  To: linus.walleij, linux-bluetooth, linux-kernel

This patch adds support for the ST-Ericsson CG2900 Connectivity
Combo controller.
This patch registers to the Bluetooth stack and, when opened, it
registers to the Bluetooth channels in the CG2900 MFD driver.

Signed-off-by: Par-Gunnar Hjalmdahl <par-gunnar.p.hjalmdahl@stericsson.com>
---
 drivers/bluetooth/Kconfig    |    7 +
 drivers/bluetooth/Makefile   |    1 +
 drivers/bluetooth/btcg2900.c |  925 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 933 insertions(+), 0 deletions(-)
 create mode 100644 drivers/bluetooth/btcg2900.c

diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig
index 02deef4..9ca8d69 100644
--- a/drivers/bluetooth/Kconfig
+++ b/drivers/bluetooth/Kconfig
@@ -219,4 +219,11 @@ config BT_ATH3K
 	  Say Y here to compile support for "Atheros firmware download driver"
 	  into the kernel or say M to compile it as module (ath3k).

+config BT_CG2900
+	tristate "ST-Ericsson CG2900 driver"
+	depends on MFD_CG2900 && BT
+	help
+	  Select if ST-Ericsson CG2900 Connectivity controller shall be used as
+	  Bluetooth controller for BlueZ.
+
 endmenu
diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile
index 71bdf13..1f8ce2d 100644
--- a/drivers/bluetooth/Makefile
+++ b/drivers/bluetooth/Makefile
@@ -16,6 +16,7 @@ obj-$(CONFIG_BT_HCIBTUSB)	+= btusb.o
 obj-$(CONFIG_BT_HCIBTSDIO)	+= btsdio.o

 obj-$(CONFIG_BT_ATH3K)		+= ath3k.o
+obj-$(CONFIG_BT_CG2900)		+= btcg2900.o
 obj-$(CONFIG_BT_MRVL)		+= btmrvl.o
 obj-$(CONFIG_BT_MRVL_SDIO)	+= btmrvl_sdio.o

diff --git a/drivers/bluetooth/btcg2900.c b/drivers/bluetooth/btcg2900.c
new file mode 100644
index 0000000..11d6518
--- /dev/null
+++ b/drivers/bluetooth/btcg2900.c
@@ -0,0 +1,925 @@
+/*
+ * Bluetooth driver for ST-Ericsson CG2900 connectivity controller.
+ *
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Authors:
+ * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com)
+ * Henrik Possung (henrik.possung@stericsson.com)
+ * Josef Kindberg (josef.kindberg@stericsson.com)
+ * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com)
+ * Kjell Andersson (kjell.k.andersson@stericsson.com)
+ *
+ * License terms:  GNU General Public License (GPL), version 2
+ */
+
+#include <asm/byteorder.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/platform_device.h>
+#include <linux/skbuff.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 <net/bluetooth/hci_core.h>
+
+#define BT_VS_BT_ENABLE			0xFF10
+
+#define VS_BT_DISABLE			0x00
+#define VS_BT_ENABLE			0x01
+
+#define BT_HEADER_LENGTH		0x03
+
+#define STLC2690_HCI_REV		0x0600
+#define CG2900_PG1_HCI_REV		0x0101
+#define CG2900_PG2_HCI_REV		0x0200
+#define CG2900_PG1_SPECIAL_HCI_REV	0x0700
+
+#define NAME				"BTCG2900 "
+
+/* Wait for 5 seconds for a response to our requests */
+#define RESP_TIMEOUT			5000
+
+/* Bluetooth error codes */
+#define HCI_ERR_NO_ERROR		0x00
+#define HCI_ERR_CMD_DISALLOWED		0x0C
+
+/**
+ * enum reset_state - RESET-states of the HCI driver.
+ *
+ * @RESET_IDLE:		No reset in progress.
+ * @RESET_ACTIVATED:	Reset in progress.
+ * @RESET_UNREGISTERED:	hdev is unregistered.
+ */
+
+enum reset_state {
+	RESET_IDLE,
+	RESET_ACTIVATED,
+	RESET_UNREGISTERED
+};
+
+/**
+ * enum enable_state - ENABLE-states of the HCI driver.
+ *
+ * @ENABLE_IDLE:			The HCI driver is loaded but not opened.
+ * @ENABLE_WAITING_BT_ENABLED_CC:	The HCI driver is waiting for a command
+ *					complete event from the BT chip as a
+ *					response to a BT Enable (true) command.
+ * @ENABLE_BT_ENABLED:			The BT chip is enabled.
+ * @ENABLE_WAITING_BT_DISABLED_CC:	The HCI driver is waiting for a command
+ *					complete event from the BT chip as a
+ *					response to a BT Enable (false) command.
+ * @ENABLE_BT_DISABLED:			The BT chip is disabled.
+ * @ENABLE_BT_ERROR:			The HCI driver is in a bad state, some
+ *					thing has failed and is not expected to
+ *					work properly.
+ */
+enum enable_state {
+	ENABLE_IDLE,
+	ENABLE_WAITING_BT_ENABLED_CC,
+	ENABLE_BT_ENABLED,
+	ENABLE_WAITING_BT_DISABLED_CC,
+	ENABLE_BT_DISABLED,
+	ENABLE_BT_ERROR
+};
+
+/**
+ * struct btcg2900_info - Specifies HCI driver private data.
+ *
+ * This type specifies CG2900 HCI driver private data.
+ *
+ * @bt_cmd:		Device structure for BT command channel.
+ * @bt_evt:		Device structure for BT event channel.
+ * @bt_acl:		Device structure for BT ACL channel.
+ * @pdev:		Device structure for platform device.
+ * @hdev:		Device structure for HCI device.
+ * @reset_state:	Device enum for HCI driver reset state.
+ * @enable_state:	Device enum for HCI driver BT enable state.
+ */
+struct btcg2900_info {
+	struct cg2900_device	*bt_cmd;
+	struct cg2900_device	*bt_evt;
+	struct cg2900_device	*bt_acl;
+	struct platform_device	*pdev;
+	struct hci_dev		*hdev;
+	enum reset_state	reset_state;
+	enum enable_state	enable_state;
+};
+
+/**
+ * struct dev_info - Specifies private data used when receiving
callbacks from CG2900 driver.
+ *
+ * @hdev:		Device structure for HCI device.
+ * @hci_data_type:	Type of data according to BlueZ.
+ */
+struct dev_info {
+	struct hci_dev	*hdev;
+	u8		hci_data_type;
+};
+
+/**
+ * struct vs_bt_enable_cmd - Specifies HCI VS Bluetooth_Enable command.
+ *
+ * @op_code:	HCI command op code.
+ * @len:	Parameter length of command.
+ * @enable:	0 for disable BT, 1 for enable BT.
+ */
+struct vs_bt_enable_cmd {
+	__le16	op_code;
+	u8	len;
+	u8	enable;
+} __attribute__((packed));
+
+static struct btcg2900_info *btcg2900_info;
+
+/*
+ * hci_wait_queue - Main Wait Queue in HCI driver.
+ */
+static DECLARE_WAIT_QUEUE_HEAD(hci_wait_queue);
+
+/* Internal function declarations */
+static int register_bluetooth(void);
+
+/* Internal functions */
+
+/**
+ * get_bt_enable_cmd() - Get HCI BT enable command.
+ * @bt_enable:	true if Bluetooth IP shall be enabled, false otherwise.
+ *
+ * Returns:
+ *   NULL if no command shall be sent,
+ *   sk_buffer with command otherwise.
+ */
+struct sk_buff *get_bt_enable_cmd(bool bt_enable)
+{
+	struct sk_buff *skb;
+	struct vs_bt_enable_cmd *cmd;
+	struct cg2900_rev_data rev_data;
+
+	if (!cg2900_get_local_revision(&rev_data)) {
+		BT_ERR(NAME "Couldn't get revision");
+		return NULL;
+	}
+
+	/* If connected chip does not support the command return NULL */
+	if (CG2900_PG1_SPECIAL_HCI_REV != rev_data.revision &&
+	    CG2900_PG1_HCI_REV != rev_data.revision &&
+	    CG2900_PG2_HCI_REV != rev_data.revision)
+		return NULL;
+
+	/* CG2900 used */
+	skb = cg2900_alloc_skb(sizeof(*cmd), GFP_KERNEL);
+	if (!skb) {
+		BT_ERR(NAME "Could not allocate skb");
+		return NULL;
+	}
+
+	cmd = (struct vs_bt_enable_cmd *)skb_put(skb, sizeof(*cmd));
+	cmd->op_code = cpu_to_le16(BT_VS_BT_ENABLE);
+	cmd->len = sizeof(*cmd) - BT_HEADER_LENGTH;
+	if (bt_enable)
+		cmd->enable = VS_BT_ENABLE;
+	else
+		cmd->enable = VS_BT_DISABLE;
+
+	return skb;
+}
+
+/**
+ * remove_bt_users() - Unregister and remove any existing BT users.
+ * @info:	HCI driver info structure.
+ */
+static void remove_bt_users(struct btcg2900_info *info)
+{
+	if (info->bt_cmd) {
+		kfree(info->bt_cmd->user_data);
+		info->bt_cmd->user_data = NULL;
+		cg2900_deregister_user(info->bt_cmd);
+		info->bt_cmd = NULL;
+	}
+
+	if (info->bt_evt) {
+		kfree(info->bt_evt->user_data);
+		info->bt_evt->user_data = NULL;
+		cg2900_deregister_user(info->bt_evt);
+		info->bt_evt = NULL;
+	}
+
+	if (info->bt_acl) {
+		kfree(info->bt_acl->user_data);
+		info->bt_acl->user_data = NULL;
+		cg2900_deregister_user(info->bt_acl);
+		info->bt_acl = NULL;
+	}
+}
+
+/**
+ * hci_read_cb() - Callback for handling data received from CG2900 driver.
+ * @dev:	Device receiving data.
+ * @skb:	Buffer with data coming from device.
+ */
+static void hci_read_cb(struct cg2900_device *dev, struct sk_buff *skb)
+{
+	int err = 0;
+	struct dev_info *dev_info;
+	struct hci_event_hdr *evt;
+	struct hci_ev_cmd_complete *cmd_complete;
+	struct hci_ev_cmd_status *cmd_status;
+	u8 status;
+
+	if (!skb) {
+		BT_ERR(NAME "NULL supplied for skb");
+		return;
+	}
+
+	if (!dev) {
+		BT_ERR(NAME "dev == NULL");
+		goto fin_free_skb;
+	}
+
+	dev_info = (struct dev_info *)dev->user_data;
+
+	if (!dev_info) {
+		BT_ERR(NAME "dev_info == NULL");
+		goto fin_free_skb;
+	}
+
+	evt = (struct hci_event_hdr *)skb->data;
+	cmd_complete = (struct hci_ev_cmd_complete *)(skb->data + sizeof(*evt));
+	cmd_status = (struct hci_ev_cmd_status *)(skb->data + sizeof(*evt));
+
+	/*
+	 * Check if HCI Driver it self is expecting a Command Complete packet
+	 * from the chip after a BT Enable command.
+	 */
+	if ((btcg2900_info->enable_state == ENABLE_WAITING_BT_ENABLED_CC ||
+	     btcg2900_info->enable_state == ENABLE_WAITING_BT_DISABLED_CC) &&
+	    btcg2900_info->bt_evt->h4_channel == dev->h4_channel &&
+	    evt->evt == HCI_EV_CMD_COMPLETE &&
+	    le16_to_cpu(cmd_complete->opcode) == BT_VS_BT_ENABLE) {
+		/*
+		 * This is the command complete event for
+		 * the HCI_Cmd_VS_Bluetooth_Enable.
+		 * Check result and update state.
+		 *
+		 * The BT chip is enabled/disabled. Either it was enabled/
+		 * disabled now (status NO_ERROR) or it was already enabled/
+		 * disabled (assuming status CMD_DISALLOWED is already enabled/
+		 * disabled).
+		 */
+		status = *(skb->data + sizeof(*evt) + sizeof(*cmd_complete));
+		if (status != HCI_ERR_NO_ERROR &&
+		    status != HCI_ERR_CMD_DISALLOWED) {
+			BT_ERR(NAME "Could not enable/disable BT core (0x%X)",
+				   status);
+			BT_DBG("New enable_state: ENABLE_BT_ERROR");
+			btcg2900_info->enable_state = ENABLE_BT_ERROR;
+			goto fin_free_skb;
+		}
+
+		if (btcg2900_info->enable_state ==
+				ENABLE_WAITING_BT_ENABLED_CC) {
+			BT_DBG("New enable_state: ENABLE_BT_ENABLED");
+			btcg2900_info->enable_state = ENABLE_BT_ENABLED;
+			BT_INFO("CG2900 BT core is enabled");
+		} else {
+			BT_DBG("New enable_state: ENABLE_BT_DISABLED");
+			btcg2900_info->enable_state = ENABLE_BT_DISABLED;
+			BT_INFO("CG2900 BT core is disabled");
+		}
+
+		/* Wake up whom ever is waiting for this result. */
+		wake_up_interruptible(&hci_wait_queue);
+		goto fin_free_skb;
+	} else if ((btcg2900_info->enable_state ==
+			ENABLE_WAITING_BT_DISABLED_CC ||
+		    btcg2900_info->enable_state ==
+			ENABLE_WAITING_BT_ENABLED_CC) &&
+		   btcg2900_info->bt_evt->h4_channel == dev->h4_channel &&
+		   evt->evt == HCI_EV_CMD_STATUS &&
+		   le16_to_cpu(cmd_status->opcode) == BT_VS_BT_ENABLE) {
+		/*
+		 * Clear the status events since the Bluez is not expecting
+		 * them.
+		 */
+		BT_DBG("HCI Driver received Command Status (BT enable): 0x%X",
+		       cmd_status->status);
+		/*
+		 * This is the command status event for
+		 * the HCI_Cmd_VS_Bluetooth_Enable.
+		 * Just free the packet.
+		 */
+		goto fin_free_skb;
+	} else {
+		bt_cb(skb)->pkt_type = dev_info->hci_data_type;
+		skb->dev = (struct net_device *)dev_info->hdev;
+		/* Update BlueZ stats */
+		dev_info->hdev->stat.byte_rx += skb->len;
+		if (bt_cb(skb)->pkt_type == HCI_ACLDATA_PKT)
+			dev_info->hdev->stat.acl_rx++;
+		else
+			dev_info->hdev->stat.evt_rx++;
+
+		BT_DBG("Data receive %d bytes", skb->len);
+
+		/* Provide BlueZ with received frame*/
+		err = hci_recv_frame(skb);
+		/* If err, skb have been freed in hci_recv_frame() */
+		if (err)
+			BT_ERR(NAME "Failed in supplying packet to Bluetooth"
+			       " stack (%d)", err);
+	}
+
+	return;
+
+fin_free_skb:
+	kfree_skb(skb);
+}
+
+/**
+ * hci_reset_cb() - Callback for handling reset from CG2900 driver.
+ * @dev:	CPD device resetting.
+ */
+static void hci_reset_cb(struct cg2900_device *dev)
+{
+	int err;
+	struct hci_dev *hdev;
+	struct dev_info *dev_info;
+	struct btcg2900_info *info;
+
+	BT_INFO(NAME "hci_reset_cb");
+
+	if (!dev) {
+		BT_ERR(NAME "NULL supplied for dev");
+		return;
+	}
+
+	dev_info = (struct dev_info *)dev->user_data;
+	if (!dev_info) {
+		BT_ERR(NAME "NULL supplied for dev_info");
+		return;
+	}
+
+	hdev = dev_info->hdev;
+	if (!hdev) {
+		BT_ERR(NAME "NULL supplied for hdev");
+		return;
+	}
+
+	info = (struct btcg2900_info *)hdev->driver_data;
+	if (!info) {
+		BT_ERR(NAME "NULL supplied for driver_data");
+		return;
+	}
+
+	switch (dev_info->hci_data_type) {
+
+	case HCI_EVENT_PKT:
+		info->bt_evt = NULL;
+		break;
+
+	case HCI_COMMAND_PKT:
+		info->bt_cmd = NULL;
+		break;
+
+	case HCI_ACLDATA_PKT:
+		info->bt_acl = NULL;
+		break;
+
+	default:
+		BT_ERR(NAME "Unknown HCI data type:%d",
+		       dev_info->hci_data_type);
+		return;
+	}
+
+	BT_DBG("New reset_state: RESET_ACTIVATED");
+	btcg2900_info->reset_state = RESET_ACTIVATED;
+
+	/*
+	 * Free userdata as device info structure will be freed by CG2900
+	 * when this callback returns.
+	 */
+	kfree(dev->user_data);
+	dev->user_data = NULL;
+
+	/*
+	 * Continue to deregister hdev if all channels has been reset else
+	 * return.
+	 */
+	if (info->bt_evt || info->bt_cmd || info->bt_acl)
+		return;
+
+	/*
+	 * Deregister HCI device. Close and Destruct functions should
+	 * in turn be called by BlueZ.
+	 */
+	BT_DBG("Deregister HCI device");
+	err = hci_unregister_dev(hdev);
+	if (err)
+		BT_ERR(NAME "Can not deregister HCI device! (%d)", err);
+		/*
+		 * Now we are in trouble. Try to register a new hdev
+		 * anyway even though this will cost some memory.
+		 */
+
+	wait_event_interruptible_timeout(hci_wait_queue,
+			(RESET_UNREGISTERED == btcg2900_info->reset_state),
+			msecs_to_jiffies(RESP_TIMEOUT));
+	if (RESET_UNREGISTERED != btcg2900_info->reset_state)
+		/*
+		 * Now we are in trouble. Try to register a new hdev
+		 * anyway even though this will cost some memory.
+		 */
+		BT_ERR(NAME "Timeout expired. Could not deregister HCI device");
+
+	/* Init and register hdev */
+	BT_DBG("Register HCI device");
+	err = register_bluetooth();
+	if (err)
+		BT_ERR(NAME "HCI Device registration error (%d).", err);
+}
+
+/*
+ * struct cg2900_cb - Specifies callback structure for CG2900 user.
+ *
+ * @read_cb:	Callback function called when data is received from
+ *		the controller.
+ * @reset_cb:	Callback function called when the controller has been reset.
+ */
+static struct cg2900_callbacks cg2900_cb = {
+	.read_cb = hci_read_cb,
+	.reset_cb = hci_reset_cb
+};
+
+/**
+ * btcg2900_open() - Open HCI interface.
+ * @hdev:	HCI device being opened.
+ *
+ * BlueZ callback function for opening HCI interface to device.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -EINVAL if NULL pointer is supplied.
+ *   -EOPNOTSUPP if supplied packet type is not supported.
+ *   -EBUSY if device is already opened.
+ *   -EACCES if opening of channels failed.
+ */
+static int btcg2900_open(struct hci_dev *hdev)
+{
+	struct btcg2900_info *info;
+	struct dev_info *dev_info;
+	struct sk_buff *enable_cmd;
+	int err;
+
+	BT_INFO("Open ST-Ericsson CG2900 driver");
+
+	if (!hdev) {
+		BT_ERR(NAME "NULL supplied for hdev");
+		return -EINVAL;
+	}
+
+	info = (struct btcg2900_info *)hdev->driver_data;
+	if (!info) {
+		BT_ERR(NAME "NULL supplied for driver_data");
+		return -EINVAL;
+	}
+
+	if (test_and_set_bit(HCI_RUNNING, &(hdev->flags))) {
+		BT_ERR(NAME "Device already opened!");
+		return -EBUSY;
+	}
+
+	if (!(info->bt_cmd)) {
+		info->bt_cmd = cg2900_register_user(CG2900_BT_CMD,
+						     &cg2900_cb);
+		if (info->bt_cmd) {
+			dev_info = kmalloc(sizeof(*dev_info), GFP_KERNEL);
+			if (dev_info) {
+				dev_info->hdev = hdev;
+				dev_info->hci_data_type = HCI_COMMAND_PKT;
+			}
+			info->bt_cmd->user_data = dev_info;
+		} else {
+			BT_ERR("Couldn't register CG2900_BT_CMD to CG2900");
+			err = -EACCES;
+			goto handle_error;
+		}
+	}
+
+	if (!(info->bt_evt)) {
+		info->bt_evt = cg2900_register_user(CG2900_BT_EVT,
+						     &cg2900_cb);
+		if (info->bt_evt) {
+			dev_info = kmalloc(sizeof(*dev_info), GFP_KERNEL);
+			if (dev_info) {
+				dev_info->hdev = hdev;
+				dev_info->hci_data_type = HCI_EVENT_PKT;
+			}
+			info->bt_evt->user_data = dev_info;
+		} else {
+			BT_ERR("Couldn't register CG2900_BT_EVT to CG2900");
+			err = -EACCES;
+			goto handle_error;
+		}
+	}
+
+	if (!(info->bt_acl)) {
+		info->bt_acl = cg2900_register_user(CG2900_BT_ACL,
+						     &cg2900_cb);
+		if (info->bt_acl) {
+			dev_info = kmalloc(sizeof(*dev_info), GFP_KERNEL);
+			if (dev_info) {
+				dev_info->hdev = hdev;
+				dev_info->hci_data_type = HCI_ACLDATA_PKT;
+			}
+			info->bt_acl->user_data = dev_info;
+		} else {
+			BT_ERR("Couldn't register CG2900_BT_ACL to CG2900");
+			err = -EACCES;
+			goto handle_error;
+		}
+	}
+
+	if (info->reset_state == RESET_ACTIVATED) {
+		BT_DBG("New reset_state: RESET_IDLE");
+		btcg2900_info->reset_state = RESET_IDLE;
+	}
+
+	/*
+	 * Call function that returns the chip dependent vs_bt_enable(true)
+	 * HCI command.
+	 * If NULL is returned, then no bt_enable command should be sent to the
+	 * chip.
+	 */
+	enable_cmd = get_bt_enable_cmd(true);
+	if (!enable_cmd) {
+		/* The chip is enabled by default */
+		BT_DBG("New enable_state: ENABLE_BT_ENABLED");
+		btcg2900_info->enable_state = ENABLE_BT_ENABLED;
+		return 0;
+	}
+
+	/* Set the HCI state before sending command to chip. */
+	BT_DBG("New enable_state: ENABLE_WAITING_BT_ENABLED_CC");
+	btcg2900_info->enable_state = ENABLE_WAITING_BT_ENABLED_CC;
+
+	/* Send command to chip */
+	cg2900_write(info->bt_cmd, enable_cmd);
+
+	/*
+	 * Wait for callback to receive command complete and then wake us up
+	 * again.
+	 */
+	wait_event_interruptible_timeout(hci_wait_queue,
+				(info->enable_state == ENABLE_BT_ENABLED),
+				msecs_to_jiffies(RESP_TIMEOUT));
+	/* Check the current state to se that it worked. */
+	if (info->enable_state != ENABLE_BT_ENABLED) {
+		BT_ERR("Could not enable CG2900 BT core (%d)",
+		       info->enable_state);
+		err = -EACCES;
+		BT_DBG("New enable_state: ENABLE_BT_DISABLED");
+		btcg2900_info->enable_state = ENABLE_BT_DISABLED;
+		goto handle_error;
+	}
+
+	return 0;
+
+handle_error:
+	remove_bt_users(info);
+	clear_bit(HCI_RUNNING, &(hdev->flags));
+	return err;
+
+}
+
+/**
+ * btcg2900_close() - Close HCI interface.
+ * @hdev:	HCI device being closed.
+ *
+ * BlueZ callback function for closing HCI interface.
+ * It flushes the interface first.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -EINVAL if NULL pointer is supplied.
+ *   -EOPNOTSUPP if supplied packet type is not supported.
+ *   -EBUSY if device is not opened.
+ */
+static int btcg2900_close(struct hci_dev *hdev)
+{
+	struct btcg2900_info *info = NULL;
+	struct sk_buff *enable_cmd;
+
+	BT_DBG("btcg2900_close");
+
+	if (!hdev) {
+		BT_ERR(NAME "NULL supplied for hdev");
+		return -EINVAL;
+	}
+
+	info = (struct btcg2900_info *)hdev->driver_data;
+	if (!info) {
+		BT_ERR(NAME "NULL supplied for driver_data");
+		return -EINVAL;
+	}
+
+	if (!test_and_clear_bit(HCI_RUNNING, &(hdev->flags))) {
+		BT_ERR(NAME "Device already closed!");
+		return -EBUSY;
+	}
+
+	/* Do not do this if there is an reset ongoing */
+	if (btcg2900_info->reset_state == RESET_ACTIVATED)
+		goto remove_users;
+
+	/*
+	 * Get the chip dependent BT Enable HCI command. The command parameter
+	 * shall be set to false to disable the BT core.
+	 * If NULL is returned, then no BT Enable command should be sent to the
+	 * chip.
+	 */
+	enable_cmd = get_bt_enable_cmd(false);
+	if (!enable_cmd) {
+		/*
+		 * The chip is enabled by default and we should not disable it.
+		 */
+		BT_DBG("New enable_state: ENABLE_BT_ENABLED");
+		btcg2900_info->enable_state = ENABLE_BT_ENABLED;
+		goto remove_users;
+	}
+
+	/* Set the HCI state before sending command to chip */
+	BT_DBG("New enable_state: ENABLE_WAITING_BT_DISABLED_CC");
+	btcg2900_info->enable_state = ENABLE_WAITING_BT_DISABLED_CC;
+
+	/* Send command to chip */
+	cg2900_write(info->bt_cmd, enable_cmd);
+
+	/*
+	 * Wait for callback to receive command complete and then wake us up
+	 * again.
+	 */
+	wait_event_interruptible_timeout(hci_wait_queue,
+				(info->enable_state == ENABLE_BT_DISABLED),
+				msecs_to_jiffies(RESP_TIMEOUT));
+	/* Check the current state to se that it worked. */
+	if (info->enable_state != ENABLE_BT_DISABLED) {
+		BT_ERR("Could not disable CG2900 BT core.");
+		BT_DBG("New enable_state: ENABLE_BT_ENABLED");
+		btcg2900_info->enable_state = ENABLE_BT_ENABLED;
+	}
+
+remove_users:
+	/* Finally deregister all users and free allocated data */
+	remove_bt_users(info);
+	return 0;
+}
+
+/**
+ * btcg2900_send() - Send packet to device.
+ * @skb:	sk buffer to be sent.
+ *
+ * BlueZ callback function for sending sk buffer.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -EINVAL if NULL pointer is supplied.
+ *   -EOPNOTSUPP if supplied packet type is not supported.
+ *   Error codes from cg2900_write.
+ */
+static int btcg2900_send(struct sk_buff *skb)
+{
+	struct hci_dev *hdev;
+	struct btcg2900_info *info;
+	int err = 0;
+
+	if (!skb) {
+		BT_ERR(NAME "NULL supplied for skb");
+		return -EINVAL;
+	}
+
+	hdev = (struct hci_dev *)(skb->dev);
+	if (!hdev) {
+		BT_ERR(NAME "NULL supplied for hdev");
+		return -EINVAL;
+	}
+
+	info = (struct btcg2900_info *)hdev->driver_data;
+	if (!info) {
+		BT_ERR(NAME "NULL supplied for info");
+		return -EINVAL;
+	}
+
+	/* Update BlueZ stats */
+	hdev->stat.byte_tx += skb->len;
+
+	BT_DBG("Data transmit %d bytes", skb->len);
+
+	switch (bt_cb(skb)->pkt_type) {
+	case HCI_COMMAND_PKT:
+		BT_DBG("Sending HCI_COMMAND_PKT");
+		err = cg2900_write(info->bt_cmd, skb);
+		hdev->stat.cmd_tx++;
+		break;
+	case HCI_ACLDATA_PKT:
+		BT_DBG("Sending HCI_ACLDATA_PKT");
+		err = cg2900_write(info->bt_acl, skb);
+		hdev->stat.acl_tx++;
+		break;
+	default:
+		BT_ERR(NAME "Trying to transmit unsupported packet type"
+		       " (0x%.2X)", bt_cb(skb)->pkt_type);
+		err = -EOPNOTSUPP;
+		break;
+	};
+
+	return err;
+}
+
+/**
+ * btcg2900_destruct() - Destruct HCI interface.
+ * @hdev:	HCI device being destructed.
+ */
+static void btcg2900_destruct(struct hci_dev *hdev)
+{
+	BT_DBG("btcg2900_destruct");
+
+	if (!btcg2900_info)
+		return;
+
+	/*
+	 * When destruct is called it means that the Bluetooth stack is done
+	 * with the HCI device and we can now free it.
+	 * Normally we do this only when removing the whole module through
+	 * btcg2900_remove(), but when being reset we free the device here and
+	 * we then set the reset state so that the reset handler can allocate a
+	 * new HCI device and then register it to the Bluetooth stack.
+	 */
+	if (btcg2900_info->reset_state == RESET_ACTIVATED) {
+		if (btcg2900_info->hdev)
+			hci_free_dev(btcg2900_info->hdev);
+		BT_DBG("New reset_state: RESET_UNREGISTERED");
+		btcg2900_info->reset_state = RESET_UNREGISTERED;
+		wake_up_interruptible(&hci_wait_queue);
+	}
+}
+
+/**
+ * register_bluetooth() - Initialize module.
+ *
+ * Alloc, init, and register HCI device to BlueZ.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -ENOMEM if allocation fails.
+ *   Error codes from hci_register_dev.
+ */
+static int register_bluetooth(void)
+{
+	int err;
+	static struct cg2900_bt_platform_data *pf_data;
+
+	pf_data = dev_get_platdata(&btcg2900_info->pdev->dev);
+
+	btcg2900_info->hdev = hci_alloc_dev();
+	if (!btcg2900_info->hdev) {
+		BT_ERR("Could not allocate mem for CG2900 BT driver");
+		return -ENOMEM;
+	}
+
+	SET_HCIDEV_DEV(btcg2900_info->hdev, &btcg2900_info->pdev->dev);
+	if (pf_data) {
+		btcg2900_info->hdev->bus = pf_data->bus;
+	} else {
+		BT_DBG(NAME "Missing platform data. Defaulting to UART");
+		btcg2900_info->hdev->bus = HCI_UART;
+	}
+	btcg2900_info->hdev->driver_data = btcg2900_info;
+	btcg2900_info->hdev->owner = THIS_MODULE;
+	btcg2900_info->hdev->open = btcg2900_open;
+	btcg2900_info->hdev->close = btcg2900_close;
+	btcg2900_info->hdev->send = btcg2900_send;
+	btcg2900_info->hdev->destruct = btcg2900_destruct;
+
+	err = hci_register_dev(btcg2900_info->hdev);
+	if (err) {
+		BT_ERR(NAME "Can not register HCI device (%d)", err);
+		hci_free_dev(btcg2900_info->hdev);
+	}
+
+	BT_DBG("New enable_state: ENABLE_IDLE");
+	btcg2900_info->enable_state = ENABLE_IDLE;
+	BT_DBG("New reset_state: RESET_IDLE");
+	btcg2900_info->reset_state = RESET_IDLE;
+
+	return err;
+}
+
+/**
+ * btcg2900_probe() - Initialize module.
+ *
+ * Allocate and initialize private data. Register to Bluetooth stack.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -ENOMEM if allocation fails.
+ *   Error codes from register_bluetooth.
+ */
+static int __devinit btcg2900_probe(struct platform_device *pdev)
+{
+	int err;
+
+	BT_INFO("btcg2900_probe");
+
+	/* Initialize private data. */
+	btcg2900_info = kzalloc(sizeof(*btcg2900_info), GFP_KERNEL);
+	if (!btcg2900_info) {
+		BT_ERR("Could not alloc btcg2900_info struct.");
+		return -ENOMEM;
+	}
+
+	btcg2900_info->pdev = pdev;
+
+	/* Init and register hdev */
+	err = register_bluetooth();
+	if (err) {
+		BT_ERR("HCI Device registration error (%d)", err);
+		kfree(btcg2900_info);
+		btcg2900_info = NULL;
+		return err;
+	}
+
+	return 0;
+}
+
+/**
+ * btcg2900_remove() - Remove module.
+ */
+static int __devexit btcg2900_remove(struct platform_device *pdev)
+{
+	int err = 0;
+
+	BT_INFO("btcg2900_remove");
+
+	if (!btcg2900_info)
+		return 0;
+
+	if (!btcg2900_info->hdev)
+		goto finished;
+
+	err = hci_unregister_dev(btcg2900_info->hdev);
+	if (err)
+		BT_ERR("Can not unregister HCI device (%d)", err);
+	hci_free_dev(btcg2900_info->hdev);
+
+finished:
+	kfree(btcg2900_info);
+	btcg2900_info = NULL;
+	return err;
+}
+
+static struct platform_driver btcg2900_driver = {
+	.driver = {
+		.name	= "cg2900-bt",
+		.owner	= THIS_MODULE,
+	},
+	.probe	= btcg2900_probe,
+	.remove	= __devexit_p(btcg2900_remove),
+};
+
+/**
+ * btcg2900_init() - Initialize module.
+ *
+ * Registers platform driver.
+ */
+static int __init btcg2900_init(void)
+{
+	BT_INFO("btcg2900_init");
+	return platform_driver_register(&btcg2900_driver);
+}
+
+/**
+ * btcg2900_exit() - Remove module.
+ *
+ * Unregisters platform driver.
+ */
+static void __exit btcg2900_exit(void)
+{
+	BT_INFO("btcg2900_exit");
+	platform_driver_unregister(&btcg2900_driver);
+}
+
+module_init(btcg2900_init);
+module_exit(btcg2900_exit);
+
+MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson");
+MODULE_AUTHOR("Henrik Possung ST-Ericsson");
+MODULE_AUTHOR("Josef Kindberg ST-Ericsson");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Linux Bluetooth HCI H:4 Driver for ST-Ericsson
controller");
-- 
1.6.3.3

^ permalink raw reply related

* [PATCH 7/9] mach-ux500: Add support for the ST-Ericsson CG2900.
From: Par-Gunnar Hjalmdahl @ 2010-10-22 10:39 UTC (permalink / raw)
  To: linus.walleij, linux-bluetooth, linux-kernel

This patch adds board specific support for the ST-Ericsson CG2900
Connectivity Combo controller.
It contains device structures as well as necessary callback
functions.

Signed-off-by: Par-Gunnar Hjalmdahl <par-gunnar.p.hjalmdahl@stericsson.com>
---
 arch/arm/mach-ux500/Makefile               |    1 +
 arch/arm/mach-ux500/board-mop500.c         |    3 +
 arch/arm/mach-ux500/devices-cg2900.c       |  261 ++++++++++++++++++++++++++++
 arch/arm/mach-ux500/include/mach/devices.h |    4 +
 4 files changed, 269 insertions(+), 0 deletions(-)
 create mode 100644 arch/arm/mach-ux500/devices-cg2900.c

diff --git a/arch/arm/mach-ux500/Makefile b/arch/arm/mach-ux500/Makefile
index 9e27a84..7754aff 100644
--- a/arch/arm/mach-ux500/Makefile
+++ b/arch/arm/mach-ux500/Makefile
@@ -13,3 +13,4 @@ obj-$(CONFIG_LOCAL_TIMERS)	+= localtimer.o
 obj-$(CONFIG_REGULATOR_AB8500)	+= board-mop500-regulators.o
 obj-$(CONFIG_U5500_MODEM_IRQ)	+= modem_irq.o
 obj-$(CONFIG_U5500_MBOX)	+= mbox.o
+obj-$(CONFIG_MFD_CG2900)	+= devices-cg2900.o
diff --git a/arch/arm/mach-ux500/board-mop500.c
b/arch/arm/mach-ux500/board-mop500.c
index 09fba17..589921b 100644
--- a/arch/arm/mach-ux500/board-mop500.c
+++ b/arch/arm/mach-ux500/board-mop500.c
@@ -303,6 +303,9 @@ static struct platform_device *platform_devs[]
__initdata = {
 	&ux500_i2c2_device,
 	&ux500_i2c3_device,
 	&ux500_ske_keypad_device,
+#ifdef CONFIG_MFD_CG2900
+	&ux500_cg2900_device,
+#endif
 };

 static void __init u8500_init_machine(void)
diff --git a/arch/arm/mach-ux500/devices-cg2900.c
b/arch/arm/mach-ux500/devices-cg2900.c
new file mode 100644
index 0000000..b5c60d5
--- /dev/null
+++ b/arch/arm/mach-ux500/devices-cg2900.c
@@ -0,0 +1,261 @@
+/*
+ * arch/arm/mach-ux500/devices-cg2900.c
+ *
+ * Copyright (C) ST-Ericsson SA 2010
+ * Authors:
+ * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for
ST-Ericsson.
+ * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson.
+ * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson.
+ * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson.
+ * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson.
+ * License terms:  GNU General Public License (GPL), version 2
+ *
+ * Board specific device support for the Linux Bluetooth HCI H:4 Driver
+ * for ST-Ericsson connectivity controller.
+ */
+
+#include <asm/byteorder.h>
+#include <asm-generic/errno-base.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/skbuff.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/mfd/cg2900.h>
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci.h>
+#include <plat/pincfg.h>
+
+#include "pins-db8500.h"
+
+#ifdef CONFIG_MFD_CG2900
+
+#define BT_ENABLE_GPIO			170
+#define GBF_ENA_RESET_GPIO		171
+#define BT_CTS_GPIO			0
+
+#define GBF_ENA_RESET_NAME		"gbf_ena_reset"
+#define BT_ENABLE_NAME			"bt_enable"
+#define CG2900_NAME			"cg2900_devices"
+
+#define UART_LINES_NUM			4
+
+#define BT_VS_POWER_SWITCH_OFF		0xFD40
+
+#define H4_HEADER_LENGTH		0x01
+#define BT_HEADER_LENGTH		0x03
+
+#define STLC2690_HCI_REV		0x0600
+#define CG2900_PG1_HCI_REV		0x0101
+#define CG2900_PG2_HCI_REV		0x0200
+#define CG2900_PG1_SPECIAL_HCI_REV	0x0700
+
+struct vs_power_sw_off_cmd {
+	__le16	op_code;
+	u8	len;
+	u8	gpio_0_7_pull_up;
+	u8	gpio_8_15_pull_up;
+	u8	gpio_16_20_pull_up;
+	u8	gpio_0_7_pull_down;
+	u8	gpio_8_15_pull_down;
+	u8	gpio_16_20_pull_down;
+} __attribute__((packed));
+
+static u16 cg2900_hci_revision;
+
+/* Pin configuration for UART functions. */
+static pin_cfg_t uart0_enabled[] = {
+	GPIO0_U0_CTSn   | (PIN_DIR_INPUT | PIN_PULL_UP),
+	GPIO1_U0_RTSn   | PIN_OUTPUT_HIGH,
+	GPIO2_U0_RXD    | (PIN_DIR_INPUT | PIN_PULL_UP),
+	GPIO3_U0_TXD    | PIN_OUTPUT_HIGH
+};
+
+/* Pin configuration for sleep mode. */
+static pin_cfg_t uart0_disabled[] = {
+	GPIO0_GPIO   | (PIN_DIR_INPUT | PIN_PULL_UP),	/* CTS pull up. */
+	GPIO1_GPIO   | PIN_OUTPUT_HIGH,		/* RTS high - flow off. */
+	GPIO2_GPIO   | (PIN_DIR_INPUT | PIN_PULL_UP),	/* RX pull down. */
+	GPIO3_GPIO   | PIN_OUTPUT_LOW		/* TX low - break on. */
+};
+
+static void cg2900_enable_chip(void)
+{
+	gpio_set_value(GBF_ENA_RESET_GPIO, 1);
+}
+
+static void cg2900_disable_chip(void)
+{
+	gpio_set_value(GBF_ENA_RESET_GPIO, 0);
+}
+
+static void cg2900_set_hci_revision(u8 hci_version, u16 hci_revision,
+				    u8 lmp_version, u8 lmp_subversion,
+				    u16 manufacturer)
+{
+	cg2900_hci_revision = hci_revision;
+	/* We don't care about the other values */
+}
+
+static struct sk_buff *cg2900_get_power_switch_off_cmd(u16 *op_code)
+{
+	struct sk_buff *skb;
+	struct vs_power_sw_off_cmd *cmd;
+
+	/* If connected chip does not support the command return NULL */
+	if (CG2900_PG1_SPECIAL_HCI_REV != cg2900_hci_revision &&
+	    CG2900_PG1_HCI_REV != cg2900_hci_revision &&
+	    CG2900_PG2_HCI_REV != cg2900_hci_revision)
+		return NULL;
+
+	skb = alloc_skb(sizeof(*cmd) + H4_HEADER_LENGTH, GFP_KERNEL);
+	if (!skb) {
+		pr_err(CG2900_NAME "Could not allocate skb");
+		return NULL;
+	}
+
+	skb_reserve(skb, H4_HEADER_LENGTH);
+	cmd = (struct vs_power_sw_off_cmd *)skb_put(skb, sizeof(*cmd));
+	cmd->op_code = cpu_to_le16(BT_VS_POWER_SWITCH_OFF);
+	cmd->len = sizeof(*cmd) - BT_HEADER_LENGTH;
+	/*
+	 * Enter system specific GPIO settings here:
+	 * Section data[3-5] is GPIO pull-up selection
+	 * Section data[6-8] is GPIO pull-down selection
+	 * Each section is a bitfield where
+	 * - byte 0 bit 0 is GPIO 0
+	 * - byte 0 bit 1 is GPIO 1
+	 * - up to
+	 * - byte 2 bit 4 which is GPIO 20
+	 * where each bit means:
+	 * - 0: No pull-up / no pull-down
+	 * - 1: Pull-up / pull-down
+	 * All GPIOs are set as input.
+	 */
+	cmd->gpio_0_7_pull_up = 0x00;
+	cmd->gpio_8_15_pull_up = 0x00;
+	cmd->gpio_16_20_pull_up = 0x00;
+	cmd->gpio_0_7_pull_down = 0x00;
+	cmd->gpio_8_15_pull_down = 0x00;
+	cmd->gpio_16_20_pull_down = 0x00;
+
+	if (op_code)
+		*op_code = BT_VS_POWER_SWITCH_OFF;
+
+	return skb;
+}
+static int cg2900_init(void)
+{
+	int err = 0;
+
+	err = gpio_request(GBF_ENA_RESET_GPIO, GBF_ENA_RESET_NAME);
+	if (err < 0) {
+		pr_err(CG2900_NAME "gpio_request failed with err: %d", err);
+		goto finished;
+	}
+
+	err = gpio_direction_output(GBF_ENA_RESET_GPIO, 1);
+	if (err < 0) {
+		pr_err(CG2900_NAME "gpio_direction_output failed with err: %d",
+		       err);
+		goto error_handling;
+	}
+
+	err = gpio_request(BT_ENABLE_GPIO, BT_ENABLE_NAME);
+	if (err < 0) {
+		pr_err(CG2900_NAME "gpio_request failed with err: %d", err);
+		goto finished;
+	}
+
+	err = gpio_direction_output(BT_ENABLE_GPIO, 1);
+	if (err < 0) {
+		pr_err(CG2900_NAME "gpio_direction_output failed with err: %d",
+		       err);
+		goto error_handling;
+	}
+
+	goto finished;
+
+error_handling:
+	gpio_free(GBF_ENA_RESET_GPIO);
+
+finished:
+	cg2900_disable_chip();
+	return err;
+}
+
+void cg2900_exit(void)
+{
+	cg2900_disable_chip();
+	gpio_free(GBF_ENA_RESET_GPIO);
+}
+
+#ifdef CONFIG_MFD_CG2900_UART
+
+static int cg2900_disable_uart(void)
+{
+	int err;
+
+	/*
+	 * Without this delay we get interrupt on CTS immediately
+	 * due to some turbulences on this line.
+	 */
+	mdelay(4);
+
+	/* Disable UART functions. */
+	err = nmk_config_pins(uart0_disabled, UART_LINES_NUM);
+	if (err)
+		goto error;
+
+	return 0;
+
+error:
+	(void)nmk_config_pins(uart0_enabled, UART_LINES_NUM);
+	pr_err(CG2900_NAME "Cannot set interrupt (%d)", err);
+	return err;
+}
+
+
+static int cg2900_enable_uart(void)
+{
+	int err;
+
+	/* Restore UART settings. */
+	err = nmk_config_pins(uart0_enabled, UART_LINES_NUM);
+	if (err)
+		pr_err(CG2900_NAME "Unable to enable UART (%d)", err);
+
+	return err;
+}
+
+#endif /* CONFIG_MFD_CG2900_UART */
+
+struct cg2900_platform_data cg2900_platform_data = {
+	.init = cg2900_init,
+	.exit = cg2900_exit,
+	.enable_chip = cg2900_enable_chip,
+	.disable_chip = cg2900_disable_chip,
+	.set_hci_revision = cg2900_set_hci_revision,
+	.get_power_switch_off_cmd = cg2900_get_power_switch_off_cmd,
+
+	.bus = HCI_UART,
+
+#ifdef CONFIG_MFD_CG2900_UART
+	.uart = {
+		.cts_irq = NOMADIK_GPIO_TO_IRQ(BT_CTS_GPIO),
+		.enable_uart = cg2900_enable_uart,
+		.disable_uart = cg2900_disable_uart
+	},
+#endif /* CONFIG_MFD_CG2900_UART */
+};
+
+struct platform_device ux500_cg2900_device = {
+	.name = "cg2900",
+	.dev = {
+		.platform_data = &cg2900_platform_data,
+	}
+};
+#endif /* CONFIG_MFD_CG2900 */
diff --git a/arch/arm/mach-ux500/include/mach/devices.h
b/arch/arm/mach-ux500/include/mach/devices.h
index b91a4d1..cbc7897 100644
--- a/arch/arm/mach-ux500/include/mach/devices.h
+++ b/arch/arm/mach-ux500/include/mach/devices.h
@@ -28,6 +28,10 @@ extern struct platform_device u8500_i2c4_device;
 extern struct platform_device u8500_dma40_device;
 extern struct platform_device ux500_ske_keypad_device;

+#ifdef CONFIG_MFD_CG2900
+extern struct platform_device ux500_cg2900_device;
+#endif
+
 extern struct amba_device u8500_sdi0_device;
 extern struct amba_device u8500_sdi1_device;
 extern struct amba_device u8500_sdi2_device;
-- 
1.6.3.3

^ permalink raw reply related

* [PATCH 6/9] mfd: Add support for the ST-Ericsson CG2900 Audio.
From: Par-Gunnar Hjalmdahl @ 2010-10-22 10:39 UTC (permalink / raw)
  To: linus.walleij, linux-bluetooth, linux-kernel

This patch adds support for controlling audio paths within
the ST-Ericsson CG2900 Connectivity Combo controller.
This patch adds API for the Kernel as well as a char device for
user space stacks to control the CG2900 audio paths.

Signed-off-by: Par-Gunnar Hjalmdahl <par-gunnar.p.hjalmdahl@stericsson.com>
---
 drivers/mfd/Kconfig               |    8 +
 drivers/mfd/cg2900/Makefile       |    2 +
 drivers/mfd/cg2900/cg2900_audio.c | 3026 +++++++++++++++++++++++++++++++++++++
 include/linux/mfd/cg2900_audio.h  |  460 ++++++
 4 files changed, 3496 insertions(+), 0 deletions(-)
 create mode 100644 drivers/mfd/cg2900/cg2900_audio.c
 create mode 100644 include/linux/mfd/cg2900_audio.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 6fcd8b6..6f92b04 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -288,6 +288,14 @@ config MFD_CG2900_UART
 	help
 	  Support for UART as transport for ST-Ericsson CG2900 Connectivity
 	  Controller
+	  Controller
+
+config MFD_CG2900_AUDIO
+	tristate "Support CG2900 audio interface"
+	depends on MFD_CG2900
+	help
+	  Support for ST-Ericsson CG2900 Connectivity audio interface. Gives a
+	  module the ability to setup audio paths within the CG2900 controller.

 config PMIC_DA903X
 	bool "Dialog Semiconductor DA9030/DA9034 PMIC Support"
diff --git a/drivers/mfd/cg2900/Makefile b/drivers/mfd/cg2900/Makefile
index c8dd713..94095c0 100644
--- a/drivers/mfd/cg2900/Makefile
+++ b/drivers/mfd/cg2900/Makefile
@@ -12,3 +12,5 @@ obj-$(CONFIG_MFD_STLC2690_CHIP)	+= stlc2690_chip.o

 obj-$(CONFIG_MFD_CG2900_UART)	+= cg2900_uart.o

+obj-$(CONFIG_MFD_CG2900_AUDIO)	+= cg2900_audio.o
+
diff --git a/drivers/mfd/cg2900/cg2900_audio.c
b/drivers/mfd/cg2900/cg2900_audio.c
new file mode 100644
index 0000000..b9ab504
--- /dev/null
+++ b/drivers/mfd/cg2900/cg2900_audio.c
@@ -0,0 +1,3026 @@
+/*
+ * drivers/mfd/cg2900/cg2900_audio.c
+ *
+ * Copyright (C) ST-Ericsson SA 2010
+ * Authors:
+ * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for
ST-Ericsson.
+ * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson.
+ * License terms:  GNU General Public License (GPL), version 2
+ *
+ * Linux Bluetooth Audio Driver for ST-Ericsson CG2900 controller.
+ */
+
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/poll.h>
+#include <linux/sched.h>
+#include <linux/skbuff.h>
+#include <linux/types.h>
+#include <linux/mfd/cg2900.h>
+#include <linux/mfd/cg2900_audio.h>
+
+#include "cg2900_debug.h"
+#include "hci_defines.h"
+#include "cg2900_chip.h"
+
+/* Char device op codes */
+#define OP_CODE_SET_DAI_CONF			0x01
+#define OP_CODE_GET_DAI_CONF			0x02
+#define OP_CODE_CONFIGURE_ENDPOINT		0x03
+#define OP_CODE_START_STREAM			0x04
+#define OP_CODE_STOP_STREAM			0x05
+
+/* Device names */
+#define DEVICE_NAME				"cg2900_audio"
+
+/* Type of channel used */
+#define BT_CHANNEL_USED				0x00
+#define FM_CHANNEL_USED				0x01
+
+#define MAX_NBR_OF_USERS			10
+#define FIRST_USER				1
+
+#define SET_RESP_STATE(__state_var, __new_state) \
+	CG2900_SET_STATE("resp_state", __state_var, __new_state)
+
+#define DEFAULT_SCO_HANDLE			0x0008
+
+/* Use a timeout of 5 seconds when waiting for a command response */
+#define RESP_TIMEOUT				5000
+
+/* Used to select proper API, ignoring subrevisions etc */
+enum chip_revision {
+	CHIP_REV_PG1,
+	CHIP_REV_PG2
+};
+
+/**
+ * enum chip_resp_state - State when communicating with the CG2900 controller.
+ * @IDLE:		No outstanding packets to the controller.
+ * @WAITING:		Packet has been sent to the controller. Waiting for
+ *			response.
+ * @RESP_RECEIVED:	Response from controller has been received but not yet
+ *			handled.
+ */
+enum chip_resp_state {
+	IDLE,
+	WAITING,
+	RESP_RECEIVED
+};
+
+/**
+ * enum main_state - Main state for the CG2900 Audio driver.
+ * @OPENED:	Audio driver has registered to CG2900 Core.
+ * @CLOSED:	Audio driver is not registered to CG2900 Core.
+ * @RESET:	A reset of CG2900 Core has occurred and no user has re-opened
+ *		the audio driver.
+ */
+enum main_state {
+	OPENED,
+	CLOSED,
+	RESET
+};
+
+/**
+ * struct char_dev_info - CG2900 character device info structure.
+ * @session:		Stored session for the char device.
+ * @stored_data:	Data returned when executing last command, if any.
+ * @stored_data_len:	Length of @stored_data in bytes.
+ * @management_mutex:	Mutex for handling access to char dev management.
+ * @rw_mutex:		Mutex for handling access to char dev writes and reads.
+ */
+struct char_dev_info {
+	int		session;
+	u8		*stored_data;
+	int		stored_data_len;
+	struct mutex	management_mutex;
+	struct mutex	rw_mutex;
+};
+
+/**
+ * struct audio_user - CG2900 audio user info structure.
+ * @session:	Stored session for the char device.
+ * @resp_state:	State for controller communications.
+ */
+struct audio_user {
+	int			session;
+	enum chip_resp_state	resp_state;
+};
+
+/**
+ * struct endpoint_list - List for storing endpoint configuration nodes.
+ * @ep_list:		Pointer to first node in list.
+ * @management_mutex:	Mutex for handling access to list.
+ */
+struct endpoint_list {
+	struct list_head	ep_list;
+	struct mutex		management_mutex;
+};
+
+/**
+ * struct endpoint_config_node - Node for storing endpoint configuration.
+ * @list:		list_head struct.
+ * @endpoint_id:	Endpoint ID.
+ * @config:		Stored configuration for this endpoint.
+ */
+struct endpoint_config_node {
+	struct list_head			list;
+	enum cg2900_audio_endpoint_id		endpoint_id;
+	union cg2900_endpoint_config_union	config;
+};
+
+/**
+ * struct audio_info - Main CG2900 Audio driver info structure.
+ * @state:			Current state of the CG2900 Audio driver.
+ * @revision:			Chip revision, used to select API.
+ * @dev:			The misc device created by this driver.
+ * @dev_bt:			CG2900 Core device registered by this driver for
+ *				the BT audio channel.
+ * @dev_fm:			CG2900 Core device registered by this driver for
+ *				the FM audio channel.
+ * @management_mutex:		Mutex for handling access to CG2900 Audio driver
+ *				management.
+ * @bt_mutex:			Mutex for handling access to BT audio channel.
+ * @fm_mutex:			Mutex for handling access to FM audio channel.
+ * @nbr_of_users_active:	Number of sessions open in the CG2900 Audio
+ *				driver.
+ * @bt_queue:			Received BT events.
+ * @fm_queue:			Received FM events.
+ * @audio_sessions:		Pointers to currently opened sessions (maps
+ *				session ID to user info).
+ * @i2s_config:			DAI I2S configuration.
+ * @i2s_pcm_config:		DAI PCM_I2S configuration.
+ * @i2s_config_known:		@true if @i2s_config has been set,
+ *				@false otherwise.
+ * @i2s_pcm_config_known:	@true if @i2s_pcm_config has been set,
+ *				@false otherwise.
+ * @endpoints:			List containing the endpoint configurations.
+ * @stream_ids:			Bitmask for in-use stream ids (only used with
+ *				PG2 chip API).
+ */
+struct audio_info {
+	enum main_state			state;
+	enum chip_revision		revision;
+	struct miscdevice		dev;
+	struct cg2900_device		*dev_bt;
+	struct cg2900_device		*dev_fm;
+	struct mutex			management_mutex;
+	struct mutex			bt_mutex;
+	struct mutex			fm_mutex;
+	int				nbr_of_users_active;
+	struct sk_buff_head		bt_queue;
+	struct sk_buff_head		fm_queue;
+	struct audio_user		*audio_sessions[MAX_NBR_OF_USERS];
+	struct cg2900_dai_conf_i2s	i2s_config;
+	struct cg2900_dai_conf_i2s_pcm	i2s_pcm_config;
+	bool				i2s_config_known;
+	bool				i2s_pcm_config_known;
+	struct endpoint_list		endpoints;
+	u32				stream_ids;
+};
+
+/**
+ * struct audio_cb_info - Callback info structure registered in @user_data.
+ * @channel:	Stores if this device handles BT or FM events.
+ * @user:	Audio user currently awaiting data on the channel.
+ */
+struct audio_cb_info {
+	int			channel;
+	struct audio_user	*user;
+};
+
+/* cg2900_audio wait queues */
+static DECLARE_WAIT_QUEUE_HEAD(wq_bt);
+static DECLARE_WAIT_QUEUE_HEAD(wq_fm);
+
+static struct audio_info *audio_info;
+
+static struct audio_cb_info cb_info_bt = {
+	.channel = BT_CHANNEL_USED,
+	.user = NULL
+};
+static struct audio_cb_info cb_info_fm = {
+	.channel = FM_CHANNEL_USED,
+	.user = NULL
+};
+
+/*
+ *	Internal conversion functions
+ *
+ *	Since the CG2900 apis uses several different ways to encode the
+ *	same parameter in different cases, we have to use translator
+ *	functions.
+ */
+
+/**
+ * session_config_sample_rate() - Convert sample rate to format used
in VS_Set_SessionConfiguration.
+ * @rate: Sample rate in API encoding.
+ */
+static u8 session_config_sample_rate(enum cg2900_endpoint_sample_rate rate)
+{
+	static const u8 codes[] = {
+		[ENDPOINT_SAMPLE_RATE_8_KHZ]    = CG2900_BT_SESSION_RATE_8K,
+		[ENDPOINT_SAMPLE_RATE_16_KHZ]   = CG2900_BT_SESSION_RATE_16K,
+		[ENDPOINT_SAMPLE_RATE_44_1_KHZ] = CG2900_BT_SESSION_RATE_44_1K,
+		[ENDPOINT_SAMPLE_RATE_48_KHZ]   = CG2900_BT_SESSION_RATE_48K
+	};
+
+	return codes[rate];
+}
+
+/**
+ * mc_i2s_sample_rate() - Convert sample rate to format used in
VS_Port_Config for I2S.
+ * @rate: Sample rate in API encoding.
+ */
+static u8 mc_i2s_sample_rate(enum cg2900_dai_sample_rate rate)
+{
+	static const u8 codes[] = {
+		[SAMPLE_RATE_8]    = CG2900_MC_I2S_SAMPLE_RATE_8,
+		[SAMPLE_RATE_16]   = CG2900_MC_I2S_SAMPLE_RATE_16,
+		[SAMPLE_RATE_44_1] = CG2900_MC_I2S_SAMPLE_RATE_44_1,
+		[SAMPLE_RATE_48]   = CG2900_MC_I2S_SAMPLE_RATE_48
+	};
+
+	return codes[rate];
+}
+
+/**
+ * mc_pcm_sample_rate() - Convert sample rate to format used in
VS_Port_Config for PCM/I2S.
+ * @rate: Sample rate in API encoding.
+ */
+static u8 mc_pcm_sample_rate(enum cg2900_dai_sample_rate rate)
+{
+	static const u8 codes[] = {
+		[SAMPLE_RATE_8]    = CG2900_MC_PCM_SAMPLE_RATE_8,
+		[SAMPLE_RATE_16]   = CG2900_MC_PCM_SAMPLE_RATE_16,
+		[SAMPLE_RATE_44_1] = CG2900_MC_PCM_SAMPLE_RATE_44_1,
+		[SAMPLE_RATE_48]   = CG2900_MC_PCM_SAMPLE_RATE_48
+	};
+
+	return codes[rate];
+}
+
+/**
+ * mc_i2s_channel_select() - Convert channel selection to format used
in VS_Port_Config.
+ * @sel: Channel selection in API encoding.
+ */
+static u8 mc_i2s_channel_select(enum cg2900_dai_channel_sel sel)
+{
+	static const u8 codes[] = {
+		[CHANNEL_SELECTION_RIGHT] = CG2900_MC_I2S_RIGHT_CHANNEL,
+		[CHANNEL_SELECTION_LEFT]  = CG2900_MC_I2S_LEFT_CHANNEL,
+		[CHANNEL_SELECTION_BOTH]  = CG2900_MC_I2S_BOTH_CHANNELS
+	};
+	return codes[sel];
+}
+
+/**
+ * get_fs_duration() - Convert framesync-enumeration to real value.
+ * @duration: Framsync duration (API encoding).
+ *
+ * Returns:
+ * Duration in bits.
+ */
+static u16 get_fs_duration(enum cg2900_dai_fs_duration duration)
+{
+	static const u16 values[] = {
+		[SYNC_DURATION_8] = 8,
+		[SYNC_DURATION_16] = 16,
+		[SYNC_DURATION_24] = 24,
+		[SYNC_DURATION_32] = 32,
+		[SYNC_DURATION_48] = 48,
+		[SYNC_DURATION_50] = 50,
+		[SYNC_DURATION_64] = 64,
+		[SYNC_DURATION_75] = 75,
+		[SYNC_DURATION_96] = 96,
+		[SYNC_DURATION_125] = 125,
+		[SYNC_DURATION_128] = 128,
+		[SYNC_DURATION_150] = 150,
+		[SYNC_DURATION_192] = 192,
+		[SYNC_DURATION_250] = 250,
+		[SYNC_DURATION_256] = 256,
+		[SYNC_DURATION_300] = 300,
+		[SYNC_DURATION_384] = 384,
+		[SYNC_DURATION_500] = 500,
+		[SYNC_DURATION_512] = 512,
+		[SYNC_DURATION_600] = 600,
+		[SYNC_DURATION_768] = 768
+	};
+	return values[duration];
+}
+
+/**
+ * mc_i2s_role() - Convert master/slave encoding to format for I2S-ports.
+ * @mode: Master/slave in API encoding.
+ */
+static u8 mc_i2s_role(enum cg2900_dai_mode mode)
+{
+	if (mode == DAI_MODE_SLAVE)
+		return CG2900_I2S_MODE_SLAVE;
+	else
+		return CG2900_I2S_MODE_MASTER;
+}
+
+/**
+ * mc_pcm_role() - Convert master/slave encoding to format for PCM/I2S-port.
+ * @mode: Master/slave in API encoding.
+ */
+static u8 mc_pcm_role(enum cg2900_dai_mode mode)
+{
+	if (mode == DAI_MODE_SLAVE)
+		return CG2900_PCM_MODE_SLAVE;
+	else
+		return CG2900_PCM_MODE_MASTER;
+}
+
+/**
+ * fm_get_conversion() - Convert sample rate to convert up/down used
in X_Set_Control FM commands.
+ * @srate: Sample rate.
+ */
+static u16 fm_get_conversion(enum cg2900_endpoint_sample_rate srate)
+{
+	if (srate >= ENDPOINT_SAMPLE_RATE_44_1_KHZ)
+		return CG2900_FM_CMD_SET_CTRL_CONV_UP;
+	else
+		return CG2900_FM_CMD_SET_CTRL_CONV_DOWN;
+}
+
+/*
+ *	Internal helper functions
+ */
+
+/**
+ * read_cb() - Handle data received from STE connectivity driver.
+ * @dev:	Device receiving data.
+ * @skb:	Buffer with data coming form device.
+ */
+static void read_cb(struct cg2900_device *dev, struct sk_buff *skb)
+{
+	struct audio_cb_info *cb_info;
+
+	CG2900_INFO("CG2900 Audio: read_cb");
+
+	if (!dev) {
+		CG2900_ERR("NULL supplied as dev");
+		return;
+	}
+
+	if (!skb) {
+		CG2900_ERR("NULL supplied as skb");
+		return;
+	}
+
+	cb_info = (struct audio_cb_info *)dev->user_data;
+	if (!cb_info) {
+		CG2900_ERR("NULL supplied as cb_info");
+		return;
+	}
+	if (!(cb_info->user)) {
+		CG2900_ERR("NULL supplied as cb_info->user");
+		return;
+	}
+
+	/* Mark that packet has been received */
+	SET_RESP_STATE(cb_info->user->resp_state, RESP_RECEIVED);
+
+	/* Handle packet depending on channel */
+	if (cb_info->channel == BT_CHANNEL_USED) {
+		skb_queue_tail(&(audio_info->bt_queue), skb);
+		wake_up_interruptible(&wq_bt);
+	} else if (cb_info->channel == FM_CHANNEL_USED) {
+		skb_queue_tail(&(audio_info->fm_queue), skb);
+		wake_up_interruptible(&wq_fm);
+	} else {
+		/* Unhandled channel; free the packet */
+		CG2900_ERR("Received callback on bad channel %d",
+			   cb_info->channel);
+		kfree_skb(skb);
+	}
+}
+
+/**
+ * reset_cb() - Reset callback function.
+ * @dev:        CG2900_Core device resetting.
+ */
+static void reset_cb(struct cg2900_device *dev)
+{
+	CG2900_INFO("CG2900 Audio: reset_cb");
+	mutex_lock(&audio_info->management_mutex);
+	audio_info->nbr_of_users_active = 0;
+	audio_info->state = RESET;
+	mutex_unlock(&audio_info->management_mutex);
+}
+
+static struct cg2900_callbacks cg2900_cb = {
+	.read_cb = read_cb,
+	.reset_cb = reset_cb
+};
+
+/**
+ * get_session_user() - Check that supplied session is within valid range.
+ * @session:	Session ID.
+ *
+ * Returns:
+ *   Audio_user if there is no error.
+ *   NULL for bad session ID.
+ */
+static struct audio_user *get_session_user(int session)
+{
+	struct audio_user *audio_user;
+
+	if (session < FIRST_USER || session >= MAX_NBR_OF_USERS) {
+		CG2900_ERR("Calling with invalid session %d", session);
+		return NULL;
+	}
+
+	audio_user = audio_info->audio_sessions[session];
+	if (!audio_user)
+		CG2900_ERR("Calling with non-opened session %d", session);
+	return audio_user;
+}
+
+/**
+ * del_endpoint_private() - Deletes an endpoint from @list.
+ * @endpoint_id:	Endpoint ID.
+ * @list:		List of endpoints.
+ *
+ * Deletes an endpoint from the supplied endpoint list.
+ * This function is not protected by any semaphore.
+ */
+static void del_endpoint_private(enum cg2900_audio_endpoint_id endpoint_id,
+				 struct endpoint_list *list)
+{
+	struct list_head *cursor, *next;
+	struct endpoint_config_node *tmp;
+
+	list_for_each_safe(cursor, next, &(list->ep_list)) {
+		tmp = list_entry(cursor, struct endpoint_config_node, list);
+		if (tmp->endpoint_id == endpoint_id) {
+			list_del(cursor);
+			kfree(tmp);
+		}
+	}
+}
+
+/**
+ * add_endpoint() - Add endpoint node to @list.
+ * @ep_config:	Endpoint configuration.
+ * @list:	List of endpoints.
+ *
+ * Add endpoint node to the supplied list and copies supplied config to node.
+ * If a node already exists for the supplied endpoint, the old node is removed
+ * and replaced by the new node.
+ */
+static void add_endpoint(struct cg2900_endpoint_config *ep_config,
+			 struct endpoint_list *list)
+{
+	struct endpoint_config_node *item;
+
+	item = kzalloc(sizeof(*item), GFP_KERNEL);
+	if (!item) {
+		CG2900_ERR("Failed to alloc memory!");
+		return;
+	}
+
+	/* Store values */
+	item->endpoint_id = ep_config->endpoint_id;
+	memcpy(&(item->config), &(ep_config->config), sizeof(item->config));
+
+	mutex_lock(&(list->management_mutex));
+
+	/*
+	 * Check if endpoint ID already exist in list.
+	 * If that is the case, remove it.
+	 */
+	if (!list_empty(&(list->ep_list)))
+		del_endpoint_private(ep_config->endpoint_id, list);
+
+	list_add_tail(&(item->list), &(list->ep_list));
+
+	mutex_unlock(&(list->management_mutex));
+}
+
+/**
+ * find_endpoint() - Finds endpoint identified by @endpoint_id in @list.
+ * @endpoint_id:	Endpoint ID.
+ * @list:		List of endpoints.
+ *
+ * Returns:
+ *   Endpoint configuration if there is no error.
+ *   NULL if no configuration can be found for @endpoint_id.
+ */
+static union cg2900_endpoint_config_union *
+find_endpoint(enum cg2900_audio_endpoint_id endpoint_id,
+	      struct endpoint_list *list)
+{
+	struct list_head *cursor, *next;
+	struct endpoint_config_node *tmp;
+	struct endpoint_config_node *ret_ep = NULL;
+
+	mutex_lock(&list->management_mutex);
+	list_for_each_safe(cursor, next, &(list->ep_list)) {
+		tmp = list_entry(cursor, struct endpoint_config_node, list);
+		if (tmp->endpoint_id == endpoint_id) {
+			ret_ep = tmp;
+			break;
+		}
+	}
+	mutex_unlock(&list->management_mutex);
+
+	if (ret_ep)
+		return &(ret_ep->config);
+	else
+		return NULL;
+}
+
+/**
+ * flush_endpoint_list() - Deletes all stored endpoints in @list.
+ * @list:	List of endpoints.
+ */
+static void flush_endpoint_list(struct endpoint_list *list)
+{
+	struct list_head *cursor, *next;
+	struct endpoint_config_node *tmp;
+
+	mutex_lock(&list->management_mutex);
+	list_for_each_safe(cursor, next, &(list->ep_list)) {
+		tmp = list_entry(cursor, struct endpoint_config_node, list);
+		list_del(cursor);
+		kfree(tmp);
+	}
+	mutex_unlock(&list->management_mutex);
+}
+
+/**
+ * new_stream_id() - Allocate a new stream id.
+ *
+ * Returns:
+ *  0-127 new valid id.
+ *  -ENOMEM if no id is available.
+ */
+static s8 new_stream_id(void)
+{
+	int r;
+
+	mutex_lock(&audio_info->management_mutex);
+
+	r = find_first_zero_bit(&audio_info->stream_ids,
+				8 * sizeof(audio_info->stream_ids));
+
+	if (r >= 8 * sizeof(audio_info->stream_ids)) {
+		r = -ENOMEM;
+		goto out;
+	}
+
+	audio_info->stream_ids |= (0x01u << r);
+
+out:
+	mutex_unlock(&audio_info->management_mutex);
+	return r;
+}
+
+/**
+ * release_stream_id() - Release a stream id.
+ * @id: stream to release.
+ */
+static void release_stream_id(u8 id)
+{
+	if (id >= 8 * sizeof(audio_info->stream_ids))
+		return;
+
+	mutex_lock(&audio_info->management_mutex);
+	audio_info->stream_ids &= ~(0x01u << id);
+	mutex_unlock(&audio_info->management_mutex);
+}
+
+/**
+ * receive_fm_write_response() - Wait for and handle the response to
an FM Legacy WriteCommand request.
+ * @audio_user:	Audio user to check for.
+ * @command:	FM command to wait for.
+ *
+ * This function first waits (up to 5 seconds) for a response to an FM
+ * write command and when one arrives, it checks that it is the one we
+ * are waiting for and also that no error has occurred.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -ECOMM if no response was received.
+ *   -EIO for other errors.
+ */
+static int receive_fm_write_response(struct audio_user *audio_user,
+				     u16 command)
+{
+	int err = 0;
+	struct sk_buff *skb;
+	struct fm_leg_cmd_cmpl *pkt;
+	u16 rsp_cmd;
+
+	/*
+	 * Wait for callback to receive command complete and then wake us up
+	 * again.
+	 */
+	if (0 >= wait_event_interruptible_timeout(wq_fm,
+			audio_user->resp_state == RESP_RECEIVED,
+			msecs_to_jiffies(RESP_TIMEOUT))) {
+		/* We timed out or an error occurred */
+		CG2900_ERR("Error occurred while waiting for return packet.");
+		return -ECOMM;
+	}
+
+	/* OK, now we should have received answer. Let's check it. */
+	skb = skb_dequeue_tail(&audio_info->fm_queue);
+	if (!skb) {
+		CG2900_ERR("No skb in queue when it should be there");
+		return -EIO;
+	}
+
+	pkt = (struct fm_leg_cmd_cmpl *)skb->data;
+
+	/* Check if we received the correct event */
+	if (pkt->opcode != CG2900_FM_GEN_ID_LEGACY) {
+		CG2900_ERR("Received unknown FM packet. 0x%X %X %X %X %X",
+			   skb->data[0], skb->data[1], skb->data[2],
+			   skb->data[3], skb->data[4]);
+		err = -EIO;
+		goto error_handling_free_skb;
+	}
+
+	/* FM Legacy Command complete event */
+	rsp_cmd = cg2900_get_fm_cmd_id(le16_to_cpu(pkt->response_head));
+
+	if (pkt->fm_function != CG2900_FM_CMD_PARAM_WRITECOMMAND ||
+	    rsp_cmd != command) {
+		CG2900_ERR("Received unexpected packet func 0x%X cmd 0x%04X",
+			   pkt->fm_function, rsp_cmd);
+		err = -EIO;
+		goto error_handling_free_skb;
+	}
+
+	if (pkt->cmd_status != CG2900_FM_CMD_STATUS_COMMAND_SUCCEEDED) {
+		CG2900_ERR("FM Command failed (%d)", pkt->cmd_status);
+		err = -EIO;
+		goto error_handling_free_skb;
+	}
+	/* Operation succeeded. We are now done */
+
+error_handling_free_skb:
+	kfree_skb(skb);
+	return err;
+}
+
+/**
+ * receive_bt_cmd_complete() - Wait for and handle an BT Command
Complete event.
+ * @audio_user:	Audio user to check for.
+ * @rsp:	Opcode of BT command to wait for.
+ * @data:	Pointer to buffer if any received data should be stored (except
+ *		status).
+ * @data_len:	Length of @data in bytes.
+ *
+ * This function first waits for BT Command Complete event (up to 5 seconds)
+ * and when one arrives, it checks that it is the one we are waiting for and
+ * also that no error has occurred.
+ * If @data is supplied it also copies received data into @data.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -ECOMM if no response was received.
+ *   -EIO for other errors.
+ */
+static int receive_bt_cmd_complete(struct audio_user *audio_user, u16 rsp,
+				   void *data, int data_len)
+{
+	int err = 0;
+	struct sk_buff *skb;
+	struct bt_cmd_cmpl_event *evt;
+	u16 opcode;
+
+	/*
+	 * Wait for callback to receive command complete and then wake us up
+	 * again.
+	 */
+	if (0 >= wait_event_interruptible_timeout(wq_bt,
+					audio_user->resp_state == RESP_RECEIVED,
+					msecs_to_jiffies(RESP_TIMEOUT))) {
+		/* We timed out or an error occurred */
+		CG2900_ERR("Error occurred while waiting for return packet.");
+		return -ECOMM;
+	}
+
+	/* OK, now we should have received answer. Let's check it. */
+	skb = skb_dequeue_tail(&audio_info->bt_queue);
+	if (!skb) {
+		CG2900_ERR("No skb in queue when it should be there");
+		return -EIO;
+	}
+
+	evt = (struct bt_cmd_cmpl_event *)skb->data;
+	if (evt->eventcode != HCI_BT_EVT_CMD_COMPLETE) {
+		CG2900_ERR("We did not receive the event we expected (0x%X)",
+			   evt->eventcode);
+		err = -EIO;
+		goto error_handling_free_skb;
+	}
+
+	opcode = le16_to_cpu(evt->opcode);
+	if (opcode != rsp) {
+		CG2900_ERR("Received cmd complete for unexpected command: "
+			   "0x%04X", opcode);
+		err = -EIO;
+		goto error_handling_free_skb;
+	}
+
+	if (evt->status != HCI_BT_ERROR_NO_ERROR) {
+		CG2900_ERR("Received command complete with err %d",
+			   evt->status);
+		err = -EIO;
+		goto error_handling_free_skb;
+	}
+
+	/* Copy the rest of the parameters if a buffer has been supplied.
+	 * The caller must have set the length correctly.
+	 */
+	if (data)
+		memcpy(data, evt->data, data_len);
+
+	/* Operation succeeded. We are now done */
+
+error_handling_free_skb:
+	kfree_skb(skb);
+	return err;
+}
+
+/**
+ * send_vs_session_ctrl() - Formats an sends a
CG2900_BT_VS_SESSION_CTRL command.
+ * @user:          Audio user this command belongs to.
+ * @stream_handle: Handle to stream.
+ * @command:       Command to execute on stream, should be one of
+ *                 CG2900_BT_SESSION_START, CG2900_BT_SESSION_STOP,
+ *                 CG2900_BT_SESSION_PAUSE, CG2900_BT_SESSION_RESUME.
+ *
+ * Packs and sends a command packet and waits for the response. Must
+ * be called with the bt_mutex held.
+ *
+ * Returns:
+ *  0 if there is no error.
+ *  -ENOMEM if not possible to allocate packet.
+ *  -ECOMM if no response was received.
+ *  -EIO for other errors.
+ */
+static int send_vs_session_ctrl(struct audio_user *user,
+				u8 stream_handle, u8 command)
+{
+	int err = 0;
+	struct bt_vs_session_ctrl_cmd *pkt;
+	struct sk_buff *skb;
+
+	CG2900_INFO("BT: HCI_VS_Session_Control");
+
+	skb = cg2900_alloc_skb(sizeof(*pkt), GFP_KERNEL);
+	if (!skb) {
+		CG2900_ERR("Could not allocate skb");
+		return -ENOMEM;
+	}
+
+	/* Enter data into the skb */
+	pkt = (struct bt_vs_session_ctrl_cmd *) skb_put(skb, sizeof(*pkt));
+
+	pkt->opcode  = cpu_to_le16(CG2900_BT_VS_SESSION_CTRL);
+	pkt->plen    = BT_PARAM_LEN(sizeof(*pkt));
+	pkt->id      = stream_handle;
+	pkt->control = command; /* Start/stop etc */
+
+	cb_info_bt.user = user;
+	SET_RESP_STATE(user->resp_state, WAITING);
+
+	/* Send packet to controller */
+	err = cg2900_write(audio_info->dev_bt, skb);
+	if (err) {
+		CG2900_ERR("Error occurred while transmitting skb (%d)", err);
+		kfree_skb(skb);
+		goto finished;
+	}
+
+	err = receive_bt_cmd_complete(user, CG2900_BT_VS_SESSION_CTRL,
+				      NULL, 0);
+finished:
+	SET_RESP_STATE(user->resp_state, IDLE);
+	return err;
+}
+
+/**
+ * send_vs_session_config() - Formats an sends a
CG2900_BT_VS_SESSION_CONFIG command.
+ * @user:          Audio user this command belongs to.
+ * @config_stream: Custom function for configuring the stream.
+ * @priv_data:     Private data passed to @config_stream untouched.
+ *
+ * Packs and sends a command packet and waits for the response. Must
+ * be called with the bt_mutex held.
+ *
+ * Space is allocated for one stream and a custom function is used to
+ * fill in the stream configuration.
+ *
+ * Returns:
+ *  0-255 stream handle if no error.
+ *  -ENOMEM if not possible to allocate packet.
+ *  -ECOMM if no response was received.
+ *  -EIO for other errors.
+ */
+static int send_vs_session_config(struct audio_user *user,
+	void(*config_stream)(void *, struct session_config_stream *),
+	void *priv_data)
+{
+	int err = 0;
+	struct sk_buff *skb;
+	struct bt_vs_session_config_cmd *pkt;
+	u8 session_id;
+
+	CG2900_INFO("BT: HCI_VS_Set_Session_Configuration");
+
+	skb = cg2900_alloc_skb(sizeof(*pkt), GFP_KERNEL);
+	if (!skb) {
+		CG2900_ERR("Could not allocate skb");
+		return -ENOMEM;
+	}
+
+	pkt = (struct bt_vs_session_config_cmd *)skb_put(skb, sizeof(*pkt));
+	/* zero the packet so we don't have to set all reserved fields */
+	memset(pkt, 0, sizeof(*pkt));
+
+	/* Common parameters */
+	pkt->opcode    = cpu_to_le16(CG2900_BT_VS_SET_SESSION_CONFIG);
+	pkt->plen      = BT_PARAM_LEN(sizeof(*pkt));
+	pkt->n_streams = 1; /* 1 stream configuration supplied */
+
+	/* Let the custom-function fill in the rest */
+	config_stream(priv_data, &pkt->stream);
+
+	cb_info_bt.user = user;
+	SET_RESP_STATE(user->resp_state, WAITING);
+
+	/* Send packet to controller */
+	err = cg2900_write(audio_info->dev_bt, skb);
+	if (err) {
+		CG2900_ERR("Error occurred while transmitting skb (%d)", err);
+		kfree_skb(skb);
+		goto finished;
+	}
+
+	err = receive_bt_cmd_complete(user,
+				      CG2900_BT_VS_SET_SESSION_CONFIG,
+				      &session_id, sizeof(session_id));
+	/* Return session id/stream handle if success */
+	if (!err)
+		err = session_id;
+
+finished:
+	SET_RESP_STATE(user->resp_state, IDLE);
+	return err;
+}
+
+/**
+ * send_fm_write_1_param() - Formats and sends an FM legacy write
command with one parameter.
+ * @user:    Audio user this command belongs to.
+ * @command: Command.
+ * @param:   Parameter for command.
+ *
+ * Packs and sends a command packet and waits for the response. Must
+ * be called with the fm_mutex held.
+ *
+ * Returns:
+ *  0 if there is no error.
+ *  -ENOMEM if not possible to allocate packet.
+ *  -ECOMM if no response was received.
+ *  -EIO for other errors.
+ */
+static int send_fm_write_1_param(struct audio_user *user,
+				 u16 command, u16 param)
+{
+	int err = 0;
+	struct sk_buff *skb;
+	struct fm_leg_cmd *cmd;
+	size_t len;
+
+	/* base package + one parameter */
+	len = sizeof(*cmd) + sizeof(cmd->fm_cmd.data[0]);
+
+	skb = cg2900_alloc_skb(len, GFP_KERNEL);
+	if (!skb) {
+		CG2900_ERR("Could not allocate skb");
+		return -ENOMEM;
+	}
+
+	cmd = (struct fm_leg_cmd *)skb_put(skb, len);
+
+	cmd->length      = CG2900_FM_CMD_PARAM_LEN(len);
+	cmd->opcode      = CG2900_FM_GEN_ID_LEGACY;
+	cmd->read_write  = CG2900_FM_CMD_LEG_PARAM_WRITE;
+	cmd->fm_function = CG2900_FM_CMD_PARAM_WRITECOMMAND;
+	/* one parameter - builtin assumption for this function */
+	cmd->fm_cmd.head    = cpu_to_le16(cg2900_make_fm_cmd_id(command, 1));
+	cmd->fm_cmd.data[0] = cpu_to_le16(param);
+
+	cb_info_fm.user = user;
+	SET_RESP_STATE(user->resp_state, WAITING);
+
+	/* Send packet to controller */
+	err = cg2900_write(audio_info->dev_fm, skb);
+	if (err) {
+		CG2900_ERR("Error occurred while transmitting skb (%d)", err);
+		kfree_skb(skb);
+		goto finished;
+	}
+
+	err = receive_fm_write_response(user, command);
+finished:
+	SET_RESP_STATE(user->resp_state, IDLE);
+	return err;
+}
+
+/**
+ * send_vs_stream_ctrl() - Formats an sends a
CG2900_MC_VS_STREAM_CONTROL command.
+ * @user:	Audio user this command belongs to.
+ * @stream:	Stream id.
+ * @command:	Start/stop etc.
+ *
+ * Packs and sends a command packet and waits for the response. Must
+ * be called with the bt_mutex held.
+ *
+ * While the HCI command allows for multiple streams in one command,
+ * this function only handles one.
+ *
+ * Returns:
+ *  0 if there is no error.
+ *  -ENOMEM if not possible to allocate packet.
+ *  -ECOMM if no response was received.
+ *  -EIO for other errors.
+ */
+static int send_vs_stream_ctrl(struct audio_user *user, u8 stream, u8 command)
+{
+	int err = 0;
+	struct sk_buff *skb;
+	struct mc_vs_stream_ctrl_cmd *cmd;
+	size_t len;
+	u8 vs_err;
+
+	/* basic length + one stream */
+	len = sizeof(*cmd) + sizeof(cmd->stream[0]);
+
+	skb = cg2900_alloc_skb(len, GFP_KERNEL);
+	if (!skb) {
+		CG2900_ERR("Could not allocate skb");
+		return -ENOMEM;
+	}
+
+	cmd = (struct mc_vs_stream_ctrl_cmd *)skb_put(skb, len);
+
+	cmd->opcode  = cpu_to_le16(CG2900_MC_VS_STREAM_CONTROL);
+	cmd->plen    = BT_PARAM_LEN(len);
+	cmd->command = command;
+
+	/* one stream */
+	cmd->n_streams  = 1;
+	cmd->stream[0] = stream;
+
+	cb_info_bt.user = user;
+	SET_RESP_STATE(user->resp_state, WAITING);
+
+	/* Send packet to controller */
+	err = cg2900_write(audio_info->dev_bt, skb);
+	if (err) {
+		CG2900_ERR("Error occurred while transmitting skb (%d)", err);
+		kfree_skb(skb);
+		goto finished;
+	}
+
+	/* All commands in PG2 API returns one byte with extra status */
+	err = receive_bt_cmd_complete(user,
+				      CG2900_MC_VS_STREAM_CONTROL,
+				      &vs_err, sizeof(vs_err));
+	if (err)
+		CG2900_DBG("VS_STREAM_CONTROL - failed with error %02x",
+			   vs_err);
+
+finished:
+	SET_RESP_STATE(user->resp_state, IDLE);
+	return err;
+}
+
+/**
+ * send_vs_create_stream() - Formats an sends a
CG2900_MC_VS_CREATE_STREAM command.
+ * @user:	Audio user this command belongs to.
+ * @inport:	Stream id.
+ * @outport:	Start/stop etc.
+ * @order:	Activation order.
+ *
+ * Packs and sends a command packet and waits for the response. Must
+ * be called with the bt_mutex held.
+ *
+ * Returns:
+ *  0 if there is no error.
+ *  -ENOMEM if not possible to allocate packet.
+ *  -ECOMM if no response was received.
+ *  -EIO for other errors.
+ */
+static int send_vs_create_stream(struct audio_user *user, u8 inport,
+				 u8 outport, u8 order)
+{
+	int err = 0;
+	struct sk_buff *skb;
+	struct mc_vs_create_stream_cmd *cmd;
+	s8 id;
+	u8 vs_err;
+
+	id = new_stream_id();
+	if (id < 0) {
+		CG2900_ERR("No free stream id");
+		err = -EIO;
+		goto finished;
+	}
+
+	skb = cg2900_alloc_skb(sizeof(*cmd), GFP_KERNEL);
+	if (!skb) {
+		CG2900_ERR("Could not allocate skb");
+		err = -ENOMEM;
+		goto finished_release_id;
+	}
+
+	cmd = (struct mc_vs_create_stream_cmd *)skb_put(skb, sizeof(*cmd));
+
+	cmd->opcode  = cpu_to_le16(CG2900_MC_VS_CREATE_STREAM);
+	cmd->plen    = BT_PARAM_LEN(sizeof(*cmd));
+	cmd->id      = (u8)id;
+	cmd->inport  = inport;
+	cmd->outport = outport;
+	cmd->order   = order;
+
+	cb_info_bt.user = user;
+	SET_RESP_STATE(user->resp_state, WAITING);
+
+	/* Send packet to controller */
+	err = cg2900_write(audio_info->dev_bt, skb);
+	if (err) {
+		CG2900_ERR("Error occurred while transmitting skb (%d)", err);
+		kfree_skb(skb);
+		goto finished_release_id;
+	}
+
+	/* All commands in PG2 API returns one byte with extra status */
+	err = receive_bt_cmd_complete(user,
+				      CG2900_MC_VS_CREATE_STREAM,
+				      &vs_err, sizeof(vs_err));
+	if (err) {
+		CG2900_DBG("VS_CREATE_STREAM - failed with error %02x",
+			   vs_err);
+		goto finished_release_id;
+	}
+
+	err = id;
+	goto finished;
+
+finished_release_id:
+	release_stream_id(id);
+finished:
+	SET_RESP_STATE(user->resp_state, IDLE);
+	return err;
+}
+
+/**
+ * send_vs_port_cfg() - Formats an sends a CG2900_MC_VS_PORT_CONFIG command.
+ * @user:	Audio user this command belongs to.
+ * @port:	Port id to configure.
+ * @cfg:	Pointer to specific configuration.
+ * @cfglen:	Length of configuration.
+ *
+ * Packs and sends a command packet and waits for the response. Must
+ * be called with the bt_mutex held.
+ *
+ * Returns:
+ *  0 if there is no error.
+ *  -ENOMEM if not possible to allocate packet.
+ *  -ECOMM if no response was received.
+ *  -EIO for other errors.
+ */
+static int send_vs_port_cfg(struct audio_user *user, u8 port,
+			    const void *cfg, size_t cfglen)
+{
+	int err = 0;
+	struct sk_buff *skb;
+	struct mc_vs_port_cfg_cmd *cmd;
+	void *ptr;
+	u8 vs_err;
+
+	skb = cg2900_alloc_skb(sizeof(*cmd) + cfglen, GFP_KERNEL);
+	if (!skb) {
+		CG2900_ERR("Could not allocate skb");
+		return -ENOMEM;
+	}
+
+	/* Fill in common part */
+	cmd = (struct mc_vs_port_cfg_cmd *) skb_put(skb, sizeof(*cmd));
+	cmd->opcode = cpu_to_le16(CG2900_MC_VS_PORT_CONFIG);
+	cmd->plen = BT_PARAM_LEN(sizeof(*cmd) + cfglen);
+	cmd->type = port;
+
+	/* Copy specific configuration */
+	ptr = skb_put(skb, cfglen);
+	memcpy(ptr, cfg, cfglen);
+
+	/* Send */
+	cb_info_bt.user = user;
+	SET_RESP_STATE(user->resp_state, WAITING);
+
+	err = cg2900_write(audio_info->dev_bt, skb);
+	if (err) {
+		CG2900_ERR("Error occurred while transmitting skb (%d)", err);
+		kfree_skb(skb);
+		goto finished;
+	}
+
+	/* All commands in PG2 API returns one byte with extra status */
+	err = receive_bt_cmd_complete(user, CG2900_MC_VS_PORT_CONFIG,
+				      &vs_err, sizeof(vs_err));
+	if (err)
+		CG2900_DBG("VS_PORT_CONFIG - failed with error %02x",
+			   vs_err);
+
+finished:
+	SET_RESP_STATE(user->resp_state, IDLE);
+	return err;
+}
+
+/**
+ * set_dai_config_pg1() - Internal implementation of
@cg2900_audio_set_dai_config for PG1 hardware.
+ * @audio_user:	Pointer to audio user struct.
+ * @config:	Pointer to the configuration to set.
+ *
+ * Sets the Digital Audio Interface (DAI) configuration for PG1
+ * hardware. This is and internal function and basic
+ * argument-verification should have been done by the caller.
+ *
+ * Returns:
+ *  0 if there is no error.
+ *  -EACCESS if port is not supported.
+ *  -ENOMEM if not possible to allocate packet.
+ *  -ECOMM if no response was received.
+ *  -EIO for other errors.
+ */
+static int set_dai_config_pg1(struct audio_user *audio_user,
+			      struct cg2900_dai_config *config)
+{
+	int err = 0;
+	struct cg2900_dai_conf_i2s_pcm *i2s_pcm;
+	struct sk_buff *skb = NULL;
+	struct bt_vs_set_hw_cfg_cmd_i2s *i2s_cmd;
+	struct bt_vs_set_hw_cfg_cmd_pcm *pcm_cmd;
+
+	/*
+	 * Use mutex to assure that only ONE command is sent at any time on
+	 * each channel.
+	 */
+	mutex_lock(&audio_info->bt_mutex);
+
+	/* Allocate the sk_buffer. The length is actually a max length since
+	 * length varies depending on logical transport.
+	 */
+	skb = cg2900_alloc_skb(CG2900_BT_LEN_VS_SET_HARDWARE_CONFIG,
+			       GFP_KERNEL);
+	if (!skb) {
+		CG2900_ERR("Could not allocate skb");
+		err = -ENOMEM;
+		goto finished_unlock_mutex;
+	}
+
+	/* Fill in hci-command according to received configuration */
+	switch (config->port) {
+	case PORT_0_I2S:
+		i2s_cmd = (struct bt_vs_set_hw_cfg_cmd_i2s *)
+			skb_put(skb, sizeof(*i2s_cmd));
+
+		i2s_cmd->opcode = cpu_to_le16(CG2900_BT_VS_SET_HARDWARE_CONFIG);
+		i2s_cmd->plen   = BT_PARAM_LEN(sizeof(*i2s_cmd));
+
+		i2s_cmd->vp_type = PORT_PROTOCOL_I2S;
+		i2s_cmd->port_id = 0x00; /* First/only I2S port */
+		i2s_cmd->half_period = config->conf.i2s.half_period;
+
+		i2s_cmd->master_slave = mc_i2s_role(config->conf.i2s.mode);
+
+		/* Store the new configuration */
+		mutex_lock(&audio_info->management_mutex);
+		memcpy(&(audio_info->i2s_config), &(config->conf.i2s),
+		       sizeof(config->conf.i2s));
+		audio_info->i2s_config_known = true;
+		mutex_unlock(&audio_info->management_mutex);
+		break;
+
+	case PORT_1_I2S_PCM:
+		pcm_cmd = (struct bt_vs_set_hw_cfg_cmd_pcm *)
+			skb_put(skb, sizeof(*pcm_cmd));
+
+		pcm_cmd->opcode = cpu_to_le16(CG2900_BT_VS_SET_HARDWARE_CONFIG);
+		pcm_cmd->plen   = BT_PARAM_LEN(sizeof(*pcm_cmd));
+
+		i2s_pcm = &config->conf.i2s_pcm;
+
+		/*
+		 * PG1 chips don't support I2S over the PCM/I2S bus,
+		 * and PG2 chips don't use this command
+		 */
+		if (i2s_pcm->protocol != PORT_PROTOCOL_PCM) {
+			CG2900_ERR("I2S not supported over the PCM/I2S bus");
+			err = -EACCES;
+			goto error_handling_free_skb;
+		}
+
+		pcm_cmd->vp_type = PORT_PROTOCOL_PCM;
+		pcm_cmd->port_id = 0x00; /* First/only PCM port */
+
+		HWCONFIG_PCM_SET_MODE(pcm_cmd, mc_pcm_role(i2s_pcm->mode));
+
+		HWCONFIG_PCM_SET_DIR(pcm_cmd, 0, i2s_pcm->slot_0_dir);
+		HWCONFIG_PCM_SET_DIR(pcm_cmd, 1, i2s_pcm->slot_1_dir);
+		HWCONFIG_PCM_SET_DIR(pcm_cmd, 2, i2s_pcm->slot_2_dir);
+		HWCONFIG_PCM_SET_DIR(pcm_cmd, 3, i2s_pcm->slot_3_dir);
+
+		pcm_cmd->bit_clock = i2s_pcm->clk;
+		pcm_cmd->frame_len =
+			cpu_to_le16(get_fs_duration(i2s_pcm->duration));
+
+		/* Store the new configuration */
+		mutex_lock(&audio_info->management_mutex);
+		memcpy(&(audio_info->i2s_pcm_config), &(config->conf.i2s_pcm),
+		       sizeof(config->conf.i2s_pcm));
+		audio_info->i2s_pcm_config_known = true;
+		mutex_unlock(&audio_info->management_mutex);
+		break;
+
+	default:
+		CG2900_ERR("Unknown port configuration %d", config->port);
+		err = -EACCES;
+		goto error_handling_free_skb;
+	};
+
+	cb_info_bt.user = audio_user;
+	SET_RESP_STATE(audio_user->resp_state, WAITING);
+
+	/* Send packet to controller */
+	err = cg2900_write(audio_info->dev_bt, skb);
+	if (err) {
+		CG2900_ERR("Error occurred while transmitting skb (%d)", err);
+		goto error_handling_free_skb;
+	}
+
+	err = receive_bt_cmd_complete(audio_user,
+				      CG2900_BT_VS_SET_HARDWARE_CONFIG,
+				      NULL, 0);
+
+	goto finished_unlock_mutex;
+
+error_handling_free_skb:
+	kfree_skb(skb);
+finished_unlock_mutex:
+	SET_RESP_STATE(audio_user->resp_state, IDLE);
+	mutex_unlock(&audio_info->bt_mutex);
+	return err;
+}
+
+/**
+ * set_dai_config_pg2() - Internal implementation of
@cg2900_audio_set_dai_config for PG2 hardware.
+ * @audio_user:	Pointer to audio user struct.
+ * @config:	Pointer to the configuration to set.
+ *
+ * Sets the Digital Audio Interface (DAI) configuration for PG2
+ * hardware. This is an internal function and basic
+ * argument-verification should have been done by the caller.
+ *
+ * Returns:
+ *  0 if there is no error.
+ *  -EACCESS if port is not supported.
+ *  -ENOMEM if not possible to allocate packet.
+ *  -ECOMM if no response was received.
+ *  -EIO for other errors.
+ */
+static int set_dai_config_pg2(struct audio_user *audio_user,
+			      struct cg2900_dai_config *config)
+{
+	int err = 0;
+	struct cg2900_dai_conf_i2s *i2s;
+	struct cg2900_dai_conf_i2s_pcm *i2s_pcm;
+
+	struct mc_vs_port_cfg_i2s i2s_cfg;
+	struct mc_vs_port_cfg_pcm_i2s pcm_cfg;
+
+	/*
+	 * Use mutex to assure that only ONE command is sent at any time on
+	 * each channel.
+	 */
+	mutex_lock(&audio_info->bt_mutex);
+
+	switch (config->port) {
+	case PORT_0_I2S:
+		i2s = &config->conf.i2s;
+
+		memset(&i2s_cfg, 0, sizeof(i2s_cfg)); /* just to be safe  */
+
+		/* master/slave */
+		PORTCFG_I2S_SET_ROLE(i2s_cfg, mc_i2s_role(i2s->mode));
+
+		PORTCFG_I2S_SET_HALFPERIOD(i2s_cfg, i2s->half_period);
+		PORTCFG_I2S_SET_CHANNELS(i2s_cfg,
+			mc_i2s_channel_select(i2s->channel_sel));
+		PORTCFG_I2S_SET_SRATE(i2s_cfg,
+			mc_i2s_sample_rate(i2s->sample_rate));
+		switch (i2s->word_width) {
+		case WORD_WIDTH_16:
+			PORTCFG_I2S_SET_WORDLEN(i2s_cfg, CG2900_MC_I2S_WORD_16);
+			break;
+		case WORD_WIDTH_32:
+			PORTCFG_I2S_SET_WORDLEN(i2s_cfg, CG2900_MC_I2S_WORD_32);
+			break;
+		}
+
+		/* Store the new configuration */
+		mutex_lock(&audio_info->management_mutex);
+		memcpy(&(audio_info->i2s_config), &(config->conf.i2s),
+		       sizeof(config->conf.i2s));
+		audio_info->i2s_config_known = true;
+		mutex_unlock(&audio_info->management_mutex);
+
+		/* Send */
+		err = send_vs_port_cfg(audio_user, CG2900_MC_PORT_I2S,
+				       &i2s_cfg, sizeof(i2s_cfg));
+		break;
+
+	case PORT_1_I2S_PCM:
+		i2s_pcm = &config->conf.i2s_pcm;
+
+		memset(&pcm_cfg, 0, sizeof(pcm_cfg)); /* just to be safe  */
+
+		/* master/slave */
+		PORTCFG_PCM_SET_ROLE(pcm_cfg, mc_pcm_role(i2s_pcm->mode));
+
+		/* set direction for all 4 slots */
+		PORTCFG_PCM_SET_DIR(pcm_cfg, 0, i2s_pcm->slot_0_dir);
+		PORTCFG_PCM_SET_DIR(pcm_cfg, 1, i2s_pcm->slot_1_dir);
+		PORTCFG_PCM_SET_DIR(pcm_cfg, 2, i2s_pcm->slot_2_dir);
+		PORTCFG_PCM_SET_DIR(pcm_cfg, 3, i2s_pcm->slot_3_dir);
+
+		/* set used SCO slots, other use cases not supported atm */
+		PORTCFG_PCM_SET_SCO_USED(pcm_cfg, 0, i2s_pcm->slot_0_used);
+		PORTCFG_PCM_SET_SCO_USED(pcm_cfg, 1, i2s_pcm->slot_1_used);
+		PORTCFG_PCM_SET_SCO_USED(pcm_cfg, 2, i2s_pcm->slot_2_used);
+		PORTCFG_PCM_SET_SCO_USED(pcm_cfg, 3, i2s_pcm->slot_3_used);
+
+		/* slot starts */
+		pcm_cfg.slot_start[0] = i2s_pcm->slot_0_start;
+		pcm_cfg.slot_start[1] = i2s_pcm->slot_1_start;
+		pcm_cfg.slot_start[2] = i2s_pcm->slot_2_start;
+		pcm_cfg.slot_start[3] = i2s_pcm->slot_3_start;
+
+		/* audio/voice sample-rate ratio */
+		PORTCFG_PCM_SET_RATIO(pcm_cfg, i2s_pcm->ratio);
+
+		/* PCM or I2S mode */
+		PORTCFG_PCM_SET_MODE(pcm_cfg, i2s_pcm->protocol);
+
+		pcm_cfg.frame_len = i2s_pcm->duration;
+
+		PORTCFG_PCM_SET_BITCLK(pcm_cfg, i2s_pcm->clk);
+		PORTCFG_PCM_SET_SRATE(pcm_cfg,
+			mc_pcm_sample_rate(i2s_pcm->sample_rate));
+
+		/* Store the new configuration */
+		mutex_lock(&audio_info->management_mutex);
+		memcpy(&(audio_info->i2s_pcm_config), &(config->conf.i2s_pcm),
+		       sizeof(config->conf.i2s_pcm));
+		audio_info->i2s_pcm_config_known = true;
+		mutex_unlock(&audio_info->management_mutex);
+
+		/* Send */
+		err = send_vs_port_cfg(audio_user, CG2900_MC_PORT_PCM_I2S,
+				       &pcm_cfg, sizeof(pcm_cfg));
+		break;
+
+	default:
+		CG2900_ERR("Unknown port configuration %d", config->port);
+		err = -EACCES;
+	};
+
+	mutex_unlock(&audio_info->bt_mutex);
+	return err;
+}
+
+/**
+ * struct i2s_fm_stream_config_priv - Helper struct for stream i2s-fm streams.
+ * @fm_config:	FM endpoint configuration.
+ * @rx:		true for FM-RX, false for FM-TX.
+ */
+struct i2s_fm_stream_config_priv {
+	struct cg2900_endpoint_config_fm	*fm_config;
+	bool					rx;
+
+};
+
+/**
+ * config_i2s_fm_stream() - Callback for @send_vs_session_config.
+ * @_priv:	Pointer to a @i2s_fm_stream_config_priv struct.
+ * @cfg:	Pointer to stream config block in command packet.
+ *
+ * Fills in stream configuration for I2S-FM RX/TX.
+ */
+
+static void config_i2s_fm_stream(void *_priv,
+				 struct session_config_stream *cfg)
+{
+	struct i2s_fm_stream_config_priv *priv = _priv;
+	struct session_config_vport *fm;
+	struct session_config_vport *i2s;
+
+	cfg->media_type = CG2900_BT_SESSION_MEDIA_TYPE_AUDIO;
+
+	if (audio_info->i2s_config.channel_sel == CHANNEL_SELECTION_BOTH)
+		SESSIONCFG_SET_CHANNELS(cfg, CG2900_BT_MEDIA_CONFIG_STEREO);
+	else
+		SESSIONCFG_SET_CHANNELS(cfg, CG2900_BT_MEDIA_CONFIG_MONO);
+
+	SESSIONCFG_I2S_SET_SRATE(cfg,
+		session_config_sample_rate(priv->fm_config->sample_rate));
+
+	cfg->codec_type = CG2900_CODEC_TYPE_NONE;
+	/* codec mode and parameters not used  */
+
+	if (priv->rx) {
+		fm  = &cfg->inport;  /* FM is input */
+		i2s = &cfg->outport; /* I2S is output */
+	} else {
+		i2s = &cfg->inport;  /* I2S is input */
+		fm  = &cfg->outport; /* FM is output */
+	}
+
+	fm->type = CG2900_BT_VP_TYPE_FM;
+
+	i2s->type = CG2900_BT_VP_TYPE_I2S;
+	i2s->i2s.index   = CG2900_BT_SESSION_I2S_INDEX_I2S;
+	i2s->i2s.channel = audio_info->i2s_config.channel_sel;
+}
+
+/**
+ * conn_start_i2s_to_fm_rx() - Start an audio stream connecting FM RX to I2S.
+ * @audio_user:		Audio user to check for.
+ * @stream_handle:	[out] Pointer where to store the stream handle.
+ *
+ * This function sets up an FM RX to I2S stream.
+ * It does this by first setting the output mode and then the configuration of
+ * the External Sample Rate Converter.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -ECOMM if no response was received.
+ *   -ENOMEM upon allocation errors.
+ *   -EIO for other errors.
+ */
+static int conn_start_i2s_to_fm_rx(struct audio_user *audio_user,
+				   unsigned int *stream_handle)
+{
+	int err = 0;
+	union cg2900_endpoint_config_union *fm_config;
+
+	fm_config = find_endpoint(ENDPOINT_FM_RX,
+				  &(audio_info->endpoints));
+	if (!fm_config) {
+		CG2900_ERR("FM RX not configured before stream start");
+		return -EIO;
+	}
+
+	if (!(audio_info->i2s_config_known)) {
+		CG2900_ERR("I2S DAI not configured before stream start");
+		return -EIO;
+	}
+
+	/*
+	 * Use mutex to assure that only ONE command is sent at any
+	 * time on each channel.
+	 */
+	mutex_lock(&audio_info->fm_mutex);
+	mutex_lock(&audio_info->bt_mutex);
+
+	/*
+	 * Now set the output mode of the External Sample Rate Converter by
+	 * sending HCI_Write command with AUP_EXT_SetMode.
+	 */
+	err = send_fm_write_1_param(audio_user,
+				    CG2900_FM_CMD_ID_AUP_EXT_SET_MODE,
+				    CG2900_FM_CMD_AUP_EXT_SET_MODE_PARALLEL);
+	if (err)
+		goto finished_unlock_mutex;
+
+	/*
+	 * Now configure the External Sample Rate Converter by sending
+	 * HCI_Write command with AUP_EXT_SetControl.
+	 */
+	err = send_fm_write_1_param(
+		audio_user, CG2900_FM_CMD_ID_AUP_EXT_SET_CTRL,
+		fm_get_conversion(fm_config->fm.sample_rate));
+	if (err)
+		goto finished_unlock_mutex;
+
+	/* Set up the stream */
+	if (audio_info->revision == CHIP_REV_PG1) {
+		struct i2s_fm_stream_config_priv stream_priv;
+
+		/* Now send HCI_VS_Set_Session_Configuration command */
+		stream_priv.fm_config = &fm_config->fm;
+		stream_priv.rx = true;
+		err = send_vs_session_config(audio_user, config_i2s_fm_stream,
+					     &stream_priv);
+	} else {
+		struct mc_vs_port_cfg_fm fm_cfg;
+
+		memset(&fm_cfg, 0, sizeof(fm_cfg));
+
+		/* Configure port FM RX */
+		/* Expects 0-3 - same as user API - so no conversion needed */
+		PORTCFG_FM_SET_SRATE(fm_cfg, (u8)fm_config->fm.sample_rate);
+
+		err = send_vs_port_cfg(audio_user, CG2900_MC_PORT_FM_RX_1,
+				       &fm_cfg, sizeof(fm_cfg));
+		if (err)
+			goto finished_unlock_mutex;
+
+		/* CreateStream */
+		err = send_vs_create_stream(audio_user,
+					    CG2900_MC_PORT_FM_RX_1,
+					    CG2900_MC_PORT_I2S,
+					    0); /* chip doesn't care */
+	}
+
+	if (err < 0)
+		goto finished_unlock_mutex;
+
+	/* Store the stream handle (used for start and stop stream) */
+	*stream_handle = (u8)err;
+	CG2900_DBG("stream_handle set to %d", *stream_handle);
+
+	/* Now start the stream */
+	if (audio_info->revision == CHIP_REV_PG1)
+		err = send_vs_session_ctrl(audio_user, *stream_handle,
+					   CG2900_BT_SESSION_START);
+	else
+		err = send_vs_stream_ctrl(audio_user, *stream_handle,
+					  CG2900_MC_STREAM_START);
+
+finished_unlock_mutex:
+	SET_RESP_STATE(audio_user->resp_state, IDLE);
+	mutex_unlock(&audio_info->bt_mutex);
+	mutex_unlock(&audio_info->fm_mutex);
+	return err;
+}
+
+/**
+ * conn_start_i2s_to_fm_tx() - Start an audio stream connecting FM TX to I2S.
+ * @audio_user:		Audio user to check for.
+ * @stream_handle:	[out] Pointer where to store the stream handle.
+ *
+ * This function sets up an I2S to FM TX stream.
+ * It does this by first setting the Audio Input source and then setting the
+ * configuration and input source of BT sample rate converter.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -ECOMM if no response was received.
+ *   -ENOMEM upon allocation errors.
+ *   -EIO for other errors.
+ */
+static int conn_start_i2s_to_fm_tx(struct audio_user *audio_user,
+				   unsigned int *stream_handle)
+{
+	int err = 0;
+	union cg2900_endpoint_config_union *fm_config;
+
+	fm_config = find_endpoint(ENDPOINT_FM_TX, &(audio_info->endpoints));
+	if (!fm_config) {
+		CG2900_ERR("FM TX not configured before stream start");
+		return -EIO;
+	}
+
+	if (!(audio_info->i2s_config_known)) {
+		CG2900_ERR("I2S DAI not configured before stream start");
+		return -EIO;
+	}
+
+	/*
+	 * Use mutex to assure that only ONE command is sent at any time
+	 * on each channel.
+	 */
+	mutex_lock(&audio_info->fm_mutex);
+	mutex_lock(&audio_info->bt_mutex);
+
+	/*
+	 * Select Audio Input Source by sending HCI_Write command with
+	 * AIP_SetMode.
+	 */
+	CG2900_DBG("FM: AIP_SetMode");
+	err = send_fm_write_1_param(audio_user, CG2900_FM_CMD_ID_AIP_SET_MODE,
+				    CG2900_FM_CMD_AIP_SET_MODE_INPUT_DIG);
+	if (err)
+		goto finished_unlock_mutex;
+
+	/*
+	 * Now configure the BT sample rate converter by sending HCI_Write
+	 * command with AIP_BT_SetControl.
+	 */
+	CG2900_DBG("FM: AIP_BT_SetControl");
+	err = send_fm_write_1_param(
+		audio_user, CG2900_FM_CMD_ID_AIP_BT_SET_CTRL,
+		fm_get_conversion(fm_config->fm.sample_rate));
+	if (err)
+		goto finished_unlock_mutex;
+
+	/*
+	 * Now set input of the BT sample rate converter by sending HCI_Write
+	 * command with AIP_BT_SetMode.
+	 */
+	CG2900_DBG("FM: AIP_BT_SetMode");
+	err = send_fm_write_1_param(audio_user,
+				    CG2900_FM_CMD_ID_AIP_BT_SET_MODE,
+				    CG2900_FM_CMD_AIP_BT_SET_MODE_INPUT_PAR);
+	if (err)
+		goto finished_unlock_mutex;
+
+	/* Set up the stream */
+	if (audio_info->revision == CHIP_REV_PG1) {
+		struct i2s_fm_stream_config_priv stream_priv;
+
+		/* Now send HCI_VS_Set_Session_Configuration command */
+		stream_priv.fm_config = &fm_config->fm;
+		stream_priv.rx = false;
+		err = send_vs_session_config(audio_user, config_i2s_fm_stream,
+					     &stream_priv);
+	} else {
+		struct mc_vs_port_cfg_fm fm_cfg;
+
+		memset(&fm_cfg, 0, sizeof(fm_cfg));
+
+		/* Configure port FM TX */
+		/* Expects 0-3 - same as user API - so no conversion needed */
+		PORTCFG_FM_SET_SRATE(fm_cfg, (u8)fm_config->fm.sample_rate);
+
+		err = send_vs_port_cfg(audio_user, CG2900_MC_PORT_FM_TX,
+				       &fm_cfg, sizeof(fm_cfg));
+		if (err)
+			goto finished_unlock_mutex;
+
+		/* CreateStream */
+		err = send_vs_create_stream(audio_user,
+					    CG2900_MC_PORT_I2S,
+					    CG2900_MC_PORT_FM_TX,
+					    0); /* chip doesn't care */
+	}
+
+	if (err < 0)
+		goto finished_unlock_mutex;
+
+	/* Store the stream handle (used for start and stop stream) */
+	*stream_handle = (u8)err;
+	CG2900_DBG("stream_handle set to %d", *stream_handle);
+
+	/* Now start the stream */
+	if (audio_info->revision == CHIP_REV_PG1)
+		err = send_vs_session_ctrl(audio_user, *stream_handle,
+					   CG2900_BT_SESSION_START);
+	else
+		err = send_vs_stream_ctrl(audio_user, *stream_handle,
+					  CG2900_MC_STREAM_START);
+
+finished_unlock_mutex:
+	SET_RESP_STATE(audio_user->resp_state, IDLE);
+	mutex_unlock(&audio_info->bt_mutex);
+	mutex_unlock(&audio_info->fm_mutex);
+	return err;
+}
+
+/**
+ * config_pcm_sco_stream() - Callback for @send_vs_session_config.
+ * @_priv:	Pointer to a @cg2900_endpoint_config_sco_in_out struct.
+ * @cfg:	Pointer to stream config block in command packet.
+ *
+ * Fills in stream configuration for PCM-SCO.
+ */
+static void config_pcm_sco_stream(void *_priv,
+				  struct session_config_stream *cfg)
+{
+	struct cg2900_endpoint_config_sco_in_out *sco_ep = _priv;
+
+	cfg->media_type = CG2900_BT_SESSION_MEDIA_TYPE_AUDIO;
+
+	SESSIONCFG_SET_CHANNELS(cfg, CG2900_BT_MEDIA_CONFIG_MONO);
+	SESSIONCFG_I2S_SET_SRATE(cfg,
+		session_config_sample_rate(sco_ep->sample_rate));
+
+	cfg->codec_type = CG2900_CODEC_TYPE_NONE;
+	/* codec mode and parameters not used  */
+
+	cfg->inport.type = CG2900_BT_VP_TYPE_BT_SCO;
+	cfg->inport.sco.acl_handle = cpu_to_le16(DEFAULT_SCO_HANDLE);
+
+	cfg->outport.type = CG2900_BT_VP_TYPE_PCM;
+	cfg->outport.pcm.index = CG2900_BT_SESSION_PCM_INDEX_PCM_I2S;
+
+	SESSIONCFG_PCM_SET_USED(cfg->outport, 0,
+				audio_info->i2s_pcm_config.slot_0_used);
+	SESSIONCFG_PCM_SET_USED(cfg->outport, 1,
+				audio_info->i2s_pcm_config.slot_1_used);
+	SESSIONCFG_PCM_SET_USED(cfg->outport, 2,
+				audio_info->i2s_pcm_config.slot_2_used);
+	SESSIONCFG_PCM_SET_USED(cfg->outport, 3,
+				audio_info->i2s_pcm_config.slot_3_used);
+
+	cfg->outport.pcm.slot_start[0] =
+		audio_info->i2s_pcm_config.slot_0_start;
+	cfg->outport.pcm.slot_start[1] =
+		audio_info->i2s_pcm_config.slot_1_start;
+	cfg->outport.pcm.slot_start[2] =
+		audio_info->i2s_pcm_config.slot_2_start;
+	cfg->outport.pcm.slot_start[3] =
+		audio_info->i2s_pcm_config.slot_3_start;
+}
+
+/**
+ * conn_start_pcm_to_sco() - Start an audio stream connecting
Bluetooth (e)SCO to PCM_I2S.
+ * @audio_user:		Audio user to check for.
+ * @stream_handle:	[out] Pointer where to store the stream handle.
+ *
+ * This function sets up a BT to_from PCM_I2S stream. It does this by
+ * first setting the Session configuration and then starting the Audio
+ * Stream.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -ECOMM if no response was received.
+ *   -ENOMEM upon allocation errors.
+ *   Errors from @cg2900_write
+ *   -EIO for other errors.
+ */
+static int conn_start_pcm_to_sco(struct audio_user *audio_user,
+				 unsigned int *stream_handle)
+{
+	int err = 0;
+	union cg2900_endpoint_config_union *bt_config;
+
+	bt_config = find_endpoint(ENDPOINT_BT_SCO_INOUT,
+				  &(audio_info->endpoints));
+	if (!bt_config) {
+		CG2900_ERR("BT not configured before stream start");
+		return -EIO;
+	}
+
+	if (!(audio_info->i2s_pcm_config_known)) {
+		CG2900_ERR("I2S_PCM DAI not configured before stream start");
+		return -EIO;
+	}
+
+	/*
+	 * Use mutex to assure that only ONE command is sent at any time on each
+	 * channel.
+	 */
+	mutex_lock(&audio_info->bt_mutex);
+
+	/* Set up the stream */
+	if (audio_info->revision == CHIP_REV_PG1) {
+		err = send_vs_session_config(audio_user, config_pcm_sco_stream,
+					     &bt_config->sco);
+	} else {
+		struct mc_vs_port_cfg_sco sco_cfg;
+
+		/* zero codec params etc */
+		memset(&sco_cfg, 0, sizeof(sco_cfg));
+		sco_cfg.acl_id = DEFAULT_SCO_HANDLE;
+		PORTCFG_SCO_SET_WBS(sco_cfg, 0); /* No WBS yet */
+		PORTCFG_SCO_SET_CODEC(sco_cfg, CG2900_CODEC_TYPE_NONE);
+
+		err = send_vs_port_cfg(audio_user, CG2900_MC_PORT_BT_SCO,
+				       &sco_cfg, sizeof(sco_cfg));
+		if (err)
+			goto finished_unlock_mutex;
+
+		/* CreateStream */
+		err = send_vs_create_stream(audio_user,
+					    CG2900_MC_PORT_PCM_I2S,
+					    CG2900_MC_PORT_BT_SCO,
+					    0); /* chip doesn't care */
+	}
+
+	if (err < 0)
+		goto finished_unlock_mutex;
+
+	/* Store the stream handle (used for start and stop stream) */
+	*stream_handle = (u8)err;
+	CG2900_DBG("stream_handle set to %d", *stream_handle);
+
+	/* Now start the stream by sending HCI_VS_Session_Control command */
+	err = send_vs_session_ctrl(audio_user, *stream_handle,
+				   CG2900_BT_SESSION_START);
+
+finished_unlock_mutex:
+	SET_RESP_STATE(audio_user->resp_state, IDLE);
+	mutex_unlock(&audio_info->bt_mutex);
+	return err;
+}
+
+/**
+ * conn_stop_stream() - Stops an audio stream defined by @stream_handle.
+ * @audio_user:		Audio user to check for.
+ * @stream_handle:	Handle of the audio stream.
+ *
+ * This function is used to stop an audio stream defined by a stream
+ * handle. It does this by first stopping the stream and then
+ * resetting the session/stream.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -ECOMM if no response was received.
+ *   -ENOMEM upon allocation errors.
+ *   Errors from @cg2900_write.
+ *   -EIO for other errors.
+ */
+static int conn_stop_stream(struct audio_user *audio_user,
+			    unsigned int stream_handle)
+{
+	int err = 0;
+	struct sk_buff *skb;
+	u16 opcode;
+
+	/*
+	 * Use mutex to assure that only ONE command is sent at any
+	 * time on each channel.
+	 */
+	mutex_lock(&audio_info->bt_mutex);
+
+	/* Now stop the stream */
+	if (audio_info->revision == CHIP_REV_PG1)
+		err = send_vs_session_ctrl(audio_user, stream_handle,
+					   CG2900_BT_SESSION_STOP);
+	else
+		err = send_vs_stream_ctrl(audio_user, stream_handle,
+					  CG2900_MC_STREAM_STOP);
+	if (err)
+		goto finished_unlock_mutex;
+
+	/* Now delete the stream - format command... */
+	if (audio_info->revision == CHIP_REV_PG1) {
+		struct bt_vs_reset_session_cfg_cmd *cmd;
+
+		CG2900_DBG("BT: HCI_VS_Reset_Session_Configuration");
+
+		skb = cg2900_alloc_skb(sizeof(*cmd), GFP_KERNEL);
+		if (!skb) {
+			CG2900_ERR("Could not allocate skb");
+			err = -ENOMEM;
+			goto finished_unlock_mutex;
+		}
+
+		cmd = (struct bt_vs_reset_session_cfg_cmd *)
+			skb_put(skb, sizeof(*cmd));
+
+		opcode = CG2900_BT_VS_RESET_SESSION_CONFIG;
+		cmd->opcode = cpu_to_le16(opcode);
+		cmd->plen   = BT_PARAM_LEN(sizeof(*cmd));
+		cmd->id     = (u8)stream_handle;
+	} else {
+		struct mc_vs_delete_stream_cmd *cmd;
+
+		CG2900_DBG("BT: HCI_VS_Delete_Stream");
+
+		skb = cg2900_alloc_skb(sizeof(*cmd), GFP_KERNEL);
+		if (!skb) {
+			CG2900_ERR("Could not allocate skb");
+			err = -ENOMEM;
+			goto finished_unlock_mutex;
+		}
+
+		cmd = (struct mc_vs_delete_stream_cmd *)
+			skb_put(skb, sizeof(*cmd));
+
+		opcode = CG2900_MC_VS_DELETE_STREAM;
+		cmd->opcode = cpu_to_le16(opcode);
+		cmd->plen   = BT_PARAM_LEN(sizeof(*cmd));
+		cmd->stream = (u8)stream_handle;
+	}
+
+	/* ...and send it */
+	cb_info_bt.user = audio_user;
+	SET_RESP_STATE(audio_user->resp_state, WAITING);
+
+	err = cg2900_write(audio_info->dev_bt, skb);
+	if (err) {
+		CG2900_ERR("Error occurred while transmitting skb (%d)", err);
+		goto error_handling_free_skb;
+	}
+
+	/* wait for response */
+	if (audio_info->revision == CHIP_REV_PG1) {
+		err = receive_bt_cmd_complete(audio_user, opcode, NULL, 0);
+	} else {
+		u8 vs_err;
+
+		/* All commands in PG2 API returns one byte extra status */
+		err = receive_bt_cmd_complete(audio_user, opcode,
+					      &vs_err, sizeof(vs_err));
+
+		if (err)
+			CG2900_DBG("VS_DELETE_STREAM - failed with error %02x",
+				   vs_err);
+		else
+			release_stream_id(stream_handle);
+
+	}
+
+	goto finished_unlock_mutex;
+
+error_handling_free_skb:
+	kfree_skb(skb);
+finished_unlock_mutex:
+	SET_RESP_STATE(audio_user->resp_state, IDLE);
+	mutex_unlock(&audio_info->bt_mutex);
+	return err;
+}
+
+/**
+ * cg2900_audio_open() - Opens a session to the ST-Ericsson CG2900
Audio control interface.
+ * @session:	[out] Address where to store the session identifier.
+ *		Allocated by caller, must not be NULL.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -EINVAL upon bad input parameter.
+ *   -ENOMEM upon allocation failure.
+ *   -EMFILE if no more user session could be opened.
+ *   -EIO upon failure to register to CG2900.
+ */
+int cg2900_audio_open(unsigned int *session)
+{
+	int err = 0;
+	int i;
+
+	CG2900_INFO("cg2900_audio_open");
+
+	if (!session) {
+		CG2900_ERR("NULL supplied as session.");
+		return -EINVAL;
+	}
+
+	mutex_lock(&audio_info->management_mutex);
+
+	*session = 0;
+
+	/*
+	 * First find a free session to use and allocate the session structure.
+	 */
+	for (i = FIRST_USER;
+	     i < MAX_NBR_OF_USERS && audio_info->audio_sessions[i];
+	     i++)
+		; /* Just loop until found or end reached */
+
+	if (i >= MAX_NBR_OF_USERS) {
+		CG2900_ERR("Couldn't find free user");
+		err = -EMFILE;
+		goto finished;
+	}
+
+	audio_info->audio_sessions[i] =
+			kzalloc(sizeof(*(audio_info->audio_sessions[0])),
+				GFP_KERNEL);
+	if (!audio_info->audio_sessions[i]) {
+		CG2900_ERR("Could not allocate user");
+		err = -ENOMEM;
+		goto finished;
+	}
+	CG2900_DBG("Found free session %d", i);
+	*session = i;
+	audio_info->nbr_of_users_active++;
+
+	SET_RESP_STATE(audio_info->audio_sessions[*session]->resp_state, IDLE);
+	audio_info->audio_sessions[*session]->session = *session;
+
+	if (audio_info->nbr_of_users_active == 1) {
+		struct cg2900_rev_data rev_data;
+
+		/*
+		 * First user so register to CG2900 Core.
+		 * First the BT audio device.
+		 */
+		audio_info->dev_bt = cg2900_register_user(CG2900_BT_AUDIO,
+							  &cg2900_cb);
+		if (!audio_info->dev_bt) {
+			CG2900_ERR("Failed to register BT audio channel");
+			err = -EIO;
+			goto error_handling;
+		}
+
+		/* Store the callback info structure */
+		audio_info->dev_bt->user_data = &cb_info_bt;
+
+		/* Then the FM audio device */
+		audio_info->dev_fm = cg2900_register_user(CG2900_FM_RADIO_AUDIO,
+							  &cg2900_cb);
+		if (!audio_info->dev_fm) {
+			CG2900_ERR("Failed to register FM audio channel");
+			err = -EIO;
+			goto error_handling;
+		}
+
+		/* Store the callback info structure */
+		audio_info->dev_fm->user_data = &cb_info_fm;
+
+		/* Read chip revision data */
+		if (!cg2900_get_local_revision(&rev_data)) {
+			CG2900_ERR("Couldn't retrieve revision data");
+			err = -EIO;
+			goto error_handling;
+		}
+
+		/* Decode revision data */
+		switch (rev_data.revision) {
+		case CG2900_PG1_REV:
+		case CG2900_PG1_SPECIAL_REV:
+			audio_info->revision = CHIP_REV_PG1;
+			break;
+
+		case CG2900_PG2_REV:
+			audio_info->revision = CHIP_REV_PG2;
+			break;
+
+		default:
+			CG2900_ERR("Chip rev 0x%04X sub 0x%04X not supported",
+				   rev_data.revision, rev_data.sub_version);
+			err = -EIO;
+			goto error_handling;
+		}
+
+		audio_info->state = OPENED;
+	}
+
+	goto finished;
+
+error_handling:
+	if (audio_info->dev_fm) {
+		cg2900_deregister_user(audio_info->dev_fm);
+		audio_info->dev_fm = NULL;
+	}
+	if (audio_info->dev_bt) {
+		cg2900_deregister_user(audio_info->dev_bt);
+		audio_info->dev_bt = NULL;
+	}
+	audio_info->nbr_of_users_active--;
+	kfree(audio_info->audio_sessions[*session]);
+	audio_info->audio_sessions[*session] = NULL;
+finished:
+	mutex_unlock(&audio_info->management_mutex);
+	return err;
+}
+EXPORT_SYMBOL(cg2900_audio_open);
+
+/**
+ * cg2900_audio_close() - Closes an opened session to the ST-Ericsson
CG2900 audio control interface.
+ * @session:	[in_out] Pointer to session identifier to close.
+ *		Will be 0 after this call.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -EINVAL upon bad input parameter.
+ *   -EIO if driver has not been opened.
+ *   -EACCES if session has not opened.
+ */
+int cg2900_audio_close(unsigned int *session)
+{
+	int err = 0;
+	struct audio_user *audio_user;
+
+	CG2900_INFO("cg2900_audio_close");
+
+	if (audio_info->state != OPENED) {
+		CG2900_ERR("Audio driver not open");
+		return -EIO;
+	}
+
+	if (!session) {
+		CG2900_ERR("NULL pointer supplied");
+		return -EINVAL;
+	}
+
+	audio_user = get_session_user(*session);
+	if (!audio_user) {
+		CG2900_ERR("Invalid session ID");
+		return -EINVAL;
+	}
+
+	mutex_lock(&audio_info->management_mutex);
+
+	if (!(audio_info->audio_sessions[*session])) {
+		CG2900_ERR("Session %d not opened", *session);
+		err = -EACCES;
+		goto err_unlock_mutex;
+	}
+
+	kfree(audio_info->audio_sessions[*session]);
+	audio_info->audio_sessions[*session] = NULL;
+	audio_info->nbr_of_users_active--;
+
+	if (audio_info->nbr_of_users_active == 0) {
+		/* No more sessions open. Deregister from CG2900 Core */
+		cg2900_deregister_user(audio_info->dev_fm);
+		cg2900_deregister_user(audio_info->dev_bt);
+		audio_info->state = CLOSED;
+	}
+
+	*session = 0;
+
+err_unlock_mutex:
+	mutex_unlock(&audio_info->management_mutex);
+	return err;
+}
+EXPORT_SYMBOL(cg2900_audio_close);
+
+/**
+ * cg2900_audio_set_dai_config() -  Sets the Digital Audio Interface
configuration.
+ * @session:	Session identifier this call is related to.
+ * @config:	Pointer to the configuration to set.
+ *		Allocated by caller, must not be NULL.
+ *
+ * Sets the Digital Audio Interface (DAI) configuration. The DAI is
the external
+ * interface between the combo chip and the platform.
+ * For example the PCM or I2S interface.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -EINVAL upon bad input parameter.
+ *   -EIO if driver has not been opened.
+ *   -ENOMEM upon allocation failure.
+ *   -EACCES if trying to set unsupported configuration.
+ *   Errors from @receive_bt_cmd_complete.
+ */
+int cg2900_audio_set_dai_config(unsigned int session,
+				struct cg2900_dai_config *config)
+{
+	int err = 0;
+	struct audio_user *audio_user;
+	struct cg2900_rev_data rev_data;
+
+	CG2900_INFO("cg2900_audio_set_dai_config");
+
+	if (audio_info->state != OPENED) {
+		CG2900_ERR("Audio driver not open");
+		return -EIO;
+	}
+
+	audio_user = get_session_user(session);
+	if (!audio_user)
+		return -EINVAL;
+
+	if (!cg2900_get_local_revision(&rev_data)) {
+		CG2900_ERR("Couldn't retrieve revision data");
+		return -EIO;
+	}
+
+	/* Different commands are used for PG1 and PG2 */
+	switch (rev_data.revision) {
+	case CG2900_PG1_REV:
+	case CG2900_PG1_SPECIAL_REV:
+		err = set_dai_config_pg1(audio_user, config);
+		break;
+
+	case CG2900_PG2_REV:
+		err = set_dai_config_pg2(audio_user, config);
+		break;
+
+	default:
+		CG2900_ERR("Chip rev 0x%04X sub 0x%04X not supported",
+			   rev_data.revision, rev_data.sub_version);
+		err = -EIO;
+	}
+
+	return err;
+}
+EXPORT_SYMBOL(cg2900_audio_set_dai_config);
+
+/**
+ * cg2900_audio_get_dai_config() - Gets the current Digital Audio
Interface configuration.
+ * @session:	Session identifier this call is related to.
+ * @config:	[out] Pointer to the configuration to get.
+ *		Allocated by caller, must not be NULL.
+ *
+ * Gets the current Digital Audio Interface configuration. Currently
this method
+ * can only be called after some one has called
+ * cg2900_audio_set_dai_config(), there is today no way of getting
+ * the static settings file parameters from this method.
+ * Note that the @port parameter within @config must be set when calling this
+ * function so that the ST-Ericsson CG2900 Audio driver will know which
+ * configuration to return.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -EINVAL upon bad input parameter.
+ *   -EIO if driver has not been opened or configuration has not been set.
+ */
+int cg2900_audio_get_dai_config(unsigned int session,
+				struct cg2900_dai_config *config)
+{
+	int err = 0;
+	struct audio_user *audio_user;
+
+	CG2900_INFO("cg2900_audio_get_dai_config");
+
+	if (audio_info->state != OPENED) {
+		CG2900_ERR("Audio driver not open");
+		return -EIO;
+	}
+
+	if (!config) {
+		CG2900_ERR("NULL supplied as config structure");
+		return -EINVAL;
+	}
+
+	audio_user = get_session_user(session);
+	if (!audio_user)
+		return -EINVAL;
+
+	/*
+	 * Return DAI configuration based on the received port.
+	 * If port has not been configured return error.
+	 */
+	switch (config->port) {
+	case PORT_0_I2S:
+		mutex_lock(&audio_info->management_mutex);
+		if (audio_info->i2s_config_known)
+			memcpy(&(config->conf.i2s),
+			       &(audio_info->i2s_config),
+			       sizeof(config->conf.i2s));
+		else
+			err = -EIO;
+		mutex_unlock(&audio_info->management_mutex);
+		break;
+
+	case PORT_1_I2S_PCM:
+		mutex_lock(&audio_info->management_mutex);
+		if (audio_info->i2s_pcm_config_known)
+			memcpy(&(config->conf.i2s_pcm),
+			       &(audio_info->i2s_pcm_config),
+			       sizeof(config->conf.i2s_pcm));
+		else
+			err = -EIO;
+		mutex_unlock(&audio_info->management_mutex);
+		break;
+
+	default:
+		CG2900_ERR("Unknown port configuration %d", config->port);
+		err = -EIO;
+		break;
+	};
+
+	return err;
+}
+EXPORT_SYMBOL(cg2900_audio_get_dai_config);
+
+/**
+ * cg2900_audio_config_endpoint() - Configures one endpoint in the
combo chip's audio system.
+ * @session:	Session identifier this call is related to.
+ * @config:	Pointer to the endpoint's configuration structure.
+ *
+ * Configures one endpoint in the combo chip's audio system.
+ * Supported @endpoint_id values are:
+ *  * ENDPOINT_BT_SCO_INOUT
+ *  * ENDPOINT_BT_A2DP_SRC
+ *  * ENDPOINT_FM_RX
+ *  * ENDPOINT_FM_TX
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -EINVAL upon bad input parameter.
+ *   -EIO if driver has not been opened.
+ *   -EACCES if supplied cg2900_dai_config struct contains not supported
+ *   endpoint_id.
+ */
+int cg2900_audio_config_endpoint(unsigned int session,
+				 struct cg2900_endpoint_config *config)
+{
+	struct audio_user *audio_user;
+
+	CG2900_INFO("cg2900_audio_config_endpoint");
+
+	if (audio_info->state != OPENED) {
+		CG2900_ERR("Audio driver not open");
+		return -EIO;
+	}
+
+	if (!config) {
+		CG2900_ERR("NULL supplied as configuration structure");
+		return -EINVAL;
+	}
+
+	audio_user = get_session_user(session);
+	if (!audio_user)
+		return -EINVAL;
+
+	switch (config->endpoint_id) {
+	case ENDPOINT_BT_SCO_INOUT:
+	case ENDPOINT_BT_A2DP_SRC:
+	case ENDPOINT_FM_RX:
+	case ENDPOINT_FM_TX:
+		add_endpoint(config, &(audio_info->endpoints));
+		break;
+
+	case ENDPOINT_PORT_0_I2S:
+	case ENDPOINT_PORT_1_I2S_PCM:
+	case ENDPOINT_SLIMBUS_VOICE:
+	case ENDPOINT_SLIMBUS_AUDIO:
+	case ENDPOINT_BT_A2DP_SNK:
+	case ENDPOINT_ANALOG_OUT:
+	case ENDPOINT_DSP_AUDIO_IN:
+	case ENDPOINT_DSP_AUDIO_OUT:
+	case ENDPOINT_DSP_VOICE_IN:
+	case ENDPOINT_DSP_VOICE_OUT:
+	case ENDPOINT_DSP_TONE_IN:
+	case ENDPOINT_BURST_BUFFER_IN:
+	case ENDPOINT_BURST_BUFFER_OUT:
+	case ENDPOINT_MUSIC_DECODER:
+	case ENDPOINT_HCI_AUDIO_IN:
+	default:
+		CG2900_ERR("Unknown endpoint_id %d", config->endpoint_id);
+		return -EACCES;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(cg2900_audio_config_endpoint);
+
+static bool is_dai_port(enum cg2900_audio_endpoint_id ep)
+{
+	/* These are the only supported ones */
+	return (ep == ENDPOINT_PORT_0_I2S) || (ep == ENDPOINT_PORT_1_I2S_PCM);
+}
+
+/**
+ * cg2900_audio_start_stream() - Connects two endpoints and starts
the audio stream.
+ * @session:		Session identifier this call is related to.
+ * @ep_1:		One of the endpoints, no relation to direction or role.
+ * @ep_2:		The other endpoint, no relation to direction or role.
+ * @stream_handle:	Pointer where to store the stream handle.
+ *			Allocated by caller, must not be NULL.
+ *
+ * Connects two endpoints and starts the audio stream.
+ * Note that the endpoints need to be configured before the stream is started;
+ * DAI endpoints, such as ENDPOINT_PORT_0_I2S, are
+ * configured through @cg2900_audio_set_dai_config() while other
+ * endpoints are configured through @cg2900_audio_config_endpoint().
+ *
+ * Supported @endpoint_id values are:
+ *  * ENDPOINT_PORT_0_I2S
+ *  * ENDPOINT_PORT_1_I2S_PCM
+ *  * ENDPOINT_BT_SCO_INOUT
+ *  * ENDPOINT_FM_RX
+ *  * ENDPOINT_FM_TX
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -EINVAL upon bad input parameter or unsupported configuration.
+ *   -EIO if driver has not been opened.
+ *   Errors from @conn_start_i2s_to_fm_rx, @conn_start_i2s_to_fm_tx, and
+ *   @conn_start_pcm_to_sco.
+ */
+int cg2900_audio_start_stream(unsigned int session,
+			      enum cg2900_audio_endpoint_id ep_1,
+			      enum cg2900_audio_endpoint_id ep_2,
+			      unsigned int *stream_handle)
+{
+	int err;
+	struct audio_user *audio_user;
+
+	CG2900_INFO("cg2900_audio_start_stream");
+
+	if (audio_info->state != OPENED) {
+		CG2900_ERR("Audio driver not open");
+		return -EIO;
+	}
+
+	audio_user = get_session_user(session);
+	if (!audio_user)
+		return -EINVAL;
+
+	/* put digital interface in ep_1 to simplify comparison below */
+	if (!is_dai_port(ep_1)) {
+		/* Swap endpoints */
+		enum cg2900_audio_endpoint_id t = ep_1;
+		ep_1 = ep_2;
+		ep_2 = t;
+	}
+
+	if (ep_1 == ENDPOINT_PORT_1_I2S_PCM && ep_2 == ENDPOINT_BT_SCO_INOUT) {
+		err = conn_start_pcm_to_sco(audio_user, stream_handle);
+	} else if (ep_1 == ENDPOINT_PORT_0_I2S && ep_2 == ENDPOINT_FM_RX) {
+		err = conn_start_i2s_to_fm_rx(audio_user, stream_handle);
+	} else if (ep_1 == ENDPOINT_PORT_0_I2S && ep_2 == ENDPOINT_FM_TX) {
+		err = conn_start_i2s_to_fm_tx(audio_user, stream_handle);
+	} else {
+		CG2900_ERR("Endpoint config not handled: ep1: %d, "
+			   "ep2: %d", ep_1, ep_2);
+		err = -EINVAL;
+	}
+
+	return err;
+}
+EXPORT_SYMBOL(cg2900_audio_start_stream);
+
+/**
+ * cg2900_audio_stop_stream() - Stops a stream and disconnects the endpoints.
+ * @session:		Session identifier this call is related to.
+ * @stream_handle:	Handle to the stream to stop.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -EINVAL upon bad input parameter.
+ *   -EIO if driver has not been opened.
+ */
+int cg2900_audio_stop_stream(unsigned int session, unsigned int stream_handle)
+{
+	struct audio_user *audio_user;
+
+	CG2900_INFO("cg2900_audio_stop_stream");
+
+	if (audio_info->state != OPENED) {
+		CG2900_ERR("Audio driver not open");
+		return -EIO;
+	}
+
+	audio_user = get_session_user(session);
+	if (!audio_user)
+		return -EINVAL;
+
+	return conn_stop_stream(audio_user, stream_handle);
+}
+EXPORT_SYMBOL(cg2900_audio_stop_stream);
+
+/**
+ * audio_dev_open() - Open char device.
+ * @inode:	Device driver information.
+ * @filp:	Pointer to the file struct.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -ENOMEM if allocation failed.
+ *   Errors from @cg2900_audio_open.
+ */
+static int audio_dev_open(struct inode *inode, struct file *filp)
+{
+	int err;
+	struct char_dev_info *char_dev_info;
+
+	CG2900_INFO("CG2900 Audio: audio_dev_open");
+
+	/*
+	 * Allocate the char dev info structure. It will be stored inside
+	 * the file pointer and supplied when file_ops are called.
+	 * It's free'd in audio_dev_release.
+	 */
+	char_dev_info = kzalloc(sizeof(*char_dev_info), GFP_KERNEL);
+	if (!char_dev_info) {
+		CG2900_ERR("Couldn't allocate char_dev_info");
+		return -ENOMEM;
+	}
+	filp->private_data = char_dev_info;
+
+	mutex_init(&char_dev_info->management_mutex);
+	mutex_init(&char_dev_info->rw_mutex);
+
+	mutex_lock(&char_dev_info->management_mutex);
+	err = cg2900_audio_open(&char_dev_info->session);
+	mutex_unlock(&char_dev_info->management_mutex);
+	if (err) {
+		CG2900_ERR("Failed to open CG2900 Audio driver (%d)", err);
+		goto error_handling_free_mem;
+	}
+
+	return 0;
+
+error_handling_free_mem:
+	kfree(char_dev_info);
+	filp->private_data = NULL;
+	return err;
+}
+
+/**
+ * audio_dev_release() - Release char device.
+ * @inode:	Device driver information.
+ * @filp:	Pointer to the file struct.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -EBADF if NULL pointer was supplied in private data.
+ *   Errors from @cg2900_audio_close.
+ */
+static int audio_dev_release(struct inode *inode, struct file *filp)
+{
+	int err = 0;
+	struct char_dev_info *dev = (struct char_dev_info *)filp->private_data;
+
+	CG2900_INFO("CG2900 Audio: audio_dev_release");
+
+	if (!dev) {
+		CG2900_ERR("No dev supplied in private data");
+		return -EBADF;
+	}
+
+	mutex_lock(&dev->management_mutex);
+	err = cg2900_audio_close(&dev->session);
+	if (err)
+		/*
+		 * Just print the error. Still free the char_dev_info since we
+		 * don't know the filp structure is valid after this call
+		 */
+		CG2900_ERR("Error when closing CG2900 audio driver (%d)", err);
+
+	mutex_unlock(&dev->management_mutex);
+
+	kfree(dev);
+	filp->private_data = NULL;
+
+	return err;
+}
+
+/**
+ * audio_dev_read() - Return information to the user from last @write call.
+ * @filp:	Pointer to the file struct.
+ * @buf:	Received buffer.
+ * @count:	Size of buffer.
+ * @f_pos:	Position in buffer.
+ *
+ * The audio_dev_read() function returns information from
+ * the last @write call to same char device.
+ * The data is in the following format:
+ *   * OpCode of command for this data
+ *   * Data content (Length of data is determined by the command OpCode, i.e.
+ *     fixed for each command)
+ *
+ * Returns:
+ *   Bytes successfully read (could be 0).
+ *   -EBADF if NULL pointer was supplied in private data.
+ *   -EFAULT if copy_to_user fails.
+ *   -ENOMEM upon allocation failure.
+ */
+static ssize_t audio_dev_read(struct file *filp, char __user *buf,
size_t count,
+			      loff_t *f_pos)
+{
+	struct char_dev_info *dev = (struct char_dev_info *)filp->private_data;
+	unsigned int bytes_to_copy = 0;
+	int err = 0;
+
+	CG2900_INFO("CG2900 Audio: audio_dev_read");
+
+	if (!dev) {
+		CG2900_ERR("No dev supplied in private data");
+		return -EBADF;
+	}
+	mutex_lock(&dev->rw_mutex);
+
+	if (dev->stored_data_len == 0) {
+		/* No data to read */
+		bytes_to_copy = 0;
+		goto finished;
+	}
+
+	bytes_to_copy = min(count, (unsigned int)(dev->stored_data_len));
+	if (bytes_to_copy < dev->stored_data_len)
+		CG2900_ERR("Not enough buffer to store all data. Throwing away "
+			   "rest of data. Saved len: %d, stored_len: %d",
+			   count, dev->stored_data_len);
+
+	err = copy_to_user(buf, dev->stored_data, bytes_to_copy);
+	/*
+	 * Throw away all data, even though not all was copied.
+	 * This char device is primarily for testing purposes so we can keep
+	 * such a limitation.
+	 */
+	kfree(dev->stored_data);
+	dev->stored_data = NULL;
+	dev->stored_data_len = 0;
+
+	if (err) {
+		CG2900_ERR("copy_to_user error %d", err);
+		err = -EFAULT;
+		goto error_handling;
+	}
+
+	goto finished;
+
+error_handling:
+	mutex_unlock(&dev->rw_mutex);
+	return (ssize_t)err;
+finished:
+	mutex_unlock(&dev->rw_mutex);
+	return bytes_to_copy;
+}
+
+/**
+ * audio_dev_write() - Call CG2900 Audio API function.
+ * @filp:	Pointer to the file struct.
+ * @buf:	Write buffer.
+ * @count:	Size of the buffer write.
+ * @f_pos:	Position of buffer.
+ *
+ * audio_dev_write() function executes supplied data and
+ * interprets it as if it was a function call to the CG2900 Audio API.
+ * The data is according to:
+ *   * OpCode (4 bytes)
+ *   * Data according to OpCode (see API). No padding between parameters
+ *
+ * OpCodes are:
+ *   * OP_CODE_SET_DAI_CONF 0x00000001
+ *   * OP_CODE_GET_DAI_CONF 0x00000002
+ *   * OP_CODE_CONFIGURE_ENDPOINT 0x00000003
+ *   * OP_CODE_START_STREAM 0x00000004
+ *   * OP_CODE_STOP_STREAM 0x00000005
+ *
+ * Returns:
+ *   Bytes successfully written (could be 0). Equals input @count if
successful.
+ *   -EBADF if NULL pointer was supplied in private data.
+ *   -EFAULT if copy_from_user fails.
+ *   Error codes from all CG2900 Audio API functions.
+ */
+static ssize_t audio_dev_write(struct file *filp, const char __user *buf,
+			       size_t count, loff_t *f_pos)
+{
+	u8 *rec_data;
+	struct char_dev_info *dev = (struct char_dev_info *)filp->private_data;
+	int err = 0;
+	int op_code = 0;
+	u8 *curr_data;
+	unsigned int stream_handle;
+	struct cg2900_dai_config dai_config;
+	struct cg2900_endpoint_config ep_config;
+	enum cg2900_audio_endpoint_id ep_1;
+	enum cg2900_audio_endpoint_id ep_2;
+	int bytes_left = count;
+
+	CG2900_INFO("CG2900 Audio: audio_dev_write count %d", count);
+
+	if (!dev) {
+		CG2900_ERR("No dev supplied in private data");
+		return -EBADF;
+	}
+
+	rec_data = kmalloc(count, GFP_KERNEL);
+	if (!rec_data) {
+		CG2900_ERR("kmalloc failed");
+		return -ENOMEM;
+	}
+
+	mutex_lock(&dev->rw_mutex);
+
+	err = copy_from_user(rec_data, buf, count);
+	if (err) {
+		CG2900_ERR("copy_from_user failed (%d)", err);
+		err = -EFAULT;
+		goto finished_mutex_unlock;
+	}
+
+	/* Initialize temporary data pointer used to traverse the packet */
+	curr_data = rec_data;
+
+	op_code = curr_data[0];
+	CG2900_DBG("op_code %d", op_code);
+	/* OpCode is int size to keep data int aligned */
+	curr_data += sizeof(unsigned int);
+	bytes_left -= sizeof(unsigned int);
+
+	switch (op_code) {
+	case OP_CODE_SET_DAI_CONF:
+		CG2900_DBG("OP_CODE_SET_DAI_CONF %d", sizeof(dai_config));
+		if (bytes_left < sizeof(dai_config)) {
+			CG2900_ERR("Not enough data supplied for "
+				   "OP_CODE_SET_DAI_CONF");
+			err = -EINVAL;
+			goto finished_mutex_unlock;
+		}
+		memcpy(&dai_config, curr_data, sizeof(dai_config));
+		CG2900_DBG("dai_config.port %d", dai_config.port);
+		err = cg2900_audio_set_dai_config(dev->session, &dai_config);
+		break;
+
+	case OP_CODE_GET_DAI_CONF:
+		CG2900_DBG("OP_CODE_GET_DAI_CONF %d", sizeof(dai_config));
+		if (bytes_left < sizeof(dai_config)) {
+			CG2900_ERR("Not enough data supplied for "
+				   "OP_CODE_GET_DAI_CONF");
+			err = -EINVAL;
+			goto finished_mutex_unlock;
+		}
+		/*
+		 * Only need to copy the port really, but let's copy
+		 * like this for simplicity. It's only test functionality
+		 * after all.
+		 */
+		memcpy(&dai_config, curr_data, sizeof(dai_config));
+		CG2900_DBG("dai_config.port %d", dai_config.port);
+		err = cg2900_audio_get_dai_config(dev->session, &dai_config);
+		if (!err) {
+			/*
+			 * Command succeeded. Store data so it can be returned
+			 * when calling read.
+			 */
+			if (dev->stored_data) {
+				CG2900_ERR("Data already allocated (%d bytes). "
+					   "Throwing it away.",
+					   dev->stored_data_len);
+				kfree(dev->stored_data);
+			}
+			dev->stored_data_len = sizeof(op_code) +
+					       sizeof(dai_config);
+			dev->stored_data = kmalloc(dev->stored_data_len,
+						   GFP_KERNEL);
+			if (dev->stored_data) {
+				memcpy(dev->stored_data, &op_code,
+				       sizeof(op_code));
+				memcpy(&(dev->stored_data[sizeof(op_code)]),
+				       &dai_config, sizeof(dai_config));
+			}
+		}
+		break;
+
+	case OP_CODE_CONFIGURE_ENDPOINT:
+		CG2900_DBG("OP_CODE_CONFIGURE_ENDPOINT %d", sizeof(ep_config));
+		if (bytes_left < sizeof(ep_config)) {
+			CG2900_ERR("Not enough data supplied for "
+				   "OP_CODE_CONFIGURE_ENDPOINT");
+			err = -EINVAL;
+			goto finished_mutex_unlock;
+		}
+		memcpy(&ep_config, curr_data, sizeof(ep_config));
+		CG2900_DBG("ep_config.endpoint_id %d", ep_config.endpoint_id);
+		err = cg2900_audio_config_endpoint(dev->session, &ep_config);
+		break;
+
+	case OP_CODE_START_STREAM:
+		CG2900_DBG("OP_CODE_START_STREAM %d",
+			   (sizeof(ep_1) + sizeof(ep_2)));
+		if (bytes_left < (sizeof(ep_1) + sizeof(ep_2))) {
+			CG2900_ERR("Not enough data supplied for "
+				   "OP_CODE_START_STREAM");
+			err = -EINVAL;
+			goto finished_mutex_unlock;
+		}
+		memcpy(&ep_1, curr_data, sizeof(ep_1));
+		curr_data += sizeof(ep_1);
+		memcpy(&ep_2, curr_data, sizeof(ep_2));
+		CG2900_DBG("ep_1 %d ep_2 %d", ep_1,
+			   ep_2);
+
+		err = cg2900_audio_start_stream(dev->session,
+			ep_1, ep_2, &stream_handle);
+		if (!err) {
+			/*
+			 * Command succeeded. Store data so it can be returned
+			 * when calling read.
+			 */
+			if (dev->stored_data) {
+				CG2900_ERR("Data already allocated (%d bytes). "
+					   "Throwing it away.",
+					   dev->stored_data_len);
+				kfree(dev->stored_data);
+			}
+			dev->stored_data_len = sizeof(op_code) +
+					       sizeof(stream_handle);
+			dev->stored_data = kmalloc(dev->stored_data_len,
+						   GFP_KERNEL);
+			if (dev->stored_data) {
+				memcpy(dev->stored_data, &op_code,
+				       sizeof(op_code));
+				memcpy(&(dev->stored_data[sizeof(op_code)]),
+				       &stream_handle, sizeof(stream_handle));
+			}
+			CG2900_DBG("stream_handle %d", stream_handle);
+		}
+		break;
+
+	case OP_CODE_STOP_STREAM:
+		if (bytes_left < sizeof(stream_handle)) {
+			CG2900_ERR("Not enough data supplied for "
+				   "OP_CODE_STOP_STREAM");
+			err = -EINVAL;
+			goto finished_mutex_unlock;
+		}
+		CG2900_DBG("OP_CODE_STOP_STREAM %d", sizeof(stream_handle));
+		memcpy(&stream_handle, curr_data, sizeof(stream_handle));
+		CG2900_DBG("stream_handle %d", stream_handle);
+		err = cg2900_audio_stop_stream(dev->session, stream_handle);
+		break;
+
+	default:
+		CG2900_ERR("Received bad op_code %d", op_code);
+		break;
+	};
+
+finished_mutex_unlock:
+	kfree(rec_data);
+	mutex_unlock(&dev->rw_mutex);
+
+	if (err)
+		return err;
+	else
+		return count;
+}
+
+/**
+ * audio_dev_poll() - Handle POLL call to the interface.
+ * @filp:	Pointer to the file struct.
+ * @wait:	Poll table supplied to caller.
+ *
+ * This function is used by the User Space application to see if the device is
+ * still open and if there is any data available for reading.
+ *
+ * Returns:
+ *   Mask of current set POLL values.
+ */
+static unsigned int audio_dev_poll(struct file *filp, poll_table *wait)
+{
+	struct char_dev_info *dev = (struct char_dev_info *)filp->private_data;
+	unsigned int mask = 0;
+
+	if (!dev) {
+		CG2900_ERR("No dev supplied in private data");
+		return POLLERR | POLLRDHUP;
+	}
+
+	if (RESET == audio_info->state)
+		mask |= POLLERR | POLLRDHUP | POLLPRI;
+	else
+		/* Unless RESET we can transmit */
+		mask |= POLLOUT;
+
+	if (dev->stored_data)
+		mask |= POLLIN | POLLRDNORM;
+
+	return mask;
+}
+
+static const struct file_operations char_dev_fops = {
+	.open = audio_dev_open,
+	.release = audio_dev_release,
+	.read = audio_dev_read,
+	.write = audio_dev_write,
+	.poll = audio_dev_poll
+};
+
+/*
+ *	Module related methods
+ */
+
+/**
+ * cg2900_audio_probe() - Initialize CG2900 audio resources.
+ * @pdev:	Platform device.
+ *
+ * Initialize the module and register misc device.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -ENOMEM if allocation fails.
+ *   -EEXIST if device has already been started.
+ *   Error codes from misc_register.
+ */
+static int __devinit cg2900_audio_probe(struct platform_device *pdev)
+{
+	int err;
+
+	CG2900_INFO("cg2900_audio_probe");
+
+	if (audio_info) {
+		CG2900_ERR("ST-Ericsson CG2900 Audio driver already initiated");
+		return -EEXIST;
+	}
+
+	/* Initialize private data. */
+	audio_info = kzalloc(sizeof(*audio_info), GFP_KERNEL);
+	if (!audio_info) {
+		CG2900_ERR("Could not alloc audio_info struct.");
+		return -ENOMEM;
+	}
+
+	/* Initiate the mutexes */
+	mutex_init(&(audio_info->management_mutex));
+	mutex_init(&(audio_info->bt_mutex));
+	mutex_init(&(audio_info->fm_mutex));
+	mutex_init(&(audio_info->endpoints.management_mutex));
+
+	/* Initiate the SKB queues */
+	skb_queue_head_init(&(audio_info->bt_queue));
+	skb_queue_head_init(&(audio_info->fm_queue));
+
+	/* Initiate the endpoint list */
+	INIT_LIST_HEAD(&(audio_info->endpoints.ep_list));
+
+	/* Prepare and register MISC device */
+	audio_info->dev.minor = MISC_DYNAMIC_MINOR;
+	audio_info->dev.name = DEVICE_NAME;
+	audio_info->dev.fops = &char_dev_fops;
+	audio_info->dev.parent = &(pdev->dev);
+
+	err = misc_register(&(audio_info->dev));
+	if (err) {
+		CG2900_ERR("Error %d registering misc dev!", err);
+		goto error_handling;
+	}
+
+	return 0;
+
+error_handling:
+	mutex_destroy(&audio_info->management_mutex);
+	mutex_destroy(&audio_info->bt_mutex);
+	mutex_destroy(&audio_info->fm_mutex);
+	mutex_destroy(&audio_info->endpoints.management_mutex);
+	kfree(audio_info);
+	audio_info = NULL;
+	return err;
+}
+
+/**
+ * cg2900_audio_remove() - Release CG2900 audio resources.
+ * @pdev:	Platform device.
+ *
+ * Remove misc device and free resources.
+ *
+ * Returns:
+ *   0 if success.
+ *   Error codes from misc_deregister.
+ */
+static int __devexit cg2900_audio_remove(struct platform_device *pdev)
+{
+	int err;
+
+	CG2900_INFO("cg2900_audio_remove");
+
+	if (!audio_info)
+		return 0;
+
+	err = misc_deregister(&audio_info->dev);
+	if (err)
+		CG2900_ERR("Error deregistering misc dev (%d)!", err);
+
+	mutex_destroy(&audio_info->management_mutex);
+	mutex_destroy(&audio_info->bt_mutex);
+	mutex_destroy(&audio_info->fm_mutex);
+
+	flush_endpoint_list(&(audio_info->endpoints));
+
+	skb_queue_purge(&(audio_info->bt_queue));
+	skb_queue_purge(&(audio_info->fm_queue));
+
+	mutex_destroy(&audio_info->endpoints.management_mutex);
+
+	kfree(audio_info);
+	audio_info = NULL;
+	return err;
+}
+
+static struct platform_driver cg2900_audio_driver = {
+	.driver = {
+		.name	= "cg2900-audio",
+		.owner	= THIS_MODULE,
+	},
+	.probe	= cg2900_audio_probe,
+	.remove	= __devexit_p(cg2900_audio_remove),
+};
+
+/**
+ * cg2900_audio_init() - Initialize module.
+ *
+ * Registers platform driver.
+ */
+static int __init cg2900_audio_init(void)
+{
+	CG2900_INFO("cg2900_audio_init");
+	return platform_driver_register(&cg2900_audio_driver);
+}
+
+/**
+ * cg2900_audio_exit() - Remove module.
+ *
+ * Unregisters platform driver.
+ */
+static void __exit cg2900_audio_exit(void)
+{
+	CG2900_INFO("cg2900_audio_exit");
+	platform_driver_unregister(&cg2900_audio_driver);
+}
+
+module_init(cg2900_audio_init);
+module_exit(cg2900_audio_exit);
+
+MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson");
+MODULE_AUTHOR("Kjell Andersson ST-Ericsson");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Linux Bluetooth Audio ST-Ericsson controller");
diff --git a/include/linux/mfd/cg2900_audio.h b/include/linux/mfd/cg2900_audio.h
new file mode 100644
index 0000000..44ded01
--- /dev/null
+++ b/include/linux/mfd/cg2900_audio.h
@@ -0,0 +1,460 @@
+/*
+ * include/linux/mfd/cg2900_audio.h
+ *
+ * Copyright (C) ST-Ericsson SA 2010
+ * Authors:
+ * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for
ST-Ericsson.
+ * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson.
+ * License terms:  GNU General Public License (GPL), version 2
+ *
+ * Linux Bluetooth Audio Driver for ST-Ericsson controller.
+ */
+
+#ifndef _CG2900_AUDIO_H_
+#define _CG2900_AUDIO_H_
+
+#include <linux/types.h>
+
+/*
+ * Digital Audio Interface configuration types
+ */
+
+/** CG2900_A2DP_MAX_AVDTP_HDR_LEN - Max length of a AVDTP header.
+ * Max length of a AVDTP header for an A2DP packet.
+ */
+#define CG2900_A2DP_MAX_AVDTP_HDR_LEN	25
+
+/**
+ * enum cg2900_dai_dir - Contains the DAI port directions alternatives.
+ * @DAI_DIR_B_RX_A_TX: Port B as Rx and port A as Tx.
+ * @DAI_DIR_B_TX_A_RX: Port B as Tx and port A as Rx.
+ */
+enum cg2900_dai_dir {
+	DAI_DIR_B_RX_A_TX = 0x00,
+	DAI_DIR_B_TX_A_RX = 0x01
+};
+
+/**
+ * enum cg2900_dai_mode - DAI mode alternatives.
+ * @DAI_MODE_SLAVE: Slave.
+ * @DAI_MODE_MASTER: Master.
+ */
+enum cg2900_dai_mode {
+	DAI_MODE_SLAVE = 0x00,
+	DAI_MODE_MASTER = 0x01
+};
+
+/**
+ * enum cg2900_dai_stream_ratio - Voice stream ratio alternatives.
+ * @STREAM_RATIO_FM16_VOICE16:	FM 16kHz, Voice 16kHz.
+ * @STREAM_RATIO_FM16_VOICE8:	FM 16kHz, Voice 8kHz.
+ * @STREAM_RATIO_FM48_VOICE16:	FM 48kHz, Voice 16Khz.
+ * @STREAM_RATIO_FM48_VOICE8:	FM 48kHz, Voice 8kHz.
+ *
+ * Contains the alternatives for the voice stream ratio between the
Audio stream
+ * sample rate and the Voice stream sample rate.
+ */
+enum cg2900_dai_stream_ratio {
+	STREAM_RATIO_FM16_VOICE16 = 0x01,
+	STREAM_RATIO_FM16_VOICE8 = 0x02,
+	STREAM_RATIO_FM48_VOICE16 = 0x03,
+	STREAM_RATIO_FM48_VOICE8 = 0x06
+};
+
+/**
+ * enum cg2900_dai_fs_duration - Frame sync duration alternatives.
+ * @SYNC_DURATION_8: 8 frames sync duration.
+ * @SYNC_DURATION_16: 16 frames sync duration.
+ * @SYNC_DURATION_24: 24 frames sync duration.
+ * @SYNC_DURATION_32: 32 frames sync duration.
+ * @SYNC_DURATION_48: 48 frames sync duration.
+ * @SYNC_DURATION_50: 50 frames sync duration.
+ * @SYNC_DURATION_64: 64 frames sync duration.
+ * @SYNC_DURATION_75: 75 frames sync duration.
+ * @SYNC_DURATION_96: 96 frames sync duration.
+ * @SYNC_DURATION_125: 125 frames sync duration.
+ * @SYNC_DURATION_128: 128 frames sync duration.
+ * @SYNC_DURATION_150: 150 frames sync duration.
+ * @SYNC_DURATION_192: 192 frames sync duration.
+ * @SYNC_DURATION_250: 250 frames sync duration.
+ * @SYNC_DURATION_256: 256 frames sync duration.
+ * @SYNC_DURATION_300: 300 frames sync duration.
+ * @SYNC_DURATION_384: 384 frames sync duration.
+ * @SYNC_DURATION_500: 500 frames sync duration.
+ * @SYNC_DURATION_512: 512 frames sync duration.
+ * @SYNC_DURATION_600: 600 frames sync duration.
+ * @SYNC_DURATION_768: 768 frames sync duration.
+ *
+ * This parameter sets the PCM frame sync duration. It is calculated as the
+ * ratio between the bit clock and the frame rate. For example, if the bit
+ * clock is 512 kHz and the stream sample rate is 8 kHz, the PCM frame sync
+ * duration is 512 / 8 = 64.
+ */
+enum cg2900_dai_fs_duration {
+	SYNC_DURATION_8   = 0,
+	SYNC_DURATION_16  = 1,
+	SYNC_DURATION_24  = 2,
+	SYNC_DURATION_32  = 3,
+	SYNC_DURATION_48  = 4,
+	SYNC_DURATION_50  = 5,
+	SYNC_DURATION_64  = 6,
+	SYNC_DURATION_75  = 7,
+	SYNC_DURATION_96  = 8,
+	SYNC_DURATION_125 = 9,
+	SYNC_DURATION_128 = 10,
+	SYNC_DURATION_150 = 11,
+	SYNC_DURATION_192 = 12,
+	SYNC_DURATION_250 = 13,
+	SYNC_DURATION_256 = 14,
+	SYNC_DURATION_300 = 15,
+	SYNC_DURATION_384 = 16,
+	SYNC_DURATION_500 = 17,
+	SYNC_DURATION_512 = 18,
+	SYNC_DURATION_600 = 19,
+	SYNC_DURATION_768 = 20
+};
+
+/**
+ * enum cg2900_dai_bit_clk - Bit Clock alternatives.
+ * @BIT_CLK_128:	128 Kbits clock.
+ * @BIT_CLK_256:	256 Kbits clock.
+ * @BIT_CLK_512:	512 Kbits clock.
+ * @BIT_CLK_768:	768 Kbits clock.
+ * @BIT_CLK_1024:	1024 Kbits clock.
+ * @BIT_CLK_1411_76:	1411.76 Kbits clock.
+ * @BIT_CLK_1536:	1536 Kbits clock.
+ * @BIT_CLK_2000:	2000 Kbits clock.
+ * @BIT_CLK_2048:	2048 Kbits clock.
+ * @BIT_CLK_2400:	2400 Kbits clock.
+ * @BIT_CLK_2823_52:	2823.52 Kbits clock.
+ * @BIT_CLK_3072:	3072 Kbits clock.
+ *
+ *  This parameter sets the bit clock speed. This is the clocking of the actual
+ *  data. A usual parameter for eSCO voice is 512 kHz.
+ */
+enum cg2900_dai_bit_clk {
+	BIT_CLK_128 = 0x00,
+	BIT_CLK_256 = 0x01,
+	BIT_CLK_512 = 0x02,
+	BIT_CLK_768 = 0x03,
+	BIT_CLK_1024 = 0x04,
+	BIT_CLK_1411_76 = 0x05,
+	BIT_CLK_1536 = 0x06,
+	BIT_CLK_2000 = 0x07,
+	BIT_CLK_2048 = 0x08,
+	BIT_CLK_2400 = 0x09,
+	BIT_CLK_2823_52 = 0x0A,
+	BIT_CLK_3072 = 0x0B
+};
+
+/**
+ * enum cg2900_dai_sample_rate - Sample rates alternatives.
+ * @SAMPLE_RATE_8:	8 kHz sample rate.
+ * @SAMPLE_RATE_16:	16 kHz sample rate.
+ * @SAMPLE_RATE_44_1:	44.1 kHz sample rate.
+ * @SAMPLE_RATE_48:	48 kHz sample rate.
+ */
+enum cg2900_dai_sample_rate {
+	SAMPLE_RATE_8    = 0,
+	SAMPLE_RATE_16   = 1,
+	SAMPLE_RATE_44_1 = 2,
+	SAMPLE_RATE_48   = 3
+};
+
+/**
+ * enum cg2900_dai_port_protocol - Port protocol alternatives.
+ * @PORT_PROTOCOL_PCM: Protocol PCM.
+ * @PORT_PROTOCOL_I2S: Protocol I2S.
+ */
+enum cg2900_dai_port_protocol {
+	PORT_PROTOCOL_PCM = 0x00,
+	PORT_PROTOCOL_I2S = 0x01
+};
+
+/**
+ * enum cg2900_dai_channel_sel - The channel selection alternatives.
+ * @CHANNEL_SELECTION_RIGHT: Right channel used.
+ * @CHANNEL_SELECTION_LEFT: Left channel used.
+ * @CHANNEL_SELECTION_BOTH: Both channels used.
+ */
+enum cg2900_dai_channel_sel {
+	CHANNEL_SELECTION_RIGHT = 0x00,
+	CHANNEL_SELECTION_LEFT = 0x01,
+	CHANNEL_SELECTION_BOTH = 0x02
+};
+
+/**
+ * struct cg2900_dai_conf_i2s_pcm - Port configuration structure.
+ * @mode:		Operational mode of the port configured.
+ * @i2s_channel_sel:	I2S channels used. Only valid if used in I2S mode.
+ * @slot_0_used:	True if SCO slot 0 is used.
+ * @slot_1_used:	True if SCO slot 1 is used.
+ * @slot_2_used:	True if SCO slot 2 is used.
+ * @slot_3_used:	True if SCO slot 3 is used.
+ * @slot_0_dir:		Direction of slot 0.
+ * @slot_1_dir:		Direction of slot 1.
+ * @slot_2_dir:		Direction of slot 2.
+ * @slot_3_dir:		Direction of slot 3.
+ * @slot_0_start:	Slot 0 start (relative to the PCM frame sync).
+ * @slot_1_start:	Slot 1 start (relative to the PCM frame sync)
+ * @slot_2_start:	Slot 2 start (relative to the PCM frame sync)
+ * @slot_3_start:	Slot 3 start (relative to the PCM frame sync)
+ * @ratio:		Voice stream ratio between the Audio stream sample rate
+ *			and the Voice stream sample rate.
+ * @protocol:		Protocol used on port.
+ * @duration:		Frame sync duration.
+ * @clk:		Bit clock.
+ * @sample_rate:	Sample rate.
+ */
+struct cg2900_dai_conf_i2s_pcm {
+	enum cg2900_dai_mode mode;
+	enum cg2900_dai_channel_sel i2s_channel_sel;
+	bool slot_0_used;
+	bool slot_1_used;
+	bool slot_2_used;
+	bool slot_3_used;
+	enum cg2900_dai_dir slot_0_dir;
+	enum cg2900_dai_dir slot_1_dir;
+	enum cg2900_dai_dir slot_2_dir;
+	enum cg2900_dai_dir slot_3_dir;
+	__u8 slot_0_start;
+	__u8 slot_1_start;
+	__u8 slot_2_start;
+	__u8 slot_3_start;
+	enum cg2900_dai_stream_ratio ratio;
+	enum cg2900_dai_port_protocol protocol;
+	enum cg2900_dai_fs_duration duration;
+	enum cg2900_dai_bit_clk clk;
+	enum cg2900_dai_sample_rate sample_rate;
+};
+
+/**
+ * enum cg2900_dai_half_period - Half period duration alternatives.
+ * @HALF_PER_DUR_8:	8 Bits.
+ * @HALF_PER_DUR_16:	16 Bits.
+ * @HALF_PER_DUR_24:	24 Bits.
+ * @HALF_PER_DUR_25:	25 Bits.
+ * @HALF_PER_DUR_32:	32 Bits.
+ * @HALF_PER_DUR_48:	48 Bits.
+ * @HALF_PER_DUR_64:	64 Bits.
+ * @HALF_PER_DUR_75:	75 Bits.
+ * @HALF_PER_DUR_96:	96 Bits.
+ * @HALF_PER_DUR_128:	128 Bits.
+ * @HALF_PER_DUR_150:	150 Bits.
+ * @HALF_PER_DUR_192:	192 Bits.
+ *
+ * This parameter sets the number of bits contained in each I2S half period,
+ * i.e. each channel slot. A usual value is 16 bits.
+ */
+enum cg2900_dai_half_period {
+	HALF_PER_DUR_8 = 0x00,
+	HALF_PER_DUR_16 = 0x01,
+	HALF_PER_DUR_24 = 0x02,
+	HALF_PER_DUR_25 = 0x03,
+	HALF_PER_DUR_32 = 0x04,
+	HALF_PER_DUR_48 = 0x05,
+	HALF_PER_DUR_64 = 0x06,
+	HALF_PER_DUR_75 = 0x07,
+	HALF_PER_DUR_96 = 0x08,
+	HALF_PER_DUR_128 = 0x09,
+	HALF_PER_DUR_150 = 0x0A,
+	HALF_PER_DUR_192 = 0x0B
+};
+
+/**
+ * enum cg2900_dai_word_width - Word width alternatives.
+ * @WORD_WIDTH_16: 16 bits words.
+ * @WORD_WIDTH_32: 32 bits words.
+ */
+enum cg2900_dai_word_width {
+	WORD_WIDTH_16 = 0x00,
+	WORD_WIDTH_32 = 0x01
+};
+
+/**
+ * struct cg2900_dai_conf_i2s - Port configuration struct for I2S.
+ * @mode:		Operational mode of the port.
+ * @half_period:	Half period duration.
+ * @channel_sel:	Channel selection.
+ * @sample_rate:	Sample rate.
+ * @word_width:		Word width.
+ */
+struct cg2900_dai_conf_i2s {
+	enum cg2900_dai_mode			mode;
+	enum cg2900_dai_half_period		half_period;
+	enum cg2900_dai_channel_sel		channel_sel;
+	enum cg2900_dai_sample_rate		sample_rate;
+	enum cg2900_dai_word_width		word_width;
+};
+
+/**
+ * union cg2900_dai_port_conf - DAI port configuration union.
+ * @i2s: The configuration struct for a port supporting only I2S.
+ * @i2s_pcm: The configuration struct for a port supporting both PCM and I2S.
+ */
+union cg2900_dai_port_conf {
+	struct cg2900_dai_conf_i2s i2s;
+	struct cg2900_dai_conf_i2s_pcm i2s_pcm;
+};
+
+/**
+ * enum cg2900_dai_ext_port_id - DAI external port id alternatives.
+ * @PORT_0_I2S: Port id is 0 and it supports only I2S.
+ * @PORT_1_I2S_PCM: Port id is 1 and it supports both I2S and PCM.
+ */
+enum cg2900_dai_ext_port_id {
+	PORT_0_I2S,
+	PORT_1_I2S_PCM
+};
+
+/**
+ * enum cg2900_audio_endpoint_id - Audio endpoint id alternatives.
+ * @ENDPOINT_PORT_0_I2S:	Internal audio endpoint of the external I2S
+ *				interface.
+ * @ENDPOINT_PORT_1_I2S_PCM:	Internal audio endpoint of the external I2S/PCM
+ *				interface.
+ * @ENDPOINT_SLIMBUS_VOICE:	Internal audio endpoint of the external Slimbus
+ *				voice interface. (Currently not supported)
+ * @ENDPOINT_SLIMBUS_AUDIO:	Internal audio endpoint of the external Slimbus
+ *				audio interface. (Currently not supported)
+ * @ENDPOINT_BT_SCO_INOUT:	Bluetooth SCO bidirectional.
+ * @ENDPOINT_BT_A2DP_SRC:	Bluetooth A2DP source.
+ * @ENDPOINT_BT_A2DP_SNK:	Bluetooth A2DP sink.
+ * @ENDPOINT_FM_RX:		FM receive.
+ * @ENDPOINT_FM_TX:		FM transmit.
+ * @ENDPOINT_ANALOG_OUT:	Analog out.
+ * @ENDPOINT_DSP_AUDIO_IN:	DSP audio in.
+ * @ENDPOINT_DSP_AUDIO_OUT:	DSP audio out.
+ * @ENDPOINT_DSP_VOICE_IN:	DSP voice in.
+ * @ENDPOINT_DSP_VOICE_OUT:	DSP voice out.
+ * @ENDPOINT_DSP_TONE_IN:	DSP tone in.
+ * @ENDPOINT_BURST_BUFFER_IN:	Burst buffer in.
+ * @ENDPOINT_BURST_BUFFER_OUT:	Burst buffer out.
+ * @ENDPOINT_MUSIC_DECODER:	Music decoder.
+ * @ENDPOINT_HCI_AUDIO_IN:	HCI audio in.
+ */
+enum cg2900_audio_endpoint_id {
+	ENDPOINT_PORT_0_I2S,
+	ENDPOINT_PORT_1_I2S_PCM,
+	ENDPOINT_SLIMBUS_VOICE,
+	ENDPOINT_SLIMBUS_AUDIO,
+	ENDPOINT_BT_SCO_INOUT,
+	ENDPOINT_BT_A2DP_SRC,
+	ENDPOINT_BT_A2DP_SNK,
+	ENDPOINT_FM_RX,
+	ENDPOINT_FM_TX,
+	ENDPOINT_ANALOG_OUT,
+	ENDPOINT_DSP_AUDIO_IN,
+	ENDPOINT_DSP_AUDIO_OUT,
+	ENDPOINT_DSP_VOICE_IN,
+	ENDPOINT_DSP_VOICE_OUT,
+	ENDPOINT_DSP_TONE_IN,
+	ENDPOINT_BURST_BUFFER_IN,
+	ENDPOINT_BURST_BUFFER_OUT,
+	ENDPOINT_MUSIC_DECODER,
+	ENDPOINT_HCI_AUDIO_IN
+};
+
+/**
+ * struct cg2900_dai_config - Configuration struct for Digital Audio Interface.
+ * @port: The port id to configure. Acts as a discriminator for @conf parameter
+ *	  which is a union.
+ * @conf: The configuration union that contains the parameters for the port.
+ */
+struct cg2900_dai_config {
+	enum cg2900_dai_ext_port_id	port;
+	union cg2900_dai_port_conf	conf;
+};
+
+/*
+ * Endpoint configuration types
+ */
+
+/**
+ * enum cg2900_endpoint_sample_rate - Audio endpoint configuration
sample rate alternatives.
+ *
+ * This enum defines the same values as @cg2900_dai_sample_rate, but
+ * is kept to preserve the API.
+ *
+ * @ENDPOINT_SAMPLE_RATE_8_KHZ: 8 kHz sample rate.
+ * @ENDPOINT_SAMPLE_RATE_16_KHZ: 16 kHz sample rate.
+ * @ENDPOINT_SAMPLE_RATE_44_1_KHZ: 44.1 kHz sample rate.
+ * @ENDPOINT_SAMPLE_RATE_48_KHZ: 48 kHz sample rate.
+ */
+enum cg2900_endpoint_sample_rate {
+	ENDPOINT_SAMPLE_RATE_8_KHZ	= SAMPLE_RATE_8,
+	ENDPOINT_SAMPLE_RATE_16_KHZ	= SAMPLE_RATE_16,
+	ENDPOINT_SAMPLE_RATE_44_1_KHZ	= SAMPLE_RATE_44_1,
+	ENDPOINT_SAMPLE_RATE_48_KHZ	= SAMPLE_RATE_48
+};
+
+
+/**
+ * struct cg2900_endpoint_config_a2dp_src - A2DP source audio
endpoint configurations.
+ * @sample_rate: Sample rate.
+ * @channel_count: Number of channels.
+ */
+struct cg2900_endpoint_config_a2dp_src {
+	enum cg2900_endpoint_sample_rate	sample_rate;
+	unsigned int				channel_count;
+};
+
+/**
+ * struct cg2900_endpoint_config_fm - Configuration parameters for an
FM endpoint.
+ * @sample_rate: The sample rate alternatives for the FM audio endpoints.
+ */
+struct cg2900_endpoint_config_fm {
+	enum cg2900_endpoint_sample_rate	sample_rate;
+};
+
+
+/**
+ * struct cg2900_endpoint_config_sco_in_out - SCO audio endpoint
configuration structure.
+ * @sample_rate: Sample rate, valid values are
+ *		 * ENDPOINT_SAMPLE_RATE_8_KHZ
+ *		 * ENDPOINT_SAMPLE_RATE_16_KHZ.
+ */
+struct cg2900_endpoint_config_sco_in_out {
+	enum cg2900_endpoint_sample_rate	sample_rate;
+};
+
+/**
+ * union cg2900_endpoint_config - Different audio endpoint configurations.
+ * @sco:	SCO audio endpoint configuration structure.
+ * @a2dp_src:	A2DP source audio endpoint configuration structure.
+ * @fm:		FM audio endpoint configuration structure.
+ */
+union cg2900_endpoint_config_union {
+	struct cg2900_endpoint_config_sco_in_out	sco;
+	struct cg2900_endpoint_config_a2dp_src		a2dp_src;
+	struct cg2900_endpoint_config_fm		fm;
+};
+
+/**
+ * struct cg2900_endpoint_config - Audio endpoint configuration.
+ * @endpoint_id:	Identifies the audio endpoint. Works as a discriminator
+ *			for the config union.
+ * @config:		Union holding the configuration parameters for
+ *			the endpoint.
+ */
+struct cg2900_endpoint_config {
+	enum cg2900_audio_endpoint_id		endpoint_id;
+	union cg2900_endpoint_config_union	config;
+};
+
+int cg2900_audio_open(unsigned int *session);
+int cg2900_audio_close(unsigned int *session);
+int cg2900_audio_set_dai_config(unsigned int session,
+				struct cg2900_dai_config *config);
+int cg2900_audio_get_dai_config(unsigned int session,
+				struct cg2900_dai_config *config);
+int cg2900_audio_config_endpoint(unsigned int session,
+				 struct cg2900_endpoint_config *config);
+int cg2900_audio_start_stream(unsigned int session,
+			      enum cg2900_audio_endpoint_id ep_1,
+			      enum cg2900_audio_endpoint_id ep_2,
+			      unsigned int *stream_handle);
+int cg2900_audio_stop_stream(unsigned int session,
+			     unsigned int stream_handle);
+
+#endif /* _CG2900_AUDIO_H_ */
-- 
1.6.3.3

^ permalink raw reply related

* [PATCH 5/9] mfd: Add UART support for the ST-Ericsson CG2900.
From: Par-Gunnar Hjalmdahl @ 2010-10-22 10:38 UTC (permalink / raw)
  To: linus.walleij, linux-bluetooth, linux-kernel

This patch adds UART support for the ST-Ericsson CG2900 Connectivity
Combo controller.
This patch registers to the TTY framework as a line discipline
driver for the N_HCI ldisc. When opened it registers as a transport
to the CG2900 framework. This patch also handles the low power
operation (suspend/resume) for the CG2900 when using UART transport.

Signed-off-by: Par-Gunnar Hjalmdahl <par-gunnar.p.hjalmdahl@stericsson.com>
---
 drivers/mfd/Kconfig              |    7 +
 drivers/mfd/cg2900/Makefile      |    2 +
 drivers/mfd/cg2900/cg2900_uart.c | 1851 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 1860 insertions(+), 0 deletions(-)
 create mode 100644 drivers/mfd/cg2900/cg2900_uart.c

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index a8e790f..6fcd8b6 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -282,6 +282,13 @@ config MFD_STLC2690_CHIP
 	help
 	  Support for ST-Ericsson STLC2690 Connectivity Controller

+config MFD_CG2900_UART
+	tristate "Support CG2900 UART transport"
+	depends on MFD_CG2900
+	help
+	  Support for UART as transport 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 4935daa..c8dd713 100644
--- a/drivers/mfd/cg2900/Makefile
+++ b/drivers/mfd/cg2900/Makefile
@@ -10,3 +10,5 @@ obj-$(CONFIG_MFD_CG2900)	+= cg2900_char_devices.o
 obj-$(CONFIG_MFD_CG2900_CHIP)	+= cg2900_chip.o
 obj-$(CONFIG_MFD_STLC2690_CHIP)	+= stlc2690_chip.o

+obj-$(CONFIG_MFD_CG2900_UART)	+= cg2900_uart.o
+
diff --git a/drivers/mfd/cg2900/cg2900_uart.c b/drivers/mfd/cg2900/cg2900_uart.c
new file mode 100644
index 0000000..f5e287d
--- /dev/null
+++ b/drivers/mfd/cg2900/cg2900_uart.c
@@ -0,0 +1,1851 @@
+/*
+ * drivers/mfd/cg2900/cg2900_uart.c
+ *
+ * Copyright (C) ST-Ericsson SA 2010
+ * Authors:
+ * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for
ST-Ericsson.
+ * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson.
+ * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson.
+ * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson.
+ * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson.
+ * License terms:  GNU General Public License (GPL), version 2
+ *
+ * Linux Bluetooth UART Driver for ST-Ericsson CG2900 connectivity controller.
+ */
+
+#include <asm/byteorder.h>
+#include <linux/device.h>
+#include <linux/gpio.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/poll.h>
+#include <linux/sched.h>
+#include <linux/skbuff.h>
+#include <linux/timer.h>
+#include <linux/regulator/consumer.h>
+#include <linux/tty.h>
+#include <linux/tty_ldisc.h>
+#include <linux/types.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"
+
+/* Workqueues' names */
+#define UART_WQ_NAME		"cg2900_uart_wq"
+#define UART_NAME		"cg2900_uart"
+
+/* Standardized Bluetooth command channels */
+#define HCI_BT_CMD_H4_CHANNEL	0x01
+#define HCI_BT_ACL_H4_CHANNEL	0x02
+#define HCI_BT_EVT_H4_CHANNEL	0x04
+
+/* H4 channels specific for CG2900 */
+#define HCI_FM_RADIO_H4_CHANNEL	0x08
+#define HCI_GNSS_H4_CHANNEL	0x09
+
+/* Timers used in milliseconds */
+#define UART_TX_TIMEOUT		100
+#define UART_RESP_TIMEOUT	1000
+
+/* State-setting defines */
+#define SET_BAUD_STATE(__new_state) \
+	CG2900_SET_STATE("baud_rate_state", uart_info->baud_rate_state, \
+			 __new_state)
+#define SET_SLEEP_STATE(__new_state) \
+	CG2900_SET_STATE("sleep_state", uart_info->sleep_state, __new_state)
+
+/* Number of bytes to reserve at start of sk_buffer when receiving packet */
+#define RX_SKB_RESERVE		8
+/* Max size of received packet (not including reserved bytes) */
+#define RX_SKB_MAX_SIZE		1024
+
+/* Max size of bytes we can receive on the UART */
+#define UART_RECEIVE_ROOM	65536
+
+/* Size of the header in the different packets */
+#define HCI_BT_EVT_HDR_SIZE	2
+#define HCI_BT_ACL_HDR_SIZE	4
+#define HCI_FM_RADIO_HDR_SIZE	1
+#define HCI_GNSS_HDR_SIZE	3
+
+/* Position of length field in the different packets */
+#define HCI_EVT_LEN_POS		2
+#define HCI_ACL_LEN_POS		3
+#define FM_RADIO_LEN_POS	1
+#define GNSS_LEN_POS		2
+
+/* Bytes in the command Hci_Cmd_ST_Set_Uart_Baud_Rate */
+#define SET_BAUD_RATE_LSB	0x09
+#define SET_BAUD_RATE_MSB	0xFC
+#define SET_BAUD_RATE_PAYL_LEN	0x01
+#define SET_BAUD_RATE_LEN	0x04
+#define BAUD_RATE_57600		0x03
+#define BAUD_RATE_115200	0x02
+#define BAUD_RATE_230400	0x01
+#define BAUD_RATE_460800	0x00
+#define BAUD_RATE_921600	0x20
+#define BAUD_RATE_2000000	0x25
+#define BAUD_RATE_3000000	0x27
+#define BAUD_RATE_4000000	0x2B
+
+/* Baud rate defines */
+#define ZERO_BAUD_RATE		0
+#define DEFAULT_BAUD_RATE	115200
+#define HIGH_BAUD_RATE		3000000
+
+/* HCI TTY line discipline value */
+#ifndef N_HCI
+#define N_HCI			15
+#endif
+
+/* IOCTLs for UART */
+#define HCIUARTSETPROTO		_IOW('U', 200, int)
+#define HCIUARTGETPROTO		_IOR('U', 201, int)
+#define HCIUARTGETDEVICE	_IOR('U', 202, int)
+#define HCIUARTSETFD		_IOW('U', 203, int)
+
+
+/* UART break control parameters */
+#define TTY_BREAK_ON		(-1)
+#define TTY_BREAK_OFF		(0)
+
+/**
+ * enum uart_rx_state - UART RX-state for UART.
+ * @W4_PACKET_TYPE:	Waiting for packet type.
+ * @W4_EVENT_HDR:	Waiting for BT event header.
+ * @W4_ACL_HDR:		Waiting for BT ACL header.
+ * @W4_FM_RADIO_HDR:	Waiting for FM header.
+ * @W4_GNSS_HDR:	Waiting for GNSS header.
+ * @W4_DATA:		Waiting for data in rest of the packet (after header).
+ */
+enum uart_rx_state {
+	W4_PACKET_TYPE,
+	W4_EVENT_HDR,
+	W4_ACL_HDR,
+	W4_FM_RADIO_HDR,
+	W4_GNSS_HDR,
+	W4_DATA
+};
+
+/**
+  * enum sleep_state - Sleep-state for UART.
+  * @CHIP_AWAKE:  Chip is awake.
+  * @CHIP_FALLING_ASLEEP:  Chip is falling asleep.
+  * @CHIP_ASLEEP: Chip is asleep.
+  * @CHIP_SUSPENDED: Chip in suspend state.
+  * @CHIP_POWERED_DOWN: Chip is off.
+  */
+enum sleep_state {
+	CHIP_AWAKE,
+	CHIP_FALLING_ASLEEP,
+	CHIP_ASLEEP,
+	CHIP_SUSPENDED,
+	CHIP_POWERED_DOWN
+};
+
+/**
+ * enum baud_rate_change_state - Baud rate-state for UART.
+ * @BAUD_IDLE:		No baud rate change is ongoing.
+ * @BAUD_SENDING_RESET:	HCI reset has been sent. Waiting for command complete
+ *			event.
+ * @BAUD_START:		Set baud rate cmd scheduled for sending.
+ * @BAUD_SENDING:	Set baud rate cmd sending in progress.
+ * @BAUD_WAITING:	Set baud rate cmd sent, waiting for command complete
+ *			event.
+ * @BAUD_SUCCESS:	Baud rate change has succeeded.
+ * @BAUD_FAIL:		Baud rate change has failed.
+ */
+enum baud_rate_change_state {
+	BAUD_IDLE,
+	BAUD_SENDING_RESET,
+	BAUD_START,
+	BAUD_SENDING,
+	BAUD_WAITING,
+	BAUD_SUCCESS,
+	BAUD_FAIL
+};
+
+/**
+ * struct uart_work_struct - Work structure for UART module.
+ * @work:	Work structure.
+ * @data:	Pointer to private data.
+ *
+ * This structure is used to pack work for work queue.
+ */
+struct uart_work_struct{
+	struct work_struct	work;
+	void			*data;
+};
+
+/**
+ * struct test_char_dev_info - Main UART info structure.
+ * @wq:			UART work queue.
+ * @tx_queue:		TX queue for sending data to chip.
+ * @tty:		TTY info structure.
+ * @rx_lock:		RX spin lock.
+ * @rx_state:		Current RX state.
+ * @rx_count:		Number of bytes left to receive.
+ * @rx_skb:		SK_buffer to store the received data into.
+ * @tx_mutex:		TX mutex.
+ * @baud_rate_state:	UART baud rate change state.
+ * @baud_rate:		Current baud rate setting.
+ * @sleep_state:	UART sleep state.
+ * @timer:		UART timer (for chip sleep).
+ * @fd:			File object to device.
+ * @sleep_state_lock:	Used to protect chip state.
+ * @sleep_allowed:	Indicate if tty has functions needed for sleep mode.
+ * @regulator:		Regulator.
+ * @regulator_enabled:	True if regulator is enabled.
+ * @dev:		Pointer to CG2900 uart device.
+ */
+struct uart_info {
+	struct workqueue_struct		*wq;
+	struct sk_buff_head		tx_queue;
+	struct tty_struct		*tty;
+	spinlock_t			rx_lock;
+	enum uart_rx_state		rx_state;
+	unsigned long			rx_count;
+	struct sk_buff			*rx_skb;
+	struct mutex			tx_mutex;
+	enum baud_rate_change_state	baud_rate_state;
+	int				baud_rate;
+	enum sleep_state		sleep_state;
+	struct timer_list		timer;
+	struct file			*fd;
+	struct mutex			sleep_state_lock;
+	bool				sleep_allowed;
+	struct regulator		*regulator;
+	bool				regulator_enabled;
+	struct device			*dev;
+};
+
+static struct uart_info *uart_info;
+
+/* Module parameters */
+static int uart_default_baud = DEFAULT_BAUD_RATE;
+static int uart_high_baud = HIGH_BAUD_RATE;
+
+static DECLARE_WAIT_QUEUE_HEAD(uart_wait_queue);
+
+static void update_timer(void);
+
+/**
+ * is_chip_flow_off() - Check if chip has set flow off.
+ * @tty:	Pointer to tty.
+ *
+ * Returns:
+ *   true - chip flows off.
+ *   false - chip flows on.
+ */
+static bool is_chip_flow_off(struct tty_struct *tty)
+{
+	int lines;
+
+	lines = tty->ops->tiocmget(tty, uart_info->fd);
+
+	if (lines & TIOCM_CTS)
+		return false;
+	else
+		return true;
+}
+
+/**
+ * create_work_item() - Create work item and add it to the work queue.
+ * @wq:		work queue struct where the work will be added.
+ * @work_func:	Work function.
+ * @data:	Private data for the work.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -EBUSY if not possible to queue work.
+ *   -ENOMEM if allocation fails.
+ */
+static int create_work_item(struct workqueue_struct *wq, work_func_t work_func,
+			    void *data)
+{
+	struct uart_work_struct *new_work;
+	int err;
+
+	new_work = kmalloc(sizeof(*new_work), GFP_ATOMIC);
+	if (!new_work) {
+		CG2900_ERR("Failed to alloc memory for uart_work_struct!");
+		return -ENOMEM;
+	}
+
+	new_work->data = data;
+	INIT_WORK(&new_work->work, work_func);
+
+	err = queue_work(wq, &new_work->work);
+	if (!err) {
+		CG2900_ERR("Failed to queue work_struct because it's already "
+			   "in the queue!");
+		kfree(new_work);
+		return -EBUSY;
+	}
+
+	return 0;
+}
+
+/**
+ * set_tty_baud() - Called to set specific baud in TTY.
+ * @tty:	Tty device.
+ * @baud:	Baud to set.
+ *
+ * Returns:
+ *   true - baudrate set with success.
+ *   false - baundrate set failure.
+ */
+static bool set_tty_baud(struct tty_struct *tty, int baud)
+{
+	struct ktermios *old_termios;
+	bool retval = true;
+
+	old_termios = kmalloc(sizeof(*old_termios), GFP_ATOMIC);
+	if (!old_termios) {
+		CG2900_ERR("Could not allocate termios");
+		return false;
+	}
+
+	mutex_lock(&(tty->termios_mutex));
+	/* Start by storing the old termios. */
+	memcpy(old_termios, tty->termios, sizeof(*old_termios));
+
+	/* Let's mark that CG2900 driver uses c_ispeed and c_ospeed fields. */
+	tty->termios->c_cflag |= BOTHER;
+
+	tty_encode_baud_rate(tty, baud, baud);
+
+	/* Finally inform the driver */
+	if (tty->ops->set_termios)
+		tty->ops->set_termios(tty, old_termios);
+	else {
+		CG2900_ERR("Can not set new baudrate.");
+		/* Copy back the old termios to restore old setting. */
+		memcpy(tty->termios, old_termios, sizeof(*old_termios));
+		retval = false;
+	}
+
+	tty->termios->c_cflag &= ~BOTHER;
+
+	mutex_unlock(&(tty->termios_mutex));
+	kfree(old_termios);
+
+	return retval;
+}
+
+/**
+ * handle_cts_irq() - Called to handle CTS interrupt in work context.
+ * @work:	work which needs to be done.
+ *
+ * The handle_cts_irq() function is a work handler called if interrupt on CTS
+ * occurred. It updates the sleep timer which will wake up the transport.
+ */
+static void handle_cts_irq(struct work_struct *work)
+{
+	/* Restart timer and disable interrupt. */
+	update_timer();
+}
+
+/**
+ * cts_interrupt() - Called to handle CTS interrupt.
+ * @irq:	Interrupt that occurred.
+ * @dev_id:	Device ID where interrupt occurred (not used).
+ *
+ * The handle_cts_irq() function is called if interrupt on CTS occurred.
+ * It disables the interrupt and starts a new work thread to handle
+ * the interrupt.
+ */
+static irqreturn_t cts_interrupt(int irq, void *dev_id)
+{
+#ifdef CONFIG_PM
+	disable_irq_wake(irq);
+#endif
+	disable_irq_nosync(irq);
+
+	/* If chip is suspended, resume callback will be called. */
+	if (CHIP_SUSPENDED != uart_info->sleep_state)
+		/* Create work and leave IRQ context. */
+		(void)create_work_item(uart_info->wq, handle_cts_irq, NULL);
+
+	return IRQ_HANDLED;
+}
+
+/**
+ * set_cts_irq() - Enable interrupt on CTS.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   Error codes from request_irq and disable_uart.
+ */
+static int set_cts_irq(void)
+{
+	int err;
+	struct cg2900_platform_data *pf_data;
+
+	pf_data = dev_get_platdata(uart_info->dev->parent);
+
+	/* First disable the UART so we can use IRQ on the GPIOs */
+	if (pf_data->uart.disable_uart) {
+		err = pf_data->uart.disable_uart();
+		if (err) {
+			CG2900_ERR("Could not disable UART (%d)", err);
+			goto error;
+		}
+	}
+
+	/* Set IRQ on CTS. */
+	err = request_irq(pf_data->uart.cts_irq,
+			  cts_interrupt,
+			  IRQF_TRIGGER_FALLING,
+			  UART_NAME,
+			  NULL);
+	if (err) {
+		CG2900_ERR("Could not request CTS IRQ (%d)", err);
+		goto error;
+	}
+
+#ifdef CONFIG_PM
+	enable_irq_wake(pf_data->uart.cts_irq);
+#endif
+	return 0;
+
+error:
+	if (pf_data->uart.enable_uart)
+		(void)pf_data->uart.enable_uart();
+	return err;
+}
+
+/**
+ * unset_cts_irq() - Disable interrupt on CTS.
+ */
+static void unset_cts_irq(void)
+{
+	int err = 0;
+	struct cg2900_platform_data *pf_data;
+
+	pf_data = dev_get_platdata(uart_info->dev->parent);
+
+	/* Free CTS interrupt and restore UART settings. */
+	free_irq(pf_data->uart.cts_irq, NULL);
+
+	if (pf_data->uart.enable_uart) {
+		err = pf_data->uart.enable_uart();
+		if (err)
+			CG2900_ERR("Unable to enable UART Hardware (%d)", err);
+	}
+}
+
+/**
+ * update_timer() - Updates or starts the sleep timer.
+ *
+ * Updates or starts the sleep timer used to detect when there are no current
+ * data transmissions.
+ */
+static void update_timer(void)
+{
+	unsigned long timeout_jiffies = cg2900_get_sleep_timeout();
+	struct tty_struct *tty;
+
+	if ((!timeout_jiffies || !uart_info->fd || !uart_info->sleep_allowed)
+		&& (uart_info->sleep_state != CHIP_SUSPENDED))
+		return;
+
+	mutex_lock(&(uart_info->sleep_state_lock));
+	/*
+	 * This function indicates data is transmitted.
+	 * Therefore see to that the chip is awake.
+	 */
+	if (CHIP_AWAKE == uart_info->sleep_state)
+		goto finished;
+
+	tty = uart_info->tty;
+
+	if (CHIP_ASLEEP == uart_info->sleep_state ||
+		CHIP_SUSPENDED == uart_info->sleep_state) {
+		/* Disable IRQ only when it was enabled. */
+		unset_cts_irq();
+		(void)set_tty_baud(tty, uart_info->baud_rate);
+	}
+	/* Set FLOW on. */
+	tty_unthrottle(tty);
+
+	/* Unset BREAK. */
+	CG2900_DBG("Clear break");
+	tty->ops->break_ctl(tty, TTY_BREAK_OFF);
+
+	SET_SLEEP_STATE(CHIP_AWAKE);
+
+finished:
+	mutex_unlock(&(uart_info->sleep_state_lock));
+	/*
+	 * If timer is running restart it. If not, start it.
+	 * All this is handled by mod_timer().
+	 */
+	mod_timer(&(uart_info->timer), jiffies + timeout_jiffies);
+}
+
+/**
+ * sleep_timer_expired() - Called when sleep timer expires.
+ * @data:	Value supplied when starting the timer.
+ *
+ * The sleep_timer_expired() function is called if there are no ongoing data
+ * transmissions. It tries to put the chip in sleep mode.
+ *
+ */
+static void sleep_timer_expired(unsigned long data)
+{
+	unsigned long timeout_jiffies = cg2900_get_sleep_timeout();
+	struct tty_struct *tty;
+
+	if (!timeout_jiffies || !uart_info->sleep_allowed || !uart_info->fd)
+		return;
+
+	mutex_lock(&(uart_info->sleep_state_lock));
+
+	tty = uart_info->tty;
+
+	switch (uart_info->sleep_state) {
+	case CHIP_FALLING_ASLEEP:
+		if (!is_chip_flow_off(tty))
+			goto run_timer;
+
+		/* Flow OFF. */
+		tty_throttle(tty);
+
+		/*
+		 * Set baud zero.
+		 * This cause shut off UART clock as well.
+		 */
+		(void)set_tty_baud(tty, ZERO_BAUD_RATE);
+
+		if (set_cts_irq() < 0) {
+			CG2900_ERR("Can not set interrupt on CTS.");
+			(void)set_tty_baud(tty, uart_info->baud_rate);
+			tty_unthrottle(tty);
+			SET_SLEEP_STATE(CHIP_AWAKE);
+			goto error;
+		}
+
+		SET_SLEEP_STATE(CHIP_ASLEEP);
+		break;
+	case CHIP_AWAKE:
+
+		CG2900_DBG("Set break");
+		tty->ops->break_ctl(tty, TTY_BREAK_ON);
+
+		SET_SLEEP_STATE(CHIP_FALLING_ASLEEP);
+		goto run_timer;
+
+	case CHIP_POWERED_DOWN:
+	case CHIP_SUSPENDED:
+	case CHIP_ASLEEP: /* Fallthrough. */
+	default:
+		CG2900_DBG("Chip sleeps, is suspended or powered down.");
+		break;
+	}
+
+	mutex_unlock(&(uart_info->sleep_state_lock));
+
+	return;
+
+run_timer:
+	mutex_unlock(&(uart_info->sleep_state_lock));
+	mod_timer(&(uart_info->timer), jiffies + timeout_jiffies);
+	return;
+error:
+	/* Disable sleep mode.*/
+	CG2900_ERR("Disable sleep mode.");
+	uart_info->sleep_allowed = false;
+	uart_info->fd = NULL;
+	mutex_unlock(&(uart_info->sleep_state_lock));
+}
+
+#ifdef CONFIG_PM
+/**
+ * cg2900_uart_suspend() - Called by Linux PM to put the device in a
low power mode.
+ * @pdev:	Pointer to platform device.
+ * @state:	New state.
+ *
+ * In UART case, CG2900 driver does nothing on suspend.
+ *
+ * Returns:
+ *   0 - Success.
+ */
+static int cg2900_uart_suspend(struct platform_device *pdev,
pm_message_t state)
+{
+	if (uart_info->sleep_state == CHIP_POWERED_DOWN)
+		return 0;
+
+	/* Timer is mostlikely running. Delete it. */
+	del_timer(&uart_info->timer);
+
+	if (CHIP_ASLEEP == uart_info->sleep_state)
+		goto finished;
+
+	if (CHIP_AWAKE == uart_info->sleep_state) {
+		uart_info->tty->ops->break_ctl(uart_info->tty, TTY_BREAK_ON);
+		SET_SLEEP_STATE(CHIP_FALLING_ASLEEP);
+		msleep(10);
+	}
+
+	if (CHIP_FALLING_ASLEEP == uart_info->sleep_state) {
+		int err;
+
+		/* Flow OFF. */
+		tty_throttle(uart_info->tty);
+		(void)set_tty_baud(uart_info->tty, ZERO_BAUD_RATE);
+
+		err = set_cts_irq();
+		if (err < 0) {
+			CG2900_ERR("Can not suspend");
+			SET_SLEEP_STATE(CHIP_AWAKE);
+			return err;
+		}
+	}
+
+finished:
+	SET_SLEEP_STATE(CHIP_SUSPENDED);
+	return 0;
+}
+
+/**
+ * cg2900_uart_resume() - Called to bring a device back from a low power state.
+ * @pdev:	Pointer to platform device.
+ *
+ * In UART case, CG2900 driver does nothing on resume.
+ *
+ * Returns:
+ *   0 - Success.
+ */
+static int cg2900_uart_resume(struct platform_device *pdev)
+{
+	if (uart_info->sleep_state != CHIP_POWERED_DOWN)
+		update_timer();
+	return 0;
+}
+#endif /* CONFIG_PM */
+
+/**
+ * cg2900_enable_regulator() - Enable regulator.
+ *
+ * Returns:
+ *   0 - Success.
+ *   Error from regulator_get, regulator_enable.
+ */
+static int cg2900_enable_regulator(void)
+{
+#ifdef CONFIG_REGULATOR
+	int err;
+
+	/* Get and enable regulator. */
+	uart_info->regulator = regulator_get(uart_info->dev->parent, "gbf_1v8");
+	if (IS_ERR(uart_info->regulator)) {
+		CG2900_ERR("Not able to find regulator.");
+		err = PTR_ERR(uart_info->regulator);
+	} else {
+		err = regulator_enable(uart_info->regulator);
+		if (err)
+			CG2900_ERR("Not able to enable regulator.");
+		else
+			uart_info->regulator_enabled = true;
+	}
+	return err;
+#else
+	return 0;
+#endif
+}
+
+/**
+ * cg2900_disable_regulator() - Disable regulator.
+ *
+ */
+static void cg2900_disable_regulator(void)
+{
+#ifdef CONFIG_REGULATOR
+	/* Disable and put regulator. */
+	if (uart_info->regulator && uart_info->regulator_enabled) {
+		regulator_disable(uart_info->regulator);
+		uart_info->regulator_enabled = false;
+	}
+	regulator_put(uart_info->regulator);
+	uart_info->regulator = NULL;
+#endif
+}
+
+/**
+ * is_set_baud_rate_cmd() - Checks if data contains set baud rate hci cmd.
+ * @data:	Pointer to data array to check.
+ *
+ * Returns:
+ *   true - if cmd found;
+ *   false - otherwise.
+ */
+static bool is_set_baud_rate_cmd(const char *data)
+{
+	bool cmd_match = false;
+
+	if ((data[0] == HCI_BT_CMD_H4_CHANNEL) &&
+	    (data[1] == SET_BAUD_RATE_LSB) &&
+	    (data[2] == SET_BAUD_RATE_MSB) &&
+	    (data[3] == SET_BAUD_RATE_PAYL_LEN)) {
+		cmd_match = true;
+	}
+	return cmd_match;
+}
+
+/**
+ * is_bt_cmd_complete_no_param() - Checks if data contains command
complete event for a certain command.
+ * @skb:	sk_buffer containing the data including H:4 header.
+ * @cmd_lsb:	Command LSB.
+ * @cmd_msb:	Command MSB.
+ *
+ * Returns:
+ *   true - If this is the command complete we were looking for;
+ *   false - otherwise.
+ */
+static bool is_bt_cmd_complete_no_param(struct sk_buff *skb, u8 cmd_lsb,
+					u8 cmd_msb)
+{
+	if ((HCI_BT_EVT_H4_CHANNEL == skb->data[0]) &&
+	    (HCI_BT_EVT_CMD_COMPLETE == skb->data[1]) &&
+	    (HCI_BT_CMD_COMPLETE_NO_PARAM_LEN == skb->data[2]) &&
+	    (cmd_lsb == skb->data[4]) &&
+	    (cmd_msb == skb->data[5]))
+		return true;
+
+	return false;
+}
+
+/**
+ * alloc_rx_skb() - Alloc an sk_buff structure for receiving data
from controller.
+ * @size:	Size in number of octets.
+ * @priority:	Allocation priority, e.g. GFP_KERNEL.
+ *
+ * Returns:
+ *   Pointer to sk_buff structure.
+ */
+static struct sk_buff *alloc_rx_skb(unsigned int size, gfp_t priority)
+{
+	struct sk_buff *skb;
+
+	/* Allocate the SKB and reserve space for the header */
+	skb = alloc_skb(size + RX_SKB_RESERVE, priority);
+	if (skb)
+		skb_reserve(skb, RX_SKB_RESERVE);
+
+	return skb;
+}
+
+/**
+ * finish_setting_baud_rate() - Handles sending the ste baud rate hci cmd.
+ * @tty:	Pointer to a tty_struct used to communicate with tty driver.
+ *
+ * finish_setting_baud_rate() makes sure that the set baud rate cmd has
+ * been really sent out on the wire and then switches the tty driver to new
+ * baud rate.
+ */
+static void finish_setting_baud_rate(struct tty_struct *tty)
+{
+	/*
+	 * Give the tty driver time to send data and proceed. If it hasn't
+	 * been sent we can't do much about it anyway.
+	 */
+	schedule_timeout_interruptible(msecs_to_jiffies(UART_TX_TIMEOUT));
+
+	/*
+	 * Now set the termios struct to the new baudrate. Start by storing
+	 * the old termios.
+	 */
+	if (set_tty_baud(tty, uart_info->baud_rate)) {
+		CG2900_DBG("Setting termios to new baud rate");
+		SET_BAUD_STATE(BAUD_WAITING);
+	} else
+		SET_BAUD_STATE(BAUD_IDLE);
+
+	tty_unthrottle(tty);
+}
+
+/**
+ * alloc_set_baud_rate_cmd() - Allocates new sk_buff and fills in the
change baud rate hci cmd.
+ * @baud:	(in/out) Requested new baud rate. Updated to default baud rate
+ *		upon invalid value.
+ *
+ * Returns:
+ *   Pointer to allocated sk_buff if successful;
+ *   NULL otherwise.
+ */
+static struct sk_buff *alloc_set_baud_rate_cmd(int *baud)
+{
+	struct sk_buff *skb;
+	u8 data[SET_BAUD_RATE_LEN];
+	u8 *h4;
+
+	skb = cg2900_alloc_skb(SET_BAUD_RATE_LEN, GFP_ATOMIC);
+	if (!skb) {
+		CG2900_ERR("Failed to alloc skb!");
+		return NULL;
+	}
+
+	/* Create the Hci_Cmd_ST_Set_Uart_Baud_Rate packet */
+	data[0] = SET_BAUD_RATE_LSB;
+	data[1] = SET_BAUD_RATE_MSB;
+	data[2] = SET_BAUD_RATE_PAYL_LEN;
+
+	switch (*baud) {
+	case 57600:
+		data[3] = BAUD_RATE_57600;
+		break;
+	case 115200:
+		data[3] = BAUD_RATE_115200;
+		break;
+	case 230400:
+		data[3] = BAUD_RATE_230400;
+		break;
+	case 460800:
+		data[3] = BAUD_RATE_460800;
+		break;
+	case 921600:
+		data[3] = BAUD_RATE_921600;
+		break;
+	case 2000000:
+		data[3] = BAUD_RATE_2000000;
+		break;
+	case 3000000:
+		data[3] = BAUD_RATE_3000000;
+		break;
+	case 4000000:
+		data[3] = BAUD_RATE_4000000;
+		break;
+	default:
+		CG2900_ERR("Invalid speed requested (%d), using 115200 bps "
+			   "instead\n", *baud);
+		data[3] = BAUD_RATE_115200;
+		*baud = 115200;
+		break;
+	};
+
+	memcpy(skb_put(skb, SET_BAUD_RATE_LEN), data, SET_BAUD_RATE_LEN);
+	h4 = skb_push(skb, HCI_H4_SIZE);
+	*h4 = HCI_BT_CMD_H4_CHANNEL;
+
+	return skb;
+}
+
+/**
+ * work_do_transmit() - Transmit data packet to connectivity
controller over UART.
+ * @work:	Pointer to work info structure. Contains uart_info structure
+ *		pointer.
+ */
+static void work_do_transmit(struct work_struct *work)
+{
+	struct sk_buff *skb;
+	struct tty_struct *tty;
+	struct uart_work_struct *current_work;
+
+	if (!work) {
+		CG2900_ERR("work == NULL");
+		return;
+	}
+
+	/* Restart timer. */
+	update_timer();
+
+	current_work = container_of(work, struct uart_work_struct, work);
+
+	if (uart_info->tty)
+		tty = uart_info->tty;
+	else {
+		CG2900_ERR("Important structs not allocated!");
+		goto finished;
+	}
+
+	mutex_lock(&uart_info->tx_mutex);
+
+	/* Retrieve the first packet in the queue */
+	skb = skb_dequeue(&uart_info->tx_queue);
+	while (skb) {
+		int len;
+
+		/*
+		 * Tell TTY that there is data waiting and call the write
+		 * function.
+		 */
+		set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
+		len = tty->ops->write(tty, skb->data, skb->len);
+		CG2900_INFO("Written %d bytes to UART of %d bytes in packet",
+			    len, skb->len);
+
+		/*
+		 * If it's set baud rate cmd set correct baud state and after
+		 * sending is finished inform the tty driver about the new
+		 * baud rate.
+		 */
+		if ((BAUD_START == uart_info->baud_rate_state) &&
+		    (is_set_baud_rate_cmd(skb->data))) {
+			CG2900_INFO("UART set baud rate cmd found.");
+			SET_BAUD_STATE(BAUD_SENDING);
+		}
+
+		/* Remove the bytes written from the sk_buffer */
+		skb_pull(skb, len);
+
+		/*
+		 * If there is more data in this sk_buffer, put it at the start
+		 * of the list and exit the loop
+		 */
+		if (skb->len) {
+			skb_queue_head(&uart_info->tx_queue, skb);
+			break;
+		}
+		/*
+		 * No more data in the sk_buffer. Free it and get next packet in
+		 * queue.
+		 * Check if set baud rate cmd is in sending progress, if so call
+		 * proper function to handle that cmd since it requires special
+		 * attention.
+		 */
+		if (BAUD_SENDING == uart_info->baud_rate_state)
+			finish_setting_baud_rate(tty);
+
+		kfree_skb(skb);
+		skb = skb_dequeue(&uart_info->tx_queue);
+	}
+
+	mutex_unlock(&uart_info->tx_mutex);
+
+finished:
+	kfree(current_work);
+}
+
+/**
+ * work_hw_deregistered() - Handle HW deregistered.
+ * @work: Reference to work data.
+ */
+static void work_hw_deregistered(struct work_struct *work)
+{
+	struct uart_work_struct *current_work;
+	int err;
+
+	if (!work) {
+		CG2900_ERR("work == NULL");
+		return;
+	}
+
+	current_work = container_of(work, struct uart_work_struct, work);
+
+	/* Purge any stored sk_buffers */
+	skb_queue_purge(&uart_info->tx_queue);
+	if (uart_info->rx_skb) {
+		kfree_skb(uart_info->rx_skb);
+		uart_info->rx_skb = NULL;
+	}
+
+	err = cg2900_deregister_trans_driver();
+	if (err)
+		CG2900_ERR("Could not deregister UART from Core (%d)", err);
+
+	kfree(current_work);
+}
+
+/**
+ * set_baud_rate() - Sets new baud rate for the UART.
+ * @baud:	New baud rate.
+ *
+ * This function first sends the HCI command
+ * Hci_Cmd_ST_Set_Uart_Baud_Rate. It then changes the baud rate in HW, and
+ * finally it waits for the Command Complete event for the
+ * Hci_Cmd_ST_Set_Uart_Baud_Rate command.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -EALREADY if baud rate change is already in progress.
+ *   -EFAULT if one or more of the UART related structs is not allocated.
+ *   -ENOMEM if skb allocation has failed.
+ *   -EPERM if setting the new baud rate has failed.
+ *   Error codes generated by create_work_item.
+ */
+static int set_baud_rate(int baud)
+{
+	struct tty_struct *tty = NULL;
+	int err = 0;
+	struct sk_buff *skb;
+	int old_baud_rate;
+
+	CG2900_INFO("set_baud_rate (%d baud)", baud);
+
+	if (uart_info->baud_rate_state != BAUD_IDLE) {
+		CG2900_ERR("Trying to set new baud rate before old setting "
+			   "is finished");
+		return -EALREADY;
+	}
+
+	if (uart_info->tty)
+		tty = uart_info->tty;
+	else {
+		CG2900_ERR("Important structs not allocated!");
+		return -EFAULT;
+	}
+
+	tty_throttle(tty);
+
+	/*
+	 * Store old baud rate so that we can restore it if something goes
+	 * wrong.
+	 */
+	old_baud_rate = uart_info->baud_rate;
+
+	skb = alloc_set_baud_rate_cmd(&baud);
+	if (!skb) {
+		CG2900_ERR("alloc_set_baud_rate_cmd failed");
+		return -ENOMEM;
+	}
+
+	SET_BAUD_STATE(BAUD_START);
+	uart_info->baud_rate = baud;
+
+	/* Queue the sk_buffer... */
+	skb_queue_tail(&uart_info->tx_queue, skb);
+
+	/* ... and call the common UART TX function */
+	err = create_work_item(uart_info->wq, work_do_transmit, NULL);
+	if (err) {
+		CG2900_ERR("Failed to send change baud rate cmd, freeing "
+			   "skb.");
+		skb = skb_dequeue_tail(&uart_info->tx_queue);
+		SET_BAUD_STATE(BAUD_IDLE);
+		uart_info->baud_rate = old_baud_rate;
+		kfree_skb(skb);
+		return err;
+	}
+
+	CG2900_DBG("Set baud rate cmd scheduled for sending.");
+
+	/*
+	 * Now wait for the command complete.
+	 * It will come at the new baudrate.
+	 */
+	wait_event_interruptible_timeout(uart_wait_queue,
+				((BAUD_SUCCESS == uart_info->baud_rate_state) ||
+				 (BAUD_FAIL    == uart_info->baud_rate_state)),
+				 msecs_to_jiffies(UART_RESP_TIMEOUT));
+	if (BAUD_SUCCESS == uart_info->baud_rate_state)
+		CG2900_DBG("Baudrate changed to %d baud", baud);
+	else {
+		CG2900_ERR("Failed to set new baudrate (%d)",
+			   uart_info->baud_rate_state);
+		err = -EPERM;
+	}
+
+	/* Finally flush the TTY so we are sure that is no bad data there */
+	if (tty->ops->flush_buffer) {
+		CG2900_DBG("Flushing TTY after baud rate change");
+		tty->ops->flush_buffer(tty);
+	}
+
+	/* Finished. Set state to IDLE */
+	SET_BAUD_STATE(BAUD_IDLE);
+
+	return err;
+}
+
+/**
+ * uart_open() - Open the CG2900 UART for data transfers.
+ * @dev:	Transport device information.
+ *
+ * Returns:
+ *   0 if there is no error,
+ *   -EACCES if write to transport failed,
+ *   -EIO if chip did not answer to commands.
+ */
+static int uart_open(struct cg2900_trans_dev *dev)
+{
+	u8 data[HCI_BT_RESET_LEN + HCI_H4_SIZE];
+	struct tty_struct *tty;
+	int bytes_written;
+
+	/*
+	 * Chip has just been started up. It has a system to autodetect
+	 * exact baud rate and transport to use. There are only a few commands
+	 * it will recognize and HCI Reset is one of them.
+	 * We therefore start with sending that before actually changing
+	 * baud rate.
+	 *
+	 * Create the Hci_Reset packet
+	 */
+	data[0] = HCI_BT_CMD_H4_CHANNEL;
+	data[1] = HCI_BT_RESET_CMD_LSB;
+	data[2] = HCI_BT_RESET_CMD_MSB;
+	data[3] = HCI_BT_RESET_PARAM_LEN;
+
+	/* Get the TTY info and send the packet */
+	tty = uart_info->tty;
+	SET_BAUD_STATE(BAUD_SENDING_RESET);
+	CG2900_DBG("Sending HCI reset before baud rate change");
+	bytes_written = tty->ops->write(tty, data,
+					HCI_BT_RESET_LEN + HCI_H4_SIZE);
+	if (bytes_written != HCI_BT_RESET_LEN + HCI_H4_SIZE) {
+		CG2900_ERR("Only wrote %d bytes", bytes_written);
+		SET_BAUD_STATE(BAUD_IDLE);
+		return -EACCES;
+	}
+
+	/*
+	 * Wait for command complete. If error, exit without changing
+	 * baud rate.
+	 */
+	wait_event_interruptible_timeout(uart_wait_queue,
+					BAUD_IDLE == uart_info->baud_rate_state,
+					msecs_to_jiffies(UART_RESP_TIMEOUT));
+	if (BAUD_IDLE != uart_info->baud_rate_state) {
+		CG2900_ERR("Failed to send HCI Reset");
+		SET_BAUD_STATE(BAUD_IDLE);
+		return -EIO;
+	}
+
+	/* Just return if there will be no change of baud rate */
+	if (uart_default_baud != uart_high_baud)
+		return set_baud_rate(uart_high_baud);
+	else
+		return 0;
+}
+
+/**
+ * uart_set_chip_power() - Enable or disable the CG2900.
+ * @chip_on:	true if chip shall be enabled, false otherwise.
+ */
+static void uart_set_chip_power(bool chip_on)
+{
+	int uart_baudrate = uart_default_baud;
+	struct tty_struct *tty;
+	struct cg2900_platform_data *pf_data;
+
+	CG2900_INFO("uart_set_chip_power: %s",
+		    (chip_on ? "ENABLE" : "DISABLE"));
+
+	if (uart_info->tty)
+		tty = uart_info->tty;
+	else {
+		CG2900_ERR("Important structs not allocated!");
+		return;
+	}
+
+	pf_data = dev_get_platdata(uart_info->dev->parent);
+
+	if (chip_on) {
+		if (cg2900_enable_regulator())
+			return;
+		if (pf_data->enable_chip) {
+			pf_data->enable_chip();
+			SET_SLEEP_STATE(CHIP_AWAKE);
+		}
+	} else {
+		if (pf_data->disable_chip) {
+			pf_data->disable_chip();
+			SET_SLEEP_STATE(CHIP_POWERED_DOWN);
+		}
+
+		cg2900_disable_regulator();
+		/*
+		 * Setting baud rate to 0 will tell UART driver to shut off its
+		 * clocks.
+		 */
+		uart_baudrate = ZERO_BAUD_RATE;
+	}
+	/*
+	 * Now we have to set the digital baseband UART
+	 * to default baudrate if chip is ON or to zero baudrate if
+	 * chip is turning OFF.
+	 */
+	 (void)set_tty_baud(tty, uart_baudrate);
+}
+
+/**
+ * uart_chip_startup_finished() - CG2900 startup finished.
+ */
+static void uart_chip_startup_finished(void)
+{
+	/* Run the timer. */
+	update_timer();
+}
+/**
+ * uart_close() - Close the CG2900 UART for data transfers.
+ * @dev:	Transport device information.
+ *
+ * Returns:
+ *   0 if there is no error.
+ */
+static int uart_close(struct cg2900_trans_dev *dev)
+{
+	/* The chip is already shut down. Power off the chip. */
+	uart_set_chip_power(false);
+
+	return 0;
+}
+
+/**
+ * uart_write() - Transmit data to CG2900 over UART.
+ * @dev:	Transport device information.
+ * @skb:	SK buffer to transmit.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   Errors from create_work_item.
+ */
+static int uart_write(struct cg2900_trans_dev *dev, struct sk_buff *skb)
+{
+	int err;
+
+	/* Delete sleep timer. */
+	(void)del_timer(&uart_info->timer);
+
+	CG2900_DBG_DATA_CONTENT("uart_write", skb->data, skb->len);
+
+	/* Queue the sk_buffer... */
+	skb_queue_tail(&uart_info->tx_queue, skb);
+
+	/* ...and start TX operation */
+	err = create_work_item(uart_info->wq, work_do_transmit, NULL);
+	if (err)
+		CG2900_ERR("Failed to create work item (%d) uart_tty_wakeup",
+			   err);
+
+	return err;
+}
+
+/**
+ * send_skb_to_core() - Sends packet received from UART to CG2900 Core.
+ * @skb:	Received data packet.
+ *
+ * This function checks if UART is waiting for Command complete event,
+ * see set_baud_rate.
+ * If it is waiting it checks if it is the expected packet and the status.
+ * If not is passes the packet to CG2900 Core.
+ */
+static void send_skb_to_core(struct sk_buff *skb)
+{
+	u8 status;
+
+	if (!skb) {
+		CG2900_ERR("Received NULL as skb");
+		return;
+	}
+
+	if (BAUD_WAITING == uart_info->baud_rate_state) {
+		/*
+		 * Should only really be one packet received now:
+		 * the CmdComplete for the SetBaudrate command
+		 * Let's see if this is the packet we are waiting for.
+		 */
+		if (!is_bt_cmd_complete_no_param(skb, SET_BAUD_RATE_LSB,
+						 SET_BAUD_RATE_MSB)) {
+			/*
+			 * Received other event. Should not really happen,
+			 * but pass the data to CG2900 Core anyway.
+			 */
+			CG2900_DBG_DATA("Sending packet to CG2900 Core while "
+					"waiting for CmdComplete");
+			cg2900_data_from_chip(skb);
+			return;
+		}
+
+		/*
+		 * We have received complete event for our baud rate
+		 * change command
+		 */
+		status = skb->data[HCI_BT_EVT_CMD_COMPL_STATUS_POS +
+				   HCI_H4_SIZE];
+		if (HCI_BT_ERROR_NO_ERROR == status) {
+			CG2900_DBG("Received baud rate change complete "
+				   "event OK");
+			SET_BAUD_STATE(BAUD_SUCCESS);
+		} else {
+			CG2900_ERR("Received baud rate change complete event "
+				   "with status 0x%X", status);
+			SET_BAUD_STATE(BAUD_FAIL);
+		}
+		wake_up_interruptible(&uart_wait_queue);
+		kfree_skb(skb);
+	} else if (BAUD_SENDING_RESET == uart_info->baud_rate_state) {
+		/*
+		 * Should only really be one packet received now:
+		 * the CmdComplete for the Reset command
+		 * Let's see if this is the packet we are waiting for.
+		 */
+		if (!is_bt_cmd_complete_no_param(skb, HCI_BT_RESET_CMD_LSB,
+						 HCI_BT_RESET_CMD_MSB)) {
+			/*
+			 * Received other event. Should not really happen,
+			 * but pass the data to CG2900 Core anyway.
+			 */
+			CG2900_DBG_DATA("Sending packet to CG2900 Core while "
+					"waiting for CmdComplete");
+			cg2900_data_from_chip(skb);
+			return;
+		}
+
+		/*
+		 * We have received complete event for our baud rate
+		 * change command
+		 */
+		status = skb->data[HCI_BT_EVT_CMD_COMPL_STATUS_POS +
+				   HCI_H4_SIZE];
+		if (HCI_BT_ERROR_NO_ERROR == status) {
+			CG2900_DBG("Received HCI reset complete event OK");
+			/*
+			 * Go back to BAUD_IDLE since this was not really
+			 * baud rate change but just a preparation of the chip
+			 * to be ready to receive commands.
+			 */
+			SET_BAUD_STATE(BAUD_IDLE);
+		} else {
+			CG2900_ERR("Received HCI reset complete event with "
+				   "status 0x%X", status);
+			SET_BAUD_STATE(BAUD_FAIL);
+		}
+		wake_up_interruptible(&uart_wait_queue);
+		kfree_skb(skb);
+	} else {
+		/* Just pass data to CG2900 Core */
+		cg2900_data_from_chip(skb);
+	}
+}
+
+static struct cg2900_trans_callbacks uart_cb = {
+	.open = uart_open,
+	.close = uart_close,
+	.write = uart_write,
+	.set_chip_power = uart_set_chip_power,
+	.chip_startup_finished  = uart_chip_startup_finished
+};
+
+/**
+ * check_data_len() - Check number of bytes to receive.
+ * @len:	Number of bytes left to receive.
+ */
+static void check_data_len(int len)
+{
+	/* First get number of bytes left in the sk_buffer */
+	register int room = skb_tailroom(uart_info->rx_skb);
+
+	if (!len) {
+		/* No data left to receive. Transmit to CG2900 Core */
+		send_skb_to_core(uart_info->rx_skb);
+	} else if (len > room) {
+		CG2900_ERR("Data length is too large (%d > %d)", len, room);
+		kfree_skb(uart_info->rx_skb);
+	} else {
+		/*
+		 * "Normal" case. Switch to data receiving state and store
+		 * data length.
+		 */
+		uart_info->rx_state = W4_DATA;
+		uart_info->rx_count = len;
+		return;
+	}
+
+	uart_info->rx_state = W4_PACKET_TYPE;
+	uart_info->rx_skb   = NULL;
+	uart_info->rx_count = 0;
+}
+
+/**
+ * uart_receive_skb() - Handles received UART data.
+ * @data:	Data received
+ * @count:	Number of bytes received
+ *
+ * The uart_receive_skb() function handles received UART data and puts it
+ * together to one complete packet.
+ *
+ * Returns:
+ *   Number of bytes not handled, i.e. 0 = no error.
+ */
+static int uart_receive_skb(const u8 *data, int count)
+{
+	const u8 *r_ptr;
+	u8 *w_ptr;
+	int len;
+	struct hci_event_hdr	*evt;
+	struct hci_acl_hdr	*acl;
+	union fm_leg_evt_or_irq	*fm;
+	struct gnss_hci_hdr	*gnss;
+	u8 *tmp;
+
+	r_ptr = data;
+	/* Continue while there is data left to handle */
+	while (count) {
+		/*
+		 * If we have already received a packet we know how many bytes
+		 * there are left.
+		 */
+		if (!uart_info->rx_count)
+			goto check_h4_header;
+
+		/* First copy received data into the skb_rx */
+		len = min_t(unsigned int, uart_info->rx_count, count);
+		memcpy(skb_put(uart_info->rx_skb, len), r_ptr, len);
+		/* Update counters from the length and step the data pointer */
+		uart_info->rx_count -= len;
+		count -= len;
+		r_ptr += len;
+
+		if (uart_info->rx_count)
+			/*
+			 * More data to receive to current packet. Break and
+			 * wait for next data on the UART.
+			 */
+			break;
+
+		/* Handle the different states */
+		tmp = uart_info->rx_skb->data + CG2900_SKB_RESERVE;
+		switch (uart_info->rx_state) {
+		case W4_DATA:
+			/*
+			 * Whole data packet has been received.
+			 * Transmit it to CG2900 Core.
+			 */
+			send_skb_to_core(uart_info->rx_skb);
+
+			uart_info->rx_state = W4_PACKET_TYPE;
+			uart_info->rx_skb = NULL;
+			continue;
+
+		case W4_EVENT_HDR:
+			evt = (struct hci_event_hdr *)tmp;
+			check_data_len(evt->plen);
+			/* Header read. Continue with next bytes */
+			continue;
+
+		case W4_ACL_HDR:
+			acl = (struct hci_acl_hdr *)tmp;
+			check_data_len(le16_to_cpu(acl->dlen));
+			/* Header read. Continue with next bytes */
+			continue;
+
+		case W4_FM_RADIO_HDR:
+			fm = (union fm_leg_evt_or_irq *)tmp;
+			check_data_len(fm->param_length);
+			/* Header read. Continue with next bytes */
+			continue;
+
+		case W4_GNSS_HDR:
+			gnss = (struct gnss_hci_hdr *)tmp;
+			check_data_len(le16_to_cpu(gnss->plen));
+			/* Header read. Continue with next bytes */
+			continue;
+
+		default:
+			CG2900_ERR("Bad state indicating memory overwrite "
+				   "(0x%X)", (u8)(uart_info->rx_state));
+			break;
+		}
+
+check_h4_header:
+		/* Check which H:4 packet this is and update RX states */
+		if (*r_ptr == HCI_BT_EVT_H4_CHANNEL) {
+			uart_info->rx_state = W4_EVENT_HDR;
+			uart_info->rx_count = HCI_BT_EVT_HDR_SIZE;
+		} else if (*r_ptr == HCI_BT_ACL_H4_CHANNEL) {
+			uart_info->rx_state = W4_ACL_HDR;
+			uart_info->rx_count = HCI_BT_ACL_HDR_SIZE;
+		} else if (*r_ptr == HCI_FM_RADIO_H4_CHANNEL) {
+			uart_info->rx_state = W4_FM_RADIO_HDR;
+			uart_info->rx_count = HCI_FM_RADIO_HDR_SIZE;
+		} else if (*r_ptr == HCI_GNSS_H4_CHANNEL) {
+			uart_info->rx_state = W4_GNSS_HDR;
+			uart_info->rx_count = HCI_GNSS_HDR_SIZE;
+		} else {
+			CG2900_ERR("Unknown HCI packet type 0x%X", (u8)*r_ptr);
+			r_ptr++;
+			count--;
+			continue;
+		}
+
+		/*
+		 * Allocate packet. We do not yet know the size and therefore
+		 * allocate max size.
+		 */
+		uart_info->rx_skb = alloc_rx_skb(RX_SKB_MAX_SIZE, GFP_ATOMIC);
+		if (!uart_info->rx_skb) {
+			CG2900_ERR("Can't allocate memory for new packet");
+			uart_info->rx_state = W4_PACKET_TYPE;
+			uart_info->rx_count = 0;
+			return 0;
+		}
+
+		/* Write the H:4 header first in the sk_buffer */
+		w_ptr = skb_put(uart_info->rx_skb, 1);
+		*w_ptr = *r_ptr;
+
+		/* First byte (H4 header) read. Goto next byte */
+		r_ptr++;
+		count--;
+	}
+
+	return count;
+}
+
+/**
+ * uart_tty_open() - Called when UART line discipline changed to N_HCI.
+ * @tty:	Pointer to associated TTY instance data.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   Errors from cg2900_register_trans_driver.
+ */
+static int uart_tty_open(struct tty_struct *tty)
+{
+	int err;
+
+	CG2900_INFO("uart_tty_open");
+
+	/* Set the structure pointers and set the UART receive room */
+	uart_info->tty = tty;
+	tty->disc_data = NULL;
+	tty->receive_room = UART_RECEIVE_ROOM;
+
+	/*
+	 * Flush any pending characters in the driver and line discipline.
+	 * Don't use ldisc_ref here as the open path is before the ldisc is
+	 * referencable.
+	 */
+	if (tty->ldisc->ops->flush_buffer)
+		tty->ldisc->ops->flush_buffer(tty);
+
+	tty_driver_flush_buffer(tty);
+
+	/* Tell CG2900 Core that UART is connected */
+	err = cg2900_register_trans_driver(&uart_cb, NULL);
+	if (err)
+		CG2900_ERR("Could not register transport driver (%d)", err);
+
+	if (tty->ops->tiocmget && tty->ops->break_ctl)
+		uart_info->sleep_allowed = true;
+	else
+		CG2900_ERR("Sleep mode not available.");
+
+	return err;
+
+}
+
+/**
+ * uart_tty_close() - Close UART tty.
+ * @tty:	Pointer to associated TTY instance data.
+ *
+ * The uart_tty_close() function is called when the line discipline is changed
+ * to something else, the TTY is closed, or the TTY detects a hangup.
+ */
+static void uart_tty_close(struct tty_struct *tty)
+{
+	int err;
+
+	CG2900_INFO("uart_tty_close");
+
+	BUG_ON(!uart_info);
+	BUG_ON(!uart_info->wq);
+
+	err = create_work_item(uart_info->wq, work_hw_deregistered, NULL);
+	if (err)
+		CG2900_ERR("Failed to create work item (%d) "
+			   "work_hw_deregistered", err);
+}
+
+/**
+ * uart_tty_wakeup() - Callback function for transmit wake up.
+ * @tty:	Pointer to associated TTY instance data.
+ *
+ * The uart_tty_wakeup() callback function is called when low level
+ * device driver can accept more send data.
+ */
+static void uart_tty_wakeup(struct tty_struct *tty)
+{
+	int err;
+
+	CG2900_INFO("uart_tty_wakeup");
+
+	/*
+	 * Clear the TTY_DO_WRITE_WAKEUP bit that is set in
+	 * work_do_transmit().
+	 */
+	clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
+
+	if (tty != uart_info->tty)
+		return;
+
+	/* Delete sleep timer. */
+	(void)del_timer(&uart_info->timer);
+
+	/* Start TX operation */
+	err = create_work_item(uart_info->wq, work_do_transmit, NULL);
+	if (err)
+		CG2900_ERR("Failed to create work item (%d) uart_tty_wakeup",
+			   err);
+}
+
+/**
+ * uart_tty_receive() - Called by TTY low level driver when receive
data is available.
+ * @tty:	Pointer to TTY instance data
+ * @data:	Pointer to received data
+ * @flags:	Pointer to flags for data
+ * @count:	Count of received data in bytes
+ */
+static void uart_tty_receive(struct tty_struct *tty, const u8 *data,
+			     char *flags, int count)
+{
+	CG2900_INFO("uart_tty_receive");
+
+	if (tty != uart_info->tty)
+		return;
+
+	CG2900_DBG_DATA("Received data with length = %d and first byte 0x%02X",
+			count, data[0]);
+	CG2900_DBG_DATA_CONTENT("uart_tty_receive", data, count);
+
+	/* Restart data timer */
+	update_timer();
+	spin_lock(&uart_info->rx_lock);
+	uart_receive_skb(data, count);
+	spin_unlock(&uart_info->rx_lock);
+
+}
+
+/**
+ * uart_tty_ioctl() -  Process IOCTL system call for the TTY device.
+ * @tty:   Pointer to TTY instance data.
+ * @file:  Pointer to open file object for device.
+ * @cmd:   IOCTL command code.
+ * @arg:   Argument for IOCTL call (cmd dependent).
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -EBADF if supplied TTY struct is not correct.
+ *   Error codes from n_tty_iotcl_helper.
+ */
+static int uart_tty_ioctl(struct tty_struct *tty, struct file *file,
+			  unsigned int cmd, unsigned long arg)
+{
+	int err = 0;
+
+	CG2900_INFO("uart_tty_ioctl cmd %d", cmd);
+	CG2900_DBG("DIR: %d, TYPE: %d, NR: %d, SIZE: %d", _IOC_DIR(cmd),
+		   _IOC_TYPE(cmd), _IOC_NR(cmd), _IOC_SIZE(cmd));
+
+
+
+	switch (cmd) {
+	case HCIUARTSETFD:
+		/* Save file object to device. */
+		if (!uart_info->fd)
+			uart_info->fd = file;
+		else
+			CG2900_DBG("Cannot store file object to device.");
+		break;
+	case HCIUARTSETPROTO: /* Fallthrough */
+	case HCIUARTGETPROTO:
+	case HCIUARTGETDEVICE:
+		/*
+		 * We don't do anything special here, but we have to show we
+		 * handle it.
+		 */
+		break;
+
+	default:
+		err = n_tty_ioctl_helper(tty, file, cmd, arg);
+		break;
+	};
+
+	return err;
+}
+
+/*
+ * We don't provide read/write/poll interface for user space.
+ */
+static ssize_t uart_tty_read(struct tty_struct *tty, struct file *file,
+			     unsigned char __user *buf, size_t nr)
+{
+	CG2900_INFO("uart_tty_read");
+	return 0;
+}
+
+static ssize_t uart_tty_write(struct tty_struct *tty, struct file *file,
+			      const unsigned char *data, size_t count)
+{
+	CG2900_INFO("uart_tty_write");
+	return count;
+}
+
+static unsigned int uart_tty_poll(struct tty_struct *tty, struct file *filp,
+				  poll_table *wait)
+{
+	return 0;
+}
+
+/* Generic functions */
+
+/* The uart_ldisc structure is used when registering to the UART framework. */
+static struct tty_ldisc_ops uart_ldisc = {
+	.magic        = TTY_LDISC_MAGIC,
+	.name         = "n_hci",
+	.open         = uart_tty_open,
+	.close        = uart_tty_close,
+	.read         = uart_tty_read,
+	.write        = uart_tty_write,
+	.ioctl        = uart_tty_ioctl,
+	.poll         = uart_tty_poll,
+	.receive_buf  = uart_tty_receive,
+	.write_wakeup = uart_tty_wakeup,
+	.owner        = THIS_MODULE
+};
+
+/**
+ * cg2900_uart_probe() - Initialize CG2900 UART resources.
+ * @pdev:	Platform device.
+ *
+ * This function initializes the module and registers to the UART framework.
+ *
+ * Returns:
+ *   0 if success.
+ *   -ENOMEM for failed alloc or structure creation.
+ *   -ECHILD for failed work queue creation.
+ *   Error codes generated by tty_register_ldisc.
+ */
+static int __devinit cg2900_uart_probe(struct platform_device *pdev)
+{
+	int err = 0;
+
+	CG2900_INFO("cg2900_uart_probe");
+
+	uart_info = kzalloc(sizeof(*uart_info), GFP_KERNEL);
+	if (!uart_info) {
+		CG2900_ERR("Couldn't allocate uart_info");
+		return -ENOMEM;
+	}
+
+	uart_info->sleep_state = CHIP_POWERED_DOWN;
+	skb_queue_head_init(&uart_info->tx_queue);
+	mutex_init(&uart_info->tx_mutex);
+	spin_lock_init(&uart_info->rx_lock);
+	mutex_init(&(uart_info->sleep_state_lock));
+
+	/* Init UART TX work queue */
+	uart_info->wq = create_singlethread_workqueue(UART_WQ_NAME);
+	if (!uart_info->wq) {
+		CG2900_ERR("Could not create workqueue");
+		err = -ECHILD; /* No child processes */
+		goto error_handling_wq;
+	}
+	init_timer(&uart_info->timer);
+	uart_info->timer.function = sleep_timer_expired;
+	uart_info->timer.expires  = jiffies + cg2900_get_sleep_timeout();
+
+	/* Register the tty discipline. We will see what will be used. */
+	err = tty_register_ldisc(N_HCI, &uart_ldisc);
+	if (err) {
+		CG2900_ERR("HCI line discipline registration failed. (0x%X)",
+			   err);
+		goto error_handling_register;
+	}
+
+	uart_info->dev = &pdev->dev;
+
+	goto finished;
+
+error_handling_register:
+	destroy_workqueue(uart_info->wq);
+error_handling_wq:
+	mutex_destroy(&uart_info->tx_mutex);
+	kfree(uart_info);
+	uart_info = NULL;
+finished:
+	return err;
+}
+
+/**
+ * cg2900_uart_remove() - Release CG2900 UART resources.
+ * @pdev:	Platform device.
+ *
+ * Returns:
+ *   0 if success.
+ *   Error codes generated by tty_unregister_ldisc.
+ */
+static int __devexit cg2900_uart_remove(struct platform_device *pdev)
+{
+	int err;
+
+	CG2900_INFO("cg2900_uart_remove");
+
+	/* Release tty registration of line discipline */
+	err = tty_unregister_ldisc(N_HCI);
+	if (err)
+		CG2900_ERR("Can't unregister HCI line discipline (%d)", err);
+
+	if (!uart_info)
+		return err;
+
+	destroy_workqueue(uart_info->wq);
+	mutex_destroy(&uart_info->tx_mutex);
+
+	kfree(uart_info);
+	uart_info = NULL;
+	return err;
+}
+
+static struct platform_driver cg2900_uart_driver = {
+	.driver = {
+		.name	= "cg2900-uart",
+		.owner	= THIS_MODULE,
+	},
+	.probe	= cg2900_uart_probe,
+	.remove	= __devexit_p(cg2900_uart_remove),
+#ifdef CONFIG_PM
+	.suspend = cg2900_uart_suspend,
+	.resume = cg2900_uart_resume
+#endif
+};
+
+/**
+ * cg2900_uart_init() - Initialize module.
+ *
+ * Registers platform driver.
+ */
+static int __init cg2900_uart_init(void)
+{
+	CG2900_INFO("cg2900_uart_init");
+	return platform_driver_register(&cg2900_uart_driver);
+}
+
+/**
+ * cg2900_uart_exit() - Remove module.
+ *
+ * Unregisters platform driver.
+ */
+static void __exit cg2900_uart_exit(void)
+{
+	CG2900_INFO("cg2900_uart_exit");
+	platform_driver_unregister(&cg2900_uart_driver);
+}
+
+module_init(cg2900_uart_init);
+module_exit(cg2900_uart_exit);
+
+module_param(uart_default_baud, int, S_IRUGO);
+MODULE_PARM_DESC(uart_default_baud,
+		 "Default UART baud rate, e.g. 115200. If not set 115200 will "
+		 "be used.");
+
+module_param(uart_high_baud, int, S_IRUGO | S_IWUSR | S_IWGRP);
+MODULE_PARM_DESC(uart_high_baud,
+		 "High speed UART baud rate, e.g. 4000000.  If not set 3000000 "
+		 "will be used.");
+
+MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("ST-Ericsson CG2900 UART Driver");
-- 
1.6.3.3

^ permalink raw reply related

* [PATCH 4/9] mfd: Add chip handler for the ST-Ericsson STLC2690.
From: Par-Gunnar Hjalmdahl @ 2010-10-22 10:37 UTC (permalink / raw)
  To: linus.walleij, linux-bluetooth, linux-kernel

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

Signed-off-by: Par-Gunnar Hjalmdahl <par-gunnar.p.hjalmdahl@stericsson.com>
---
 drivers/mfd/Kconfig                |    6 +
 drivers/mfd/cg2900/Makefile        |    1 +
 drivers/mfd/cg2900/stlc2690_chip.c | 1150 ++++++++++++++++++++++++++++++++++++
 drivers/mfd/cg2900/stlc2690_chip.h |   37 ++
 4 files changed, 1194 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 fca7e29..a8e790f 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -276,6 +276,12 @@ config MFD_CG2900_CHIP
 	help
 	  Support for ST-Ericsson CG2900 Connectivity Controller

+config MFD_STLC2690_CHIP
+	tristate "Support STLC2690 Connectivity controller"
+	depends on MFD_CG2900
+	help
+	  Support for ST-Ericsson STLC2690 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 c4aabf3..4935daa 100644
--- a/drivers/mfd/cg2900/Makefile
+++ b/drivers/mfd/cg2900/Makefile
@@ -8,4 +8,5 @@ export-objs			:= cg2900_core.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..a7b81b9
--- /dev/null
+++ b/drivers/mfd/cg2900/stlc2690_chip.c
@@ -0,0 +1,1150 @@
+/*
+ * drivers/mfd/cg2900/ste_stlc2690.c
+ *
+ * Copyright (C) ST-Ericsson SA 2010
+ * Authors:
+ * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for
ST-Ericsson.
+ * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson.
+ * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson.
+ * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson.
+ * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson.
+ * License terms:  GNU General Public License (GPL), version 2
+ *
+ * Linux Bluetooth HCI H:4 Driver for ST-Ericsson STLC2690 BT/FM controller.
+ */
+
+#include <asm/byteorder.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 "hci_defines.h"
+#include "stlc2690_chip.h"
+#include "cg2900_core.h"
+#include "cg2900_debug.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					"stlc2690_wq"
+#define PATCH_INFO_FILE				"cg2900_patch_info.fw"
+#define FACTORY_SETTINGS_INFO_FILE		"cg2900_settings_info.fw"
+
+/* Supported chips */
+#define SUPP_MANUFACTURER			0x30
+#define SUPP_REVISION_MIN			0x0500
+#define SUPP_REVISION_MAX			0x06FF
+
+/* Size of file chunk ID */
+#define FILE_CHUNK_ID_SIZE			1
+#define VS_SEND_FILE_CHUNK_ID_POS		4
+#define BT_CMD_LEN_POS				3
+
+/* State setting macros */
+#define SET_BOOT_STATE(__new_state) \
+	CG2900_SET_STATE("boot_state", stlc2690_info->boot_state, __new_state)
+#define SET_FILE_LOAD_STATE(__new_state) \
+	CG2900_SET_STATE("file_load_state", stlc2690_info->file_load_state, \
+			 __new_state)
+#define SET_DOWNLOAD_STATE(__new_state) \
+	CG2900_SET_STATE("download_state", stlc2690_info->download_state, \
+			 __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_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
+
+/**
+ * struct stlc2690_work_struct - Work structure for CG2900 Core module.
+ * @work:	Work structure.
+ * @skb:	Data packet.
+ * @data:	Private data for user.
+ *
+ * This structure is used to pack work for work queue.
+ */
+struct stlc2690_work_struct {
+	struct work_struct	work;
+	struct sk_buff		*skb;
+	void			*data;
+};
+
+/**
+ * enum boot_state - BOOT-state for CG2900 Core.
+ * @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 Core is retreiving file to load.
+ * @BOOT_DOWNLOAD_PATCH:		CG2900 Core is downloading patches.
+ * @BOOT_ACTIVATE_PATCHES_AND_SETTINGS:	CG2900 Core is activating patches and
+ *					settings.
+ * @BOOT_READY:				CG2900 Core boot is ready.
+ * @BOOT_FAILED:			CG2900 Core 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_READY,
+	BOOT_FAILED
+};
+
+/**
+ * enum file_load_state - BOOT_FILE_LOAD-state for CG2900 Core.
+ * @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_device_id - Structure for connecting H4 channel to user.
+ * @name:		Name of device.
+ * @h4_channel:	HCI H:4 channel used by this device.
+ */
+struct stlc2690_device_id {
+	char	*name;
+	int	h4_channel;
+};
+
+/**
+ * struct stlc2690_info - Main info structure for STLC2690.
+ * @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 STLC2690.
+ * @file_load_state:	Current BOOT_FILE_LOAD-state of STLC2690.
+ * @download_state:	Current BOOT_DOWNLOAD-state of STLC2690.
+ * @wq:			STLC2690 workqueue.
+ * @chip_dev:		Chip info.
+ */
+struct stlc2690_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 file_load_state		file_load_state;
+	enum download_state		download_state;
+	struct workqueue_struct		*wq;
+	struct cg2900_chip_dev		chip_dev;
+};
+
+static struct stlc2690_info *stlc2690_info;
+
+#define NBR_OF_DEVS			6
+
+/*
+ * stlc2690_channels() - Array containing available H4 channels for
the STLC2690
+ * ST-Ericsson Connectivity controller.
+ */
+struct stlc2690_device_id stlc2690_channels[NBR_OF_DEVS] = {
+	{CG2900_BT_CMD,			CHANNEL_BT_CMD},
+	{CG2900_BT_ACL,			CHANNEL_BT_ACL},
+	{CG2900_BT_EVT,			CHANNEL_BT_EVT},
+	{CG2900_HCI_LOGGER,		CHANNEL_HCI_LOGGER},
+	{CG2900_US_CTRL,		CHANNEL_US_CTRL},
+	{CG2900_CORE,			CHANNEL_CORE}
+};
+
+/*
+ * Internal functions
+ */
+
+/**
+ * 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 CG2900 Core.
+ * Note that the data must contain the H:4 header.
+ */
+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 = alloc_skb(length, GFP_ATOMIC);
+	if (!skb) {
+		CG2900_ERR("Couldn't allocate sk_buff with length %d", length);
+		return;
+	}
+
+	memcpy(skb_put(skb, length), data, length);
+	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);
+	}
+}
+
+/**
+ * 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;
+	struct hci_command_hdr *hdr;
+	u8 *tmp;
+	u8 *data;
+	u8 plen = sizeof(*cmd) + BT_BDADDR_SIZE - 1;
+
+	data = kmalloc(sizeof(*hdr) + plen, GFP_KERNEL);
+	if (!data)
+		return;
+	tmp = data;
+
+	hdr = (struct hci_command_hdr *)tmp;
+	hdr->opcode = cpu_to_le16(STLC2690_BT_OP_VS_STORE_IN_FS);
+	hdr->plen = plen;
+
+	tmp += sizeof(*hdr);
+	cmd = (struct bt_vs_store_in_fs_cmd *)tmp;
+	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);
+
+	SET_BOOT_STATE(BOOT_SEND_BD_ADDRESS);
+
+	create_and_send_bt_cmd(data, sizeof(*hdr) + plen);
+
+	kfree(data);
+}
+
+/**
+ * create_work_item() - Create work item and add it to the work queue.
+ * @work_func:	Work function.
+ * @skb:	Data packet.
+ * @data:	Private data for caller.
+ */
+static void create_work_item(work_func_t work_func, struct sk_buff *skb,
+			     void *data)
+{
+	struct stlc2690_work_struct *new_work;
+	int wq_err = 1;
+
+	new_work = kmalloc(sizeof(*new_work), GFP_ATOMIC);
+	if (!new_work) {
+		CG2900_ERR("Failed to alloc memory for stlc2690_work_struct!");
+		return;
+	}
+
+	new_work->skb = skb;
+	new_work->data = data;
+	INIT_WORK(&new_work->work, work_func);
+
+	wq_err = queue_work(stlc2690_info->wq, &new_work->work);
+	if (!wq_err) {
+		CG2900_ERR("Failed to queue work_struct because it's already in"
+			   " the queue!");
+		kfree(new_work);
+	}
+}
+
+/**
+ * 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: file_name 0x%X, "
+			   "line_buffer 0x%X",
+			   (u32)file_name, (u32)line_buffer);
+		goto finished;
+	}
+
+	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.
+		 * Store the filename if the actual file can be found in
+		 * the file system.
+		 */
+		if (sscanf(line_buffer, "%x%x%s", &hci_rev, &lmp_sub,
+			   *file_name) == 3
+		    && hci_rev == stlc2690_info->chip_dev.chip.hci_revision
+		    && lmp_sub ==
+				 stlc2690_info->chip_dev.chip.hci_sub_version) {
+			CG2900_DBG("File matching chip found\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);
+
+finished:
+	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 hci_command_hdr *hdr;
+	struct bt_vs_write_file_block_cmd *cmd;
+	u8 *data;
+	u8 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)(stlc2690_info->fw_file->size -
+				  stlc2690_info->file_offset));
+
+	if (bytes_to_copy <= 0) {
+		/* Nothing more to read in file. */
+		SET_DOWNLOAD_STATE(DOWNLOAD_SUCCESS);
+		stlc2690_info->chunk_id = 0;
+		stlc2690_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. */
+	plen = sizeof(*cmd) - 1 + bytes_to_copy;
+	skb = cg2900_alloc_skb(sizeof(*hdr) + 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, sizeof(*hdr) + plen);
+
+	data = skb->data;
+	hdr = (struct hci_command_hdr *)data;
+	hdr->opcode = cpu_to_le16(STLC2690_BT_OP_VS_WRITE_FILE_BLOCK);
+	hdr->plen = plen;
+
+	data += sizeof(*hdr);
+	cmd = (struct bt_vs_write_file_block_cmd *)data;
+	cmd->id = stlc2690_info->chunk_id;
+	stlc2690_info->chunk_id++;
+
+	/* Copy the data from offset position */
+	memcpy(&(cmd->data),
+	       &(stlc2690_info->fw_file->data[stlc2690_info->file_offset]),
+	       bytes_to_copy);
+
+	/* Increase offset with number of bytes copied */
+	stlc2690_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 (stlc2690_info->download_state != DOWNLOAD_SUCCESS)
+		return;
+
+	/* Settings file finished. Release used resources */
+	CG2900_DBG("Settings file finished, release used resources");
+
+	if (stlc2690_info->fw_file) {
+		release_firmware(stlc2690_info->fw_file);
+		stlc2690_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 (stlc2690_info->download_state != DOWNLOAD_SUCCESS)
+		return;
+
+	/* Patch file finished. Release used resources */
+	CG2900_DBG("Patch file finished, release used resources");
+
+	if (stlc2690_info->fw_file) {
+		release_firmware(stlc2690_info->fw_file);
+		stlc2690_info->fw_file = NULL;
+	}
+
+	err = request_firmware(&(stlc2690_info->fw_file),
+			       stlc2690_info->settings_file_name,
+			       stlc2690_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_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 stlc2690_work_struct *current_work = NULL;
+
+	if (!work) {
+		CG2900_ERR("work == NULL");
+		return;
+	}
+
+	current_work = container_of(work, struct stlc2690_work_struct, work);
+
+	cg2900_chip_startup_finished(-EIO);
+
+	kfree(current_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)
+{
+	struct stlc2690_work_struct *current_work;
+	int err = 0;
+	bool file_found;
+	const struct firmware *patch_info;
+	const struct firmware *settings_info;
+
+	if (!work) {
+		CG2900_ERR("work == NULL");
+		return;
+	}
+
+	current_work = container_of(work, struct stlc2690_work_struct, work);
+
+	/* Check that we are in the right state */
+	if (stlc2690_info->boot_state != BOOT_GET_FILES_TO_LOAD)
+		goto finished;
+
+	/* Open patch info file. */
+	err = request_firmware(&patch_info, PATCH_INFO_FILE,
+			       stlc2690_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,
+				      &(stlc2690_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,
+			       stlc2690_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,
+				      &(stlc2690_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);
+	stlc2690_info->chunk_id = 0;
+	stlc2690_info->file_offset = 0;
+	stlc2690_info->fw_file = NULL;
+
+	/* OK. Now it is time to download the patches */
+	err = request_firmware(&(stlc2690_info->fw_file),
+			       stlc2690_info->patch_file_name,
+			       stlc2690_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(current_work);
+}
+
+/**
+ * work_cont_with_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_with_file_download(struct work_struct *work)
+{
+	struct stlc2690_work_struct *current_work;
+
+	if (!work) {
+		CG2900_ERR("work == NULL");
+		return;
+	}
+
+	current_work = container_of(work, struct stlc2690_work_struct, work);
+
+	/* Continue to send patches or settings to the controller */
+	if (stlc2690_info->file_load_state == FILE_LOAD_GET_PATCH)
+		send_patch_file();
+	else if (stlc2690_info->file_load_state ==
+			FILE_LOAD_GET_STATIC_SETTINGS)
+		send_settings_file();
+	else
+		CG2900_INFO("No more files to load");
+
+	kfree(current_work);
+}
+
+/**
+ * handle_reset_cmd_complete() - Handle a received HCI Command
Complete event for a Reset command.
+ * @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;
+
+	CG2900_INFO("Received Reset complete event");
+
+	if (stlc2690_info->boot_state != BOOT_ACTIVATE_PATCHES_AND_SETTINGS)
+		return false;
+
+	status = data[0];
+
+	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 Reset complete event with status 0x%X",
+			   status);
+		SET_BOOT_STATE(BOOT_FAILED);
+		cg2900_chip_startup_finished(-EIO);
+	}
+	return true;
+}
+
+/**
+ * handle_vs_store_in_fs_cmd_complete() - Handle a received HCI
Command Complete event for a VS StoreInFS command.
+ * @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;
+
+	CG2900_INFO("Received Store_in_FS complete event");
+
+	if (stlc2690_info->boot_state != BOOT_SEND_BD_ADDRESS)
+		return false;
+
+	status = data[0];
+
+	if (HCI_BT_ERROR_NO_ERROR == status) {
+		struct hci_command_hdr cmd;
+
+		/* Send HCI Reset command to activate patches */
+		SET_BOOT_STATE(BOOT_ACTIVATE_PATCHES_AND_SETTINGS);
+		cmd.opcode = cpu_to_le16(HCI_OP_RESET);
+		cmd.plen = 0; /* No parameters for HCI 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, NULL, NULL);
+	}
+	/* We have now handled the packet */
+	return true;
+}
+
+/**
+ * handle_vs_write_file_block_cmd_complete() - Handle a received HCI
Command Complete event for a VS WriteFileBlock command.
+ * @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;
+
+	if ((stlc2690_info->boot_state != BOOT_DOWNLOAD_PATCH) ||
+	    (stlc2690_info->download_state != DOWNLOAD_PENDING))
+		return false;
+
+	status = data[0];
+	if (HCI_BT_ERROR_NO_ERROR == status) {
+		/* Received good confirmation. Start work to continue. */
+		create_work_item(work_cont_with_file_download, NULL, NULL);
+	} else {
+		CG2900_ERR("Command Complete for WriteFileBlock received with "
+			   "error 0x%X", status);
+		SET_DOWNLOAD_STATE(DOWNLOAD_FAILED);
+		SET_BOOT_STATE(BOOT_FAILED);
+		if (stlc2690_info->fw_file) {
+			release_firmware(stlc2690_info->fw_file);
+			stlc2690_info->fw_file = NULL;
+		}
+		create_work_item(work_reset_after_error, NULL, NULL);
+	}
+	/* We have now handled the packet */
+	return true;
+}
+
+/**
+ * handle_rx_data_bt_evt() - Check if received data should be handled.
+ * @skb:	Data packet
+ *
+ * The handle_rx_data_bt_evt() function checks if received data should be
+ * handled by STLC2690. 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;
+	struct hci_ev_cmd_complete *cmd_complete;
+	u16 op_code;
+
+	evt = (struct hci_event_hdr *)data;
+
+	/* First check the event code. Only handle Command Complete Event */
+	if (HCI_EV_CMD_COMPLETE != evt->evt)
+		return false;
+
+	data += sizeof(*evt);
+	cmd_complete = (struct hci_ev_cmd_complete *)data;
+
+	op_code = le16_to_cpu(cmd_complete->opcode);
+
+	CG2900_DBG_DATA("Received Command Complete: op_code = 0x%04X", op_code);
+	data += sizeof(*cmd_complete); /* Move to first byte after OCF */
+
+	if (op_code == HCI_OP_RESET)
+		pkt_handled = handle_reset_cmd_complete(data);
+	else if (op_code == STLC2690_BT_OP_VS_STORE_IN_FS)
+		pkt_handled = handle_vs_store_in_fs_cmd_complete(data);
+	else if (op_code == STLC2690_BT_OP_VS_WRITE_FILE_BLOCK)
+		pkt_handled = handle_vs_write_file_block_cmd_complete(data);
+
+	if (pkt_handled)
+		kfree_skb(skb);
+
+	return pkt_handled;
+}
+
+/**
+ * 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, NULL, NULL);
+
+	return 0;
+}
+
+/**
+ * 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 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)
+{
+	/* Then check if this is a response to data we have sent */
+	return handle_rx_data_bt_evt(skb);
+}
+
+/**
+ * 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 < NBR_OF_DEVS; i++) {
+		if (0 == strncmp(name, stlc2690_channels[i].name,
+				 CG2900_MAX_NAME_SIZE)) {
+			/* Device found. Return H4 channel */
+			*h4_channel = stlc2690_channels[i].h4_channel;
+			err = 0;
+		}
+	}
+
+	return err;
+}
+
+/**
+ * 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("check_chip_support");
+
+	/*
+	 * Check if this is a CG2690 revision. We do not care about
+	 * the sub-version at the moment.
+	 * Change this if necessary.
+	 */
+	if ((dev->chip.manufacturer != SUPP_MANUFACTURER) ||
+	    (dev->chip.hci_revision < SUPP_REVISION_MIN) ||
+	    (dev->chip.hci_revision > SUPP_REVISION_MAX)) {
+		CG2900_DBG("Chip not supported by STLC2690 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 STLC2690 driver");
+
+	/* Store needed data */
+	dev->user_data = stlc2690_info;
+	memcpy(&(stlc2690_info->chip_dev), dev, sizeof(*dev));
+	/* Set the callbacks */
+	dev->cb.chip_startup = chip_startup;
+	dev->cb.data_from_chip = data_from_chip;
+	dev->cb.get_h4_channel = get_h4_channel;
+
+	return true;
+}
+
+static struct cg2900_id_callbacks stlc2690_id_callbacks = {
+	.check_chip_support = check_chip_support
+};
+
+/**
+ * stlc2690_chip_probe() - Initialize STLC2690 chip handler resources.
+ * @pdev:	Platform device.
+ *
+ * This function initialize 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 = 0;
+
+	CG2900_INFO("stlc2690_chip_probe");
+
+	stlc2690_info = kzalloc(sizeof(*stlc2690_info), GFP_ATOMIC);
+	if (!stlc2690_info) {
+		CG2900_ERR("Couldn't allocate stlc2690_info");
+		err = -ENOMEM;
+		goto finished;
+	}
+
+	stlc2690_info->dev = &(pdev->dev);
+
+	stlc2690_info->wq = create_singlethread_workqueue(WQ_NAME);
+	if (!stlc2690_info->wq) {
+		CG2900_ERR("Could not create workqueue");
+		err = -ENOMEM;
+		goto err_handling_free_info;
+	}
+
+	/*
+	 * Allocate file names that will be used, deallocated in stlc2690_exit.
+	 */
+	stlc2690_info->patch_file_name = kzalloc(NAME_MAX + 1, GFP_ATOMIC);
+	if (!stlc2690_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 stlc2690_exit.
+	 */
+	stlc2690_info->settings_file_name = kzalloc(NAME_MAX + 1,
+						    GFP_ATOMIC);
+	if (!stlc2690_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(&stlc2690_id_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(stlc2690_info->settings_file_name);
+err_handling_free_patch_name:
+	kfree(stlc2690_info->patch_file_name);
+err_handling_destroy_wq:
+	destroy_workqueue(stlc2690_info->wq);
+err_handling_free_info:
+	kfree(stlc2690_info);
+	stlc2690_info = NULL;
+finished:
+	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)
+{
+	CG2900_INFO("stlc2690_chip_remove");
+
+	if (!stlc2690_info)
+		return 0;
+
+	kfree(stlc2690_info->settings_file_name);
+	kfree(stlc2690_info->patch_file_name);
+	destroy_workqueue(stlc2690_info->wq);
+	kfree(stlc2690_info);
+	stlc2690_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)
+{
+	CG2900_INFO("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)
+{
+	CG2900_INFO("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..deb974d
--- /dev/null
+++ b/drivers/mfd/cg2900/stlc2690_chip.h
@@ -0,0 +1,37 @@
+/*
+ * drivers/mfd/cg2900/stlc2690_chip.h
+ *
+ * Copyright (C) ST-Ericsson SA 2010
+ * Authors:
+ * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for
ST-Ericsson.
+ * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson.
+ * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson.
+ * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson.
+ * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson.
+ * License terms:  GNU General Public License (GPL), version 2
+ *
+ * Linux Bluetooth HCI H:4 Driver for ST-Ericsson STLC2690 BT/FM controller.
+ */
+
+#ifndef _STLC2690_CHIP_H_
+#define _STLC2690_CHIP_H_
+
+/* BT VS Store In FS command */
+#define STLC2690_BT_OP_VS_STORE_IN_FS			0xFC22
+struct bt_vs_store_in_fs_cmd {
+	__u8	user_id;
+	__u8	len;
+	__u8	data; /* Really a data array of variable size */
+} __attribute__((packed));
+
+/* BT VS Write File Block command */
+#define STLC2690_BT_OP_VS_WRITE_FILE_BLOCK		0xFC2E
+struct bt_vs_write_file_block_cmd {
+	__u8	id;
+	__u8	data; /* Really a data array of variable size */
+} __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.6.3.3

^ permalink raw reply related

* [PATCH 3/9] MFD: Add chip handler for the ST-Ericsson CG2900.
From: Par-Gunnar Hjalmdahl @ 2010-10-22 10:36 UTC (permalink / raw)
  To: linus.walleij, linux-bluetooth, linux-kernel

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@stericsson.com>
---
 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@stericsson.com) for
ST-Ericsson.
+ * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson.
+ * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson.
+ * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson.
+ * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson.
+ * License terms:  GNU General Public License (GPL), version 2
+ *
+ * Linux Bluetooth HCI H:4 Driver for ST-Ericsson CG2900 GPS/BT/FM controller.
+ */
+
+#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@stericsson.com) for
ST-Ericsson.
+ * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson.
+ * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson.
+ * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson.
+ * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson.
+ * License terms:  GNU General Public License (GPL), version 2
+ *
+ * Linux Bluetooth HCI H:4 Driver for ST-Ericsson CG2900 GPS/BT/FM controller.
+ */
+
+#ifndef _CG2900_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

^ permalink raw reply related

* [PATCH 2/9] mfd: Add char devices for the ST-Ericsson CG2900.
From: Par-Gunnar Hjalmdahl @ 2010-10-22 10:36 UTC (permalink / raw)
  To: linus.walleij, linux-bluetooth, linux-kernel

This patch adds char devices to the ST-Ericsson CG2900 driver.
The reason for this is to allow users of CG2900, such as GPS, to
be placed in user space.

Signed-off-by: Par-Gunnar Hjalmdahl <par-gunnar.p.hjalmdahl@stericsson.com>
---
 drivers/mfd/cg2900/Makefile              |    2 +
 drivers/mfd/cg2900/cg2900_char_devices.c |  745 ++++++++++++++++++++++++++++++
 2 files changed, 747 insertions(+), 0 deletions(-)
 create mode 100644 drivers/mfd/cg2900/cg2900_char_devices.c

diff --git a/drivers/mfd/cg2900/Makefile b/drivers/mfd/cg2900/Makefile
index a736101..0ac9bc6 100644
--- a/drivers/mfd/cg2900/Makefile
+++ b/drivers/mfd/cg2900/Makefile
@@ -5,3 +5,5 @@
 obj-$(CONFIG_MFD_CG2900)	+= cg2900_core.o
 export-objs			:= cg2900_core.o

+obj-$(CONFIG_MFD_CG2900)	+= cg2900_char_devices.o
+
diff --git a/drivers/mfd/cg2900/cg2900_char_devices.c
b/drivers/mfd/cg2900/cg2900_char_devices.c
new file mode 100644
index 0000000..6b8e0b0
--- /dev/null
+++ b/drivers/mfd/cg2900/cg2900_char_devices.c
@@ -0,0 +1,745 @@
+/*
+ * drivers/mfd/cg2900/cg2900_char_devices.c
+ *
+ * Copyright (C) ST-Ericsson SA 2010
+ * Authors:
+ * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for
ST-Ericsson.
+ * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson.
+ * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson.
+ * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson.
+ * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson.
+ * License terms:  GNU General Public License (GPL), version 2
+ *
+ * Linux Bluetooth HCI H:4 Driver for ST-Ericsson connectivity controller.
+ */
+
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/file.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/poll.h>
+#include <linux/sched.h>
+#include <linux/skbuff.h>
+#include <linux/mfd/cg2900.h>
+
+#include "cg2900_core.h"
+#include "cg2900_debug.h"
+
+#define NAME					"CharDev "
+
+/* Ioctls */
+#define CG2900_CHAR_DEV_IOCTL_RESET		_IOW('U', 210, int)
+#define CG2900_CHAR_DEV_IOCTL_CHECK4RESET	_IOR('U', 212, int)
+#define CG2900_CHAR_DEV_IOCTL_GET_REVISION	_IOR('U', 213, int)
+#define CG2900_CHAR_DEV_IOCTL_GET_SUB_VER	_IOR('U', 214, int)
+
+#define CG2900_CHAR_DEV_IOCTL_EVENT_RESET	1
+#define CG2900_CHAR_DEV_IOCTL_EVENT_CLOSED	2
+
+/* Internal type definitions */
+
+/**
+ * enum char_reset_state - Reset state.
+ * @CG2900_CHAR_IDLE:	Idle state.
+ * @CG2900_CHAR_RESET:	Reset state.
+ */
+enum char_reset_state {
+	CG2900_CHAR_IDLE,
+	CG2900_CHAR_RESET
+};
+
+/**
+ * struct char_dev_user - Stores device information.
+ * @dev:		Registered CG2900 Core device.
+ * @miscdev:	Registered device struct.
+ * @name:		Name of device.
+ * @rx_queue:		Data queue.
+ * @rx_wait_queue:	Wait queue.
+ * @reset_wait_queue:	Reset Wait queue.
+ * @reset_state:	Reset state.
+ * @read_mutex:		Read mutex.
+ * @write_mutex:	Write mutex.
+ * @list:		List header for inserting into device list.
+ */
+struct char_dev_user {
+	struct cg2900_device	*dev;
+	struct miscdevice	*miscdev;
+	char			*name;
+	struct sk_buff_head	rx_queue;
+	wait_queue_head_t	rx_wait_queue;
+	wait_queue_head_t	reset_wait_queue;
+	enum char_reset_state	reset_state;
+	struct mutex		read_mutex;
+	struct mutex		write_mutex;
+	struct list_head	list;
+};
+
+/**
+ * struct char_info - Stores all current users.
+ * @open_mutex:	Open mutex (used for both open and release).
+ * @dev_users:	List of char dev users.
+ */
+struct char_info {
+	struct mutex		open_mutex;
+	struct list_head	dev_users;
+};
+
+static struct char_info *char_info;
+
+/**
+ * char_dev_read_cb() - Handle data received from controller.
+ * @dev:	Device receiving data.
+ * @skb:	Buffer with data coming from controller.
+ *
+ * The char_dev_read_cb() function handles data received from
STE-CG2900 driver.
+ */
+static void char_dev_read_cb(struct cg2900_device *dev, struct sk_buff *skb)
+{
+	struct char_dev_user *char_dev = (struct char_dev_user *)dev->user_data;
+
+	CG2900_INFO("char_dev_read_cb");
+
+	if (!char_dev) {
+		CG2900_ERR("No char dev! Exiting");
+		kfree_skb(skb);
+		return;
+	}
+
+	skb_queue_tail(&char_dev->rx_queue, skb);
+
+	wake_up_interruptible(&char_dev->rx_wait_queue);
+}
+
+/**
+ * char_dev_reset_cb() - Handle reset from controller.
+ * @dev:	Device resetting.
+ *
+ * The char_dev_reset_cb() function handles reset from the CG2900 driver.
+ */
+static void char_dev_reset_cb(struct cg2900_device *dev)
+{
+	struct char_dev_user *char_dev = (struct char_dev_user *)dev->user_data;
+
+	CG2900_INFO("char_dev_reset_cb");
+
+	if (!char_dev) {
+		CG2900_ERR("char_dev == NULL");
+		return;
+	}
+
+	char_dev->reset_state = CG2900_CHAR_RESET;
+	/*
+	 * The device will be freed by CG2900 Core when this function is
+	 * finished.
+	 */
+	char_dev->dev = NULL;
+
+	wake_up_interruptible(&char_dev->rx_wait_queue);
+	wake_up_interruptible(&char_dev->reset_wait_queue);
+}
+
+static struct cg2900_callbacks char_cb = {
+	.read_cb = char_dev_read_cb,
+	.reset_cb = char_dev_reset_cb
+};
+
+/**
+ * char_dev_open() - Open char device.
+ * @inode:	Device driver information.
+ * @filp:	Pointer to the file struct.
+ *
+ * The char_dev_open() function opens the char device.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -EACCES if device was already registered to driver or if registration
+ *   failed.
+ */
+static int char_dev_open(struct inode *inode, struct file *filp)
+{
+	int err = 0;
+	int minor;
+	struct char_dev_user *dev = NULL;
+	struct char_dev_user *tmp;
+	struct list_head *cursor;
+
+	mutex_lock(&char_info->open_mutex);
+
+	minor = iminor(inode);
+
+	/* Find the device for this file */
+	list_for_each(cursor, &char_info->dev_users) {
+		tmp = list_entry(cursor, struct char_dev_user, list);
+		if (tmp->miscdev->minor == minor) {
+			dev = tmp;
+			break;
+		}
+	}
+	if (!dev) {
+		CG2900_ERR("Could not identify device in inode");
+		err = -EINVAL;
+		goto error_handling;
+	}
+
+	filp->private_data = dev;
+
+	CG2900_INFO("char_dev_open %s", dev->name);
+
+	if (dev->dev) {
+		CG2900_ERR("Device already registered to CG2900 Driver");
+		err = -EACCES;
+		goto error_handling;
+	}
+	/* First initiate wait queues for this device. */
+	init_waitqueue_head(&dev->rx_wait_queue);
+	init_waitqueue_head(&dev->reset_wait_queue);
+
+	dev->reset_state = CG2900_CHAR_IDLE;
+
+	/* Register to CG2900 Driver */
+	dev->dev = cg2900_register_user(dev->name, &char_cb);
+	if (dev->dev)
+		dev->dev->user_data = dev;
+	else {
+		CG2900_ERR("Couldn't register to CG2900 for H:4 channel %s",
+			   dev->name);
+		err = -EACCES;
+	}
+
+error_handling:
+	mutex_unlock(&char_info->open_mutex);
+	return err;
+}
+
+/**
+ * char_dev_release() - Release char device.
+ * @inode:	Device driver information.
+ * @filp:	Pointer to the file struct.
+ *
+ * The char_dev_release() function release the char device.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -EBADF if NULL pointer was supplied in private data.
+ */
+static int char_dev_release(struct inode *inode, struct file *filp)
+{
+	int err = 0;
+	struct char_dev_user *dev = (struct char_dev_user *)filp->private_data;
+
+	CG2900_INFO("char_dev_release");
+
+	if (!dev) {
+		CG2900_ERR("Calling with NULL pointer");
+		return -EBADF;
+	}
+
+	mutex_lock(&char_info->open_mutex);
+	mutex_lock(&dev->read_mutex);
+	mutex_lock(&dev->write_mutex);
+
+	if (dev->reset_state == CG2900_CHAR_IDLE)
+		cg2900_deregister_user(dev->dev);
+
+	dev->dev = NULL;
+	filp->private_data = NULL;
+	wake_up_interruptible(&dev->rx_wait_queue);
+	wake_up_interruptible(&dev->reset_wait_queue);
+
+	mutex_unlock(&dev->write_mutex);
+	mutex_unlock(&dev->read_mutex);
+	mutex_unlock(&char_info->open_mutex);
+
+	return err;
+}
+
+/**
+ * char_dev_read() - Queue and copy buffer to user.
+ * @filp:	Pointer to the file struct.
+ * @buf:	Received buffer.
+ * @count:	Size of buffer.
+ * @f_pos:	Position in buffer.
+ *
+ * The char_dev_read() function queues and copy the received buffer to
+ * the user space char device. If no data is available this function
will block.
+ *
+ * Returns:
+ *   Bytes successfully read (could be 0).
+ *   -EBADF if NULL pointer was supplied in private data.
+ *   -EFAULT if copy_to_user fails.
+ *   Error codes from wait_event_interruptible.
+ */
+static ssize_t char_dev_read(struct file *filp, char __user *buf, size_t count,
+			     loff_t *f_pos)
+{
+	struct char_dev_user *dev = (struct char_dev_user *)filp->private_data;
+	struct sk_buff *skb;
+	int bytes_to_copy;
+	int err = 0;
+
+	CG2900_INFO("char_dev_read");
+
+	if (!dev) {
+		CG2900_ERR("Calling with NULL pointer");
+		return -EBADF;
+	}
+	mutex_lock(&dev->read_mutex);
+
+	if (skb_queue_empty(&dev->rx_queue)) {
+		err = wait_event_interruptible(dev->rx_wait_queue,
+				(!(skb_queue_empty(&dev->rx_queue))) ||
+				(CG2900_CHAR_RESET == dev->reset_state) ||
+				(dev->dev == NULL));
+		if (err) {
+			CG2900_ERR("Failed to wait for event");
+			goto error_handling;
+		}
+	}
+
+	if (!dev->dev) {
+		CG2900_DBG("dev is empty - return with negative bytes");
+		err = -EBADF;
+		goto error_handling;
+	}
+
+	skb = skb_dequeue(&dev->rx_queue);
+	if (!skb) {
+		CG2900_DBG("skb queue is empty - return with zero bytes");
+		bytes_to_copy = 0;
+		goto finished;
+	}
+
+	bytes_to_copy = min(count, skb->len);
+
+	err = copy_to_user(buf, skb->data, bytes_to_copy);
+	if (err) {
+		skb_queue_head(&dev->rx_queue, skb);
+		err = -EFAULT;
+		goto error_handling;
+	}
+
+	skb_pull(skb, bytes_to_copy);
+
+	if (skb->len > 0)
+		skb_queue_head(&dev->rx_queue, skb);
+	else
+		kfree_skb(skb);
+
+	goto finished;
+
+error_handling:
+	mutex_unlock(&dev->read_mutex);
+	return (ssize_t)err;
+finished:
+	mutex_unlock(&dev->read_mutex);
+	return bytes_to_copy;
+}
+
+/**
+ * char_dev_write() - Copy buffer from user and write to CG2900 driver.
+ * @filp:	Pointer to the file struct.
+ * @buf:	Write buffer.
+ * @count:	Size of the buffer write.
+ * @f_pos:	Position of buffer.
+ *
+ * Returns:
+ *   Bytes successfully written (could be 0).
+ *   -EBADF if NULL pointer was supplied in private data.
+ *   -EFAULT if copy_from_user fails.
+ */
+static ssize_t char_dev_write(struct file *filp, const char __user *buf,
+			      size_t count, loff_t *f_pos)
+{
+	struct sk_buff *skb;
+	struct char_dev_user *dev = (struct char_dev_user *)filp->private_data;
+	int err = 0;
+
+	CG2900_INFO("char_dev_write");
+
+	if (!dev) {
+		CG2900_ERR("Calling with NULL pointer");
+		return -EBADF;
+	}
+	mutex_lock(&dev->write_mutex);
+
+	skb = cg2900_alloc_skb(count, GFP_ATOMIC);
+	if (!skb) {
+		CG2900_ERR("Couldn't allocate sk_buff with length %d", count);
+		goto error_handling;
+	}
+
+	if (copy_from_user(skb_put(skb, count), buf, count)) {
+		kfree_skb(skb);
+		err = -EFAULT;
+		goto error_handling;
+	}
+
+	err = cg2900_write(dev->dev, skb);
+	if (err) {
+		CG2900_ERR("cg2900_write failed (%d)", err);
+		kfree_skb(skb);
+		goto error_handling;
+	}
+
+	mutex_unlock(&dev->write_mutex);
+	return count;
+
+error_handling:
+	mutex_unlock(&dev->write_mutex);
+	return err;
+}
+
+/**
+ * char_dev_unlocked_ioctl() - Handle IOCTL call to the interface.
+ * @filp:	Pointer to the file struct.
+ * @cmd:	IOCTL command.
+ * @arg:	IOCTL argument.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -EBADF if NULL pointer was supplied in private data.
+ *   -EINVAL if supplied cmd is not supported.
+ *   For cmd CG2900_CHAR_DEV_IOCTL_CHECK4RESET 0x01 is returned if device is
+ *   reset and 0x02 is returned if device is closed.
+ */
+static long char_dev_unlocked_ioctl(struct file *filp, unsigned int cmd,
+				    unsigned long arg)
+{
+	struct char_dev_user *dev = (struct char_dev_user *)filp->private_data;
+	struct cg2900_rev_data rev_data;
+	int err = 0;
+
+	CG2900_INFO("char_dev_unlocked_ioctl cmd %d for %s", cmd, dev->name);
+	CG2900_DBG("DIR: %d, TYPE: %d, NR: %d, SIZE: %d",
+		     _IOC_DIR(cmd), _IOC_TYPE(cmd), _IOC_NR(cmd),
+		     _IOC_SIZE(cmd));
+
+	switch (cmd) {
+	case CG2900_CHAR_DEV_IOCTL_RESET:
+		if (!dev) {
+			err = -EBADF;
+			goto error_handling;
+		}
+		CG2900_INFO("ioctl reset command for device %s", dev->name);
+		err = cg2900_reset(dev->dev);
+		break;
+
+	case CG2900_CHAR_DEV_IOCTL_CHECK4RESET:
+		if (!dev) {
+			CG2900_INFO("ioctl check for reset command for device");
+			/* Return positive value if closed */
+			err = CG2900_CHAR_DEV_IOCTL_EVENT_CLOSED;
+		} else if (dev->reset_state == CG2900_CHAR_RESET) {
+			CG2900_INFO("ioctl check for reset command for device "
+				    "%s", dev->name);
+			/* Return positive value if reset */
+			err = CG2900_CHAR_DEV_IOCTL_EVENT_RESET;
+		}
+		break;
+
+	case CG2900_CHAR_DEV_IOCTL_GET_REVISION:
+		CG2900_INFO("ioctl check for local revision info");
+		if (cg2900_get_local_revision(&rev_data)) {
+			CG2900_DBG("Read revision data revision %d "
+				   "sub_version %d",
+				   rev_data.revision, rev_data.sub_version);
+			err = rev_data.revision;
+		} else {
+			CG2900_DBG("No revision data available");
+			err = -EIO;
+		}
+		break;
+
+	case CG2900_CHAR_DEV_IOCTL_GET_SUB_VER:
+		CG2900_INFO("ioctl check for local sub-version info");
+		if (cg2900_get_local_revision(&rev_data)) {
+			CG2900_DBG("Read revision data revision %d "
+				   "sub_version %d",
+				   rev_data.revision, rev_data.sub_version);
+			err = rev_data.sub_version;
+		} else {
+			CG2900_DBG("No revision data available");
+			err = -EIO;
+		}
+		break;
+
+	default:
+		CG2900_ERR("Unknown ioctl command %08X", cmd);
+		err = -EINVAL;
+		break;
+	};
+
+error_handling:
+	return err;
+}
+
+/**
+ * char_dev_poll() - Handle POLL call to the interface.
+ * @filp:	Pointer to the file struct.
+ * @wait:	Poll table supplied to caller.
+ *
+ * Returns:
+ *   Mask of current set POLL values
+ */
+static unsigned int char_dev_poll(struct file *filp, poll_table *wait)
+{
+	struct char_dev_user *dev = (struct char_dev_user *)filp->private_data;
+	unsigned int mask = 0;
+
+	if (!dev) {
+		CG2900_DBG("Device not open");
+		return POLLERR | POLLRDHUP;
+	}
+
+	poll_wait(filp, &dev->reset_wait_queue, wait);
+	poll_wait(filp, &dev->rx_wait_queue, wait);
+
+	if (!dev->dev)
+		mask |= POLLERR | POLLRDHUP;
+	else
+		mask |= POLLOUT; /* We can TX unless there is an error */
+
+	if (!(skb_queue_empty(&dev->rx_queue)))
+		mask |= POLLIN | POLLRDNORM;
+
+	if (CG2900_CHAR_RESET == dev->reset_state)
+		mask |= POLLPRI;
+
+	return mask;
+}
+
+/*
+ * struct char_dev_fops - Char devices file operations.
+ * @read:		Function that reads from the char device.
+ * @write:		Function that writes to the char device.
+ * @unlocked_ioctl:	Function that performs IO operations with
+ *			the char device.
+ * @poll:		Function that checks if there are possible operations
+ *			with the char device.
+ * @open:		Function that opens the char device.
+ * @release:		Function that release the char device.
+ */
+static const struct file_operations char_dev_fops = {
+	.read		= char_dev_read,
+	.write		= char_dev_write,
+	.unlocked_ioctl	= char_dev_unlocked_ioctl,
+	.poll		= char_dev_poll,
+	.open		= char_dev_open,
+	.release	= char_dev_release
+};
+
+/**
+ * setup_dev() - Set up the char device structure for device.
+ * @parent:	Parent device pointer.
+ * @name:	Name of registered device.
+ *
+ * The setup_dev() function sets up the char_dev structure for this device.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -EINVAL if NULL pointer has been supplied.
+ *   Error codes from cdev_add and device_create.
+ */
+static int setup_dev(struct device *parent, char *name)
+{
+	int err = 0;
+	struct char_dev_user *dev_usr;
+
+	CG2900_INFO(NAME "setup_dev");
+
+	dev_usr = kzalloc(sizeof(*dev_usr), GFP_KERNEL);
+	if (!dev_usr) {
+		CG2900_ERR("Couldn't allocate dev_usr");
+		return -ENOMEM;
+	}
+
+	/* Store device name */
+	dev_usr->name = name;
+
+	dev_usr->miscdev = kzalloc(sizeof(*(dev_usr->miscdev)),
+				       GFP_KERNEL);
+	if (!dev_usr->miscdev) {
+		CG2900_ERR("Couldn't allocate char_dev");
+		err = -ENOMEM;
+		goto err_free_usr;
+	}
+
+	/* Prepare miscdevice struct before registering the device */
+	dev_usr->miscdev->minor = MISC_DYNAMIC_MINOR;
+	dev_usr->miscdev->name = name;
+	dev_usr->miscdev->fops = &char_dev_fops;
+	dev_usr->miscdev->parent = parent;
+
+	err = misc_register(dev_usr->miscdev);
+	if (err) {
+		CG2900_ERR("Error %d registering misc dev!", err);
+		goto err_free_dev;
+	}
+
+	CG2900_INFO("Added char device %s with major 0x%X and minor 0x%X",
+		    name, MAJOR(dev_usr->miscdev->this_device->devt),
+		    MINOR(dev_usr->miscdev->this_device->devt));
+
+	mutex_init(&dev_usr->read_mutex);
+	mutex_init(&dev_usr->write_mutex);
+
+	skb_queue_head_init(&dev_usr->rx_queue);
+
+	list_add_tail(&dev_usr->list, &char_info->dev_users);
+	return 0;
+
+err_free_dev:
+	kfree(dev_usr->miscdev);
+	dev_usr->miscdev = NULL;
+err_free_usr:
+	kfree(dev_usr);
+	return err;
+}
+
+/**
+ * remove_dev() - Remove char device structure for device.
+ * @dev_usr:	Char device user.
+ *
+ * The remove_dev() function releases the char_dev structure for this device.
+ */
+static void remove_dev(struct char_dev_user *dev_usr)
+{
+	CG2900_INFO(NAME "remove_dev");
+
+	if (!dev_usr)
+		return;
+
+	skb_queue_purge(&dev_usr->rx_queue);
+
+	mutex_destroy(&dev_usr->read_mutex);
+	mutex_destroy(&dev_usr->write_mutex);
+
+	/* Remove device node in file system. */
+	misc_deregister(dev_usr->miscdev);
+	kfree(dev_usr->miscdev);
+	dev_usr->miscdev = NULL;
+
+	kfree(dev_usr);
+}
+
+/**
+ * cg2900_char_probe() - Initialize char device module.
+ * @pdev:	Platform device.
+ *
+ * Returns:
+ *   0 if success.
+ *   -ENOMEM if allocation fails.
+ *   -EACCES if device already have been initiated.
+ */
+static int __devinit cg2900_char_probe(struct platform_device *pdev)
+{
+	struct device *parent;
+
+	CG2900_INFO("cg2900_char_probe");
+
+	if (char_info) {
+		CG2900_ERR("Char devices already initiated");
+		return -EACCES;
+	}
+
+	parent = pdev->dev.parent;
+
+	/* Initialize private data. */
+	char_info = kzalloc(sizeof(*char_info), GFP_ATOMIC);
+	if (!char_info) {
+		CG2900_ERR("Could not alloc char_info struct.");
+		return -ENOMEM;
+	}
+
+	mutex_init(&char_info->open_mutex);
+	INIT_LIST_HEAD(&char_info->dev_users);
+
+	setup_dev(parent, CG2900_BT_CMD);
+	setup_dev(parent, CG2900_BT_ACL);
+	setup_dev(parent, CG2900_BT_EVT);
+	setup_dev(parent, CG2900_FM_RADIO);
+	setup_dev(parent, CG2900_GNSS);
+	setup_dev(parent, CG2900_DEBUG);
+	setup_dev(parent, CG2900_STE_TOOLS);
+	setup_dev(parent, CG2900_HCI_LOGGER);
+	setup_dev(parent, CG2900_US_CTRL);
+	setup_dev(parent, CG2900_BT_AUDIO);
+	setup_dev(parent, CG2900_FM_RADIO_AUDIO);
+	setup_dev(parent, CG2900_CORE);
+
+	return 0;
+}
+
+/**
+ * cg2900_char_remove() - Release the char device module.
+ * @pdev:	Platform device.
+ *
+ * Returns:
+ *   0 if success (always success).
+ */
+static int __devexit cg2900_char_remove(struct platform_device *pdev)
+{
+	struct list_head *cursor, *next;
+	struct char_dev_user *tmp;
+
+	CG2900_INFO("cg2900_char_remove");
+
+	if (!char_info)
+		return 0;
+
+	list_for_each_safe(cursor, next, &char_info->dev_users) {
+		tmp = list_entry(cursor, struct char_dev_user, list);
+		list_del(cursor);
+		remove_dev(tmp);
+	}
+
+	mutex_destroy(&char_info->open_mutex);
+
+	kfree(char_info);
+	char_info = NULL;
+	return 0;
+}
+
+static struct platform_driver cg2900_char_driver = {
+	.driver = {
+		.name	= "cg2900-chardev",
+		.owner	= THIS_MODULE,
+	},
+	.probe	= cg2900_char_probe,
+	.remove	= __devexit_p(cg2900_char_remove),
+};
+
+/**
+ * cg2900_char_init() - Initialize module.
+ *
+ * Registers platform driver.
+ */
+static int __init cg2900_char_init(void)
+{
+	CG2900_INFO("cg2900_char_init");
+	return platform_driver_register(&cg2900_char_driver);
+}
+
+/**
+ * cg2900_char_exit() - Remove module.
+ *
+ * Unregisters platform driver.
+ */
+static void __exit cg2900_char_exit(void)
+{
+	CG2900_INFO("cg2900_char_exit");
+	platform_driver_unregister(&cg2900_char_driver);
+}
+
+module_init(cg2900_char_init);
+module_exit(cg2900_char_exit);
+
+MODULE_AUTHOR("Henrik Possung ST-Ericsson");
+MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("ST-Ericsson CG2900 Char Devices Driver");
-- 
1.6.3.3

^ permalink raw reply related

* [PATCH 1/9] mfd: Add support for the ST-Ericsson CG2900.
From: Par-Gunnar Hjalmdahl @ 2010-10-22 10:35 UTC (permalink / raw)
  To: linus.walleij, linux-bluetooth, linux-kernel

This patch adds support for the ST-Ericsson CG2900 Connectivity
Combo controller.
This patch adds the central framework to be able to register CG2900 users,
transports, and chip handlers.

Signed-off-by: Par-Gunnar Hjalmdahl <par-gunnar.p.hjalmdahl@stericsson.com>
---
 drivers/mfd/Kconfig               |    8 +
 drivers/mfd/Makefile              |    3 +
 drivers/mfd/cg2900/Makefile       |    7 +
 drivers/mfd/cg2900/cg2900_core.c  | 2401 +++++++++++++++++++++++++++++++++++++
 drivers/mfd/cg2900/cg2900_core.h  |  303 +++++
 drivers/mfd/cg2900/cg2900_debug.h |   76 ++
 drivers/mfd/cg2900/hci_defines.h  |   81 ++
 include/linux/mfd/cg2900.h        |  187 +++
 8 files changed, 3066 insertions(+), 0 deletions(-)
 create mode 100644 drivers/mfd/cg2900/Makefile
 create mode 100644 drivers/mfd/cg2900/cg2900_core.c
 create mode 100644 drivers/mfd/cg2900/cg2900_core.h
 create mode 100644 drivers/mfd/cg2900/cg2900_debug.h
 create mode 100644 drivers/mfd/cg2900/hci_defines.h
 create mode 100644 include/linux/mfd/cg2900.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 6c6b9f0..3ee9c66 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -262,6 +262,14 @@ config MFD_TC6393XB
 	help
 	  Support for Toshiba Mobile IO Controller TC6393XB

+config MFD_CG2900
+	tristate "Support ST-Ericsson CG2900 main structure"
+	depends on NET
+	help
+	  Support for ST-Ericsson CG2900 Connectivity Combo controller main structure.
+	  Supports multiple functionalities muxed over a Bluetooth HCI H:4 interface.
+	  CG2900 support Bluetooth, FM radio, and GPS.
+
 config PMIC_DA903X
 	bool "Dialog Semiconductor DA9030/DA9034 PMIC Support"
 	depends on I2C=y
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 70b2699..acf1fcb 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -79,3 +79,6 @@ obj-$(CONFIG_MFD_JANZ_CMODIO)	+= janz-cmodio.o
 obj-$(CONFIG_MFD_JZ4740_ADC)	+= jz4740-adc.o
 obj-$(CONFIG_MFD_TPS6586X)	+= tps6586x.o
 obj-$(CONFIG_MFD_VX855)		+= vx855.o
+
+obj-y				+= cg2900/
+
diff --git a/drivers/mfd/cg2900/Makefile b/drivers/mfd/cg2900/Makefile
new file mode 100644
index 0000000..a736101
--- /dev/null
+++ b/drivers/mfd/cg2900/Makefile
@@ -0,0 +1,7 @@
+#
+# Makefile for ST-Ericsson CG2900 connectivity combo controller
+#
+
+obj-$(CONFIG_MFD_CG2900)	+= cg2900_core.o
+export-objs			:= cg2900_core.o
+
diff --git a/drivers/mfd/cg2900/cg2900_core.c b/drivers/mfd/cg2900/cg2900_core.c
new file mode 100644
index 0000000..a5951cb
--- /dev/null
+++ b/drivers/mfd/cg2900/cg2900_core.c
@@ -0,0 +1,2401 @@
+/*
+ * drivers/mfd/cg2900/cg2900_core.c
+ *
+ * Copyright (C) ST-Ericsson SA 2010
+ * Authors:
+ * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for
ST-Ericsson.
+ * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson.
+ * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson.
+ * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson.
+ * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson.
+ * License terms:  GNU General Public License (GPL), version 2
+ *
+ * Linux Bluetooth HCI H:4 Driver for ST-Ericsson CG2900 GPS/BT/FM controller.
+ */
+
+#include <asm/byteorder.h>
+#include <linux/firmware.h>
+#include <linux/fs.h>
+#include <linux/gfp.h>
+#include <linux/init.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/poll.h>
+#include <linux/sched.h>
+#include <linux/skbuff.h>
+#include <linux/stat.h>
+#include <linux/time.h>
+#include <linux/timer.h>
+#include <linux/types.h>
+#include <linux/wait.h>
+#include <linux/workqueue.h>
+#include <linux/mfd/cg2900.h>
+#include <linux/mfd/core.h>
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci.h>
+
+#include "cg2900_core.h"
+#include "cg2900_debug.h"
+#include "hci_defines.h"
+
+/* Device names */
+#define CG2900_CDEV_NAME		"cg2900_core_test"
+#define CG2900_CLASS_NAME		"cg2900_class"
+#define CG2900_DEVICE_NAME		"cg2900_driver"
+#define CORE_WQ_NAME			"cg2900_core_wq"
+
+#define SET_MAIN_STATE(__core_new_state) \
+	CG2900_SET_STATE("main_state", core_info->main_state, \
+			 __core_new_state)
+#define SET_BOOT_STATE(__core_new_state) \
+	CG2900_SET_STATE("boot_state", core_info->boot_state, __core_new_state)
+#define SET_TRANSPORT_STATE(__core_new_state) \
+	CG2900_SET_STATE("transport_state", core_info->transport_state, \
+			 __core_new_state)
+
+#define LOGGER_DIRECTION_TX		0
+#define LOGGER_DIRECTION_RX		1
+
+/* Number of bytes to reserve at start of sk_buffer when receiving packet */
+#define RX_SKB_RESERVE			8
+
+/*
+ * Timeout values
+ */
+#define CHIP_STARTUP_TIMEOUT		(15000)	/* ms */
+#define CHIP_SHUTDOWN_TIMEOUT		(15000)	/* ms */
+#define LINE_TOGGLE_DETECT_TIMEOUT	(50)	/* ms */
+#define CHIP_READY_TIMEOUT		(100)	/* ms */
+#define REVISION_READOUT_TIMEOUT	(500)	/* ms */
+
+/*
+ * We can have up to 32 char devs with current bit mask and we also have
+ * the parent device here in the transport so that is 33 devices in total.
+ */
+#define MAX_NBR_OF_DEVS			33
+
+/* Default H4 channels which may change depending on connected controller */
+#define HCI_FM_RADIO_H4_CHANNEL		0x08
+#define HCI_GNSS_H4_CHANNEL		0x09
+
+/*
+ *	Internal type definitions
+ */
+
+/**
+ * enum main_state - Main-state for CG2900 Core.
+ * @CORE_INITIALIZING:	CG2900 Core initializing.
+ * @CORE_IDLE:		No user registered to CG2900 Core.
+ * @CORE_BOOTING:	CG2900 Core booting after first user is registered.
+ * @CORE_CLOSING:	CG2900 Core closing after last user has deregistered.
+ * @CORE_RESETING:	CG2900 Core reset requested.
+ * @CORE_ACTIVE:	CG2900 Core up and running with at least one user.
+ */
+enum main_state {
+	CORE_INITIALIZING,
+	CORE_IDLE,
+	CORE_BOOTING,
+	CORE_CLOSING,
+	CORE_RESETING,
+	CORE_ACTIVE
+};
+
+/**
+ * enum boot_state - BOOT-state for CG2900 Core.
+ * @BOOT_NOT_STARTED:				Boot has not yet started.
+ * @BOOT_READ_LOCAL_VERSION_INFORMATION:	ReadLocalVersionInformation
+ *						command has been sent.
+ * @BOOT_READY:					CG2900 Core boot is ready.
+ * @BOOT_FAILED:				CG2900 Core boot failed.
+ */
+enum boot_state {
+	BOOT_NOT_STARTED,
+	BOOT_READ_LOCAL_VERSION_INFORMATION,
+	BOOT_READY,
+	BOOT_FAILED
+};
+
+/**
+ * enum transport_state - State for the CG2900 transport.
+ * @TRANS_INITIALIZING:	Transport initializing.
+ * @TRANS_OPENED:	Transport is opened (data can be sent).
+ * @TRANS_CLOSED:	Transport is closed (data cannot be sent).
+ */
+enum transport_state {
+	TRANS_INITIALIZING,
+	TRANS_OPENED,
+	TRANS_CLOSED
+};
+
+/**
+ * struct cg2900_users - Stores all current users of CG2900 Core.
+ * @bt_cmd:		BT command channel user.
+ * @bt_acl:		BT ACL channel user.
+ * @bt_evt:		BT event channel user.
+ * @fm_radio:		FM radio channel user.
+ * @gnss GNSS:		GNSS channel user.
+ * @debug Debug:	Internal debug channel user.
+ * @ste_tools:		ST-E tools channel user.
+ * @hci_logger:		HCI logger channel user.
+ * @us_ctrl:		User space control channel user.
+ * @bt_audio:		BT audio command channel user.
+ * @fm_radio_audio:	FM audio command channel user.
+ * @core:		Core command channel user.
+ * @nbr_of_users:	Number of users currently registered (not including
+ *			the HCI logger).
+ */
+struct cg2900_users {
+	struct cg2900_device	*bt_cmd;
+	struct cg2900_device	*bt_acl;
+	struct cg2900_device	*bt_evt;
+	struct cg2900_device	*fm_radio;
+	struct cg2900_device	*gnss;
+	struct cg2900_device	*debug;
+	struct cg2900_device	*ste_tools;
+	struct cg2900_device	*hci_logger;
+	struct cg2900_device	*us_ctrl;
+	struct cg2900_device	*bt_audio;
+	struct cg2900_device	*fm_radio_audio;
+	struct cg2900_device	*core;
+	unsigned int		nbr_of_users;
+};
+
+/**
+  * struct local_chip_info - Stores local controller info.
+  * @version_set:		true if version data is valid.
+  * @hci_version:		HCI version of local controller.
+  * @hci_revision:		HCI revision of local controller.
+  * @lmp_pal_version:		LMP/PAL version of local controller.
+  * @manufacturer:		Manufacturer of local controller.
+  * @lmp_pal_subversion:	LMP/PAL sub-version of local controller.
+  *
+  * According to Bluetooth HCI Read Local Version Information command.
+  */
+struct local_chip_info {
+	bool	version_set;
+	u8	hci_version;
+	u16	hci_revision;
+	u8	lmp_pal_version;
+	u16	manufacturer;
+	u16	lmp_pal_subversion;
+};
+
+/**
+ * struct chip_handler_item - Structure to store chip handler cb.
+ * @list:	list_head struct.
+ * @cb:		Chip handler callback struct.
+ */
+struct chip_handler_item {
+	struct list_head		list;
+	struct cg2900_id_callbacks	cb;
+};
+
+/**
+ * struct cg2900_work_struct - Work structure for CG2900 Core module.
+ * @work:	Work structure.
+ * @data:	Pointer to private data.
+ *
+ * This structure is used to pack work for work queue.
+ */
+struct cg2900_work_struct{
+	struct work_struct	work;
+	void			*data;
+};
+
+/**
+ * struct test_char_dev_info - Stores device information.
+ * @test_miscdev:	Registered Misc Device.
+ * @rx_queue:		RX data queue.
+ */
+struct test_char_dev_info {
+	struct miscdevice	test_miscdev;
+	struct sk_buff_head	rx_queue;
+};
+
+/**
+ * struct trans_info - Stores transport information.
+ * @dev:	Transport device.
+ * @cb:		Transport cb.
+ */
+struct trans_info {
+	struct cg2900_trans_dev		dev;
+	struct cg2900_trans_callbacks	cb;
+};
+
+/**
+ * struct core_info - Main info structure for CG2900 Core.
+ * @users:		Stores all users of CG2900 Core.
+ * @local_chip_info:	Stores information of local controller.
+ * @main_state:		Current Main-state of CG2900 Core.
+ * @boot_state:		Current BOOT-state of CG2900 Core.
+ * @transport_state:	Current TRANSPORT-state of CG2900 Core.
+ * @wq:			CG2900 Core workqueue.
+ * @hci_logger_config:	Stores HCI logger configuration.
+ * @chip_dev:		Device structure for chip driver.
+ * @h4_channels:	HCI H:4 channel used by this device.
+ * @test_char_dev:	Stores information of test char dev.
+ * @trans_info:		Stores information about current transport.
+ * @dev:		Device structure for STE Connectivity driver.
+ */
+struct core_info {
+	struct cg2900_users		users;
+	struct local_chip_info		local_chip_info;
+	enum main_state			main_state;
+	enum boot_state			boot_state;
+	enum transport_state		transport_state;
+	struct workqueue_struct		*wq;
+	struct cg2900_hci_logger_config	hci_logger_config;
+	struct cg2900_chip_dev		chip_dev;
+	struct cg2900_h4_channels	h4_channels;
+	struct test_char_dev_info	*test_char_dev;
+	struct trans_info		*trans_info;
+	struct device			*dev;
+};
+
+/* core_info - Main information object for CG2900 Core. */
+static struct core_info *core_info;
+
+/* Module parameters */
+int cg2900_debug_level = CG2900_DEFAULT_DEBUG_LEVEL;
+EXPORT_SYMBOL(cg2900_debug_level);
+
+u8 bd_address[] = {0x00, 0xBE, 0xAD, 0xDE, 0x80, 0x00};
+EXPORT_SYMBOL(bd_address);
+int bd_addr_count = BT_BDADDR_SIZE;
+
+/* Setting default values to ST-E CG2900 */
+int default_manufacturer = 0x30;
+EXPORT_SYMBOL(default_manufacturer);
+int default_hci_revision = 0x0700;
+EXPORT_SYMBOL(default_hci_revision);
+int default_sub_version = 0x0011;
+EXPORT_SYMBOL(default_sub_version);
+
+static int sleep_timeout_ms = 100;
+
+/*
+ * chip_handlers - List of the register handlers for different chips.
+ */
+LIST_HEAD(chip_handlers);
+
+/*
+ * main_wait_queue - Main Wait Queue in CG2900 Core.
+ */
+static DECLARE_WAIT_QUEUE_HEAD(main_wait_queue);
+
+/*
+ * main_wait_queue - Char device Wait Queue in CG2900 Core.
+ */
+static DECLARE_WAIT_QUEUE_HEAD(char_wait_queue);
+
+/**
+ * free_user_dev - Frees user device and also sets it to NULL to inform caller.
+ * @dev:	Pointer to user device.
+ */
+static void free_user_dev(struct cg2900_device **dev)
+{
+	if (*dev) {
+		kfree((*dev)->cb);
+		kfree(*dev);
+		*dev = NULL;
+	}
+}
+
+/**
+ * handle_reset_of_user - Calls the reset callback and frees the device.
+ * @dev:	Pointer to CG2900 device.
+ */
+static void handle_reset_of_user(struct cg2900_device **dev)
+{
+	if (*dev) {
+		if ((*dev)->cb->reset_cb)
+			(*dev)->cb->reset_cb((*dev));
+		free_user_dev(dev);
+	}
+}
+
+/**
+ * transmit_skb_to_chip() - Transmit buffer to the transport.
+ * @skb:	Data packet.
+ * @use_logger:	True if HCI logger shall be used, false otherwise.
+ *
+ * The transmit_skb_to_chip() function transmit buffer to the transport.
+ * If enabled, copy the transmitted data to the HCI logger as well.
+ */
+static void transmit_skb_to_chip(struct sk_buff *skb, bool use_logger)
+{
+	int err;
+	struct sk_buff *skb_log;
+	struct trans_info *trans_info = core_info->trans_info;
+	struct cg2900_device *logger;
+
+	CG2900_DBG_DATA("transmit_skb_to_chip %d bytes. First byte 0x%02X",
+			skb->len, *(skb->data));
+
+	if (TRANS_CLOSED == core_info->transport_state) {
+		CG2900_ERR("Trying to write on a closed channel");
+		kfree_skb(skb);
+		return;
+	}
+
+	/*
+	 * If HCI logging is enabled for this channel, copy the data to
+	 * the HCI logging output.
+	 */
+	logger = core_info->users.hci_logger;
+	if (!use_logger || !logger)
+		goto transmit;
+
+	/*
+	 * Alloc a new sk_buff and copy the data into it. Then send it to
+	 * the HCI logger.
+	 */
+	skb_log = alloc_skb(skb->len + 1, GFP_ATOMIC);
+	if (!skb_log) {
+		CG2900_ERR("Couldn't allocate skb_log");
+		goto transmit;
+	}
+
+	memcpy(skb_put(skb_log, skb->len), skb->data, skb->len);
+	skb_log->data[0] = (u8) LOGGER_DIRECTION_TX;
+
+	if (logger->cb->read_cb)
+		logger->cb->read_cb(logger, skb_log);
+
+transmit:
+	if (trans_info && trans_info->cb.write) {
+		err = trans_info->cb.write(&trans_info->dev, skb);
+		if (err)
+			CG2900_ERR("Transport write failed (%d)", err);
+	} else {
+		CG2900_ERR("No way to write to chip");
+		err = -EPERM;
+	}
+
+	if (err)
+		kfree_skb(skb);
+}
+
+/**
+ * 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 allocates sk_buffer, copy supplied
+ * data to it, and send the sk_buffer to the transport.
+ */
+static void create_and_send_bt_cmd(void *data, int length)
+{
+	struct sk_buff *skb;
+
+	skb = cg2900_alloc_skb(length, GFP_ATOMIC);
+	if (!skb) {
+		CG2900_ERR("Couldn't allocate sk_buff with length %d",
+			     length);
+		return;
+	}
+
+	memcpy(skb_put(skb, length), data, length);
+	skb_push(skb, CG2900_SKB_RESERVE);
+	skb->data[0] = HCI_BT_CMD_H4_CHANNEL;
+
+	transmit_skb_to_chip(skb, core_info->hci_logger_config.bt_cmd_enable);
+}
+
+/**
+ * chip_not_detected() - Called when it is not possible to detect the chip.
+ *
+ * This function sets chip information to default values if it is not possible
+ * to read out information from the chip. This is common when running module
+ * tests.
+ */
+static void chip_not_detected(void)
+{
+	struct list_head *cursor;
+	struct chip_handler_item *tmp;
+
+	CG2900_ERR("Could not read out revision from the chip. This is "
+		   "typical when running stubbed CG2900.\n"
+		   "Switching to default value:\n"
+		   "\tman 0x%04X\n"
+		   "\trev 0x%04X\n"
+		   "\tsub 0x%04X",
+		   default_manufacturer,
+		   default_hci_revision,
+		   default_sub_version);
+
+	core_info->chip_dev.chip.manufacturer = default_manufacturer;
+	core_info->chip_dev.chip.hci_revision = default_hci_revision;
+	core_info->chip_dev.chip.hci_sub_version = default_sub_version;
+
+	memset(&(core_info->chip_dev.cb), 0, sizeof(core_info->chip_dev.cb));
+
+	/* Find the handler for our default chip */
+	list_for_each(cursor, &chip_handlers) {
+		tmp = list_entry(cursor, struct chip_handler_item, list);
+		if (tmp->cb.check_chip_support(&(core_info->chip_dev))) {
+			CG2900_INFO("Chip handler found");
+			SET_BOOT_STATE(BOOT_READY);
+			break;
+		}
+	}
+}
+
+/**
+ * enable_hci_logger() - Enable HCI logger for each device.
+ * @skb:	Received sk buffer.
+ *
+ * The enable_hci_logger() change HCI logger configuration for all registered
+ * devices.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -EACCES if bad structure was supplied.
+ */
+static int enable_hci_logger(struct sk_buff *skb)
+{
+	struct cg2900_users *users;
+	struct cg2900_hci_logger_config	*config;
+
+	if (skb->len != sizeof(*config)) {
+		CG2900_ERR("Trying to configure HCI logger with bad structure");
+		return -EACCES;
+	}
+
+	users = &(core_info->users);
+	config = &(core_info->hci_logger_config);
+
+	/* First store the logger config */
+	memcpy(config, skb->data, sizeof(*config));
+
+	/* Then go through all devices and set the right settings */
+	if (users->bt_cmd)
+		users->bt_cmd->logger_enabled = config->bt_cmd_enable;
+	if (users->bt_audio)
+		users->bt_audio->logger_enabled = config->bt_audio_enable;
+	if (users->bt_acl)
+		users->bt_acl->logger_enabled = config->bt_acl_enable;
+	if (users->bt_evt)
+		users->bt_evt->logger_enabled = config->bt_evt_enable;
+	if (users->fm_radio)
+		users->fm_radio->logger_enabled = config->fm_radio_enable;
+	if (users->fm_radio_audio)
+		users->fm_radio_audio->logger_enabled =
+				config->fm_radio_audio_enable;
+	if (users->gnss)
+		users->gnss->logger_enabled = config->gnss_enable;
+
+	kfree_skb(skb);
+	return 0;
+}
+
+/**
+ * find_bt_audio_user() - Check if data packet is an audio related packet.
+ * @h4_channel:	H4 channel.
+ * @dev:	Stored CG2900 device.
+ * @skb:	skb with received packet.
+ * Returns:
+ *   0 - if no error occurred.
+ *   -ENXIO - if cg2900_device not found.
+ */
+static int find_bt_audio_user(int h4_channel, struct cg2900_device **dev,
+				  const struct sk_buff * const skb)
+{
+	if (core_info->chip_dev.cb.is_bt_audio_user &&
+	    core_info->chip_dev.cb.is_bt_audio_user(h4_channel, skb)) {
+		*dev = core_info->users.bt_audio;
+		if (!(*dev)) {
+			CG2900_ERR("H:4 channel not registered in core_info: "
+				     "0x%X", h4_channel);
+			return -ENXIO;
+		}
+	}
+	return 0;
+}
+
+/**
+ * find_fm_audio_user() - Check if data packet is an audio related packet.
+ * @h4_channel:	H4 channel.
+ * @dev:	Stored CG2900 device.
+ * @skb:	skb with received packet.
+ * Returns:
+ *   0 if no error occurred.
+ *   -ENXIO if cg2900_device not found.
+ */
+static int find_fm_audio_user(int h4_channel, struct cg2900_device **dev,
+				  const struct sk_buff * const skb)
+{
+	if (core_info->chip_dev.cb.is_fm_audio_user &&
+	    core_info->chip_dev.cb.is_fm_audio_user(h4_channel, skb)) {
+		*dev = core_info->users.fm_radio_audio;
+		if (!(*dev)) {
+			CG2900_ERR("H:4 channel not registered in core_info: "
+				     "0x%X", h4_channel);
+			return -ENXIO;
+		}
+	}
+	return 0;
+}
+
+/**
+ * find_h4_user() - Get H4 user based on supplied H4 channel.
+ * @h4_channel:	H4 channel.
+ * @dev:	Stored CG2900 device.
+ * @skb:	(optional) skb with received packet. Set to NULL if NA.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -EINVAL if bad channel is supplied or no user was found.
+ *   -ENXIO if channel is audio channel but not registered with CG2900.
+ */
+static int find_h4_user(int h4_channel, struct cg2900_device **dev,
+			const struct sk_buff * const skb)
+{
+	int err = 0;
+	struct cg2900_users *users = &(core_info->users);
+	struct cg2900_h4_channels *chan = &(core_info->h4_channels);
+
+	if (h4_channel == chan->bt_cmd_channel) {
+		*dev = users->bt_cmd;
+	} else if (h4_channel == chan->bt_acl_channel) {
+		*dev = users->bt_acl;
+	} else if (h4_channel == chan->bt_evt_channel) {
+		*dev = users->bt_evt;
+		/* Check if it's event generated by previously sent audio user
+		 * command. If so then that event should be dispatched to audio
+		 * user*/
+		err = find_bt_audio_user(h4_channel, dev, skb);
+	} else if (h4_channel == chan->gnss_channel) {
+		*dev = users->gnss;
+	} else if (h4_channel == chan->fm_radio_channel) {
+		*dev = users->fm_radio;
+		/* Check if it's an event generated by previously sent audio
+		 * user command. If so then that event should be dispatched to
+		 * audio user */
+		err = find_fm_audio_user(h4_channel, dev, skb);
+	} else if (h4_channel == chan->debug_channel) {
+		*dev = users->debug;
+	} else if (h4_channel == chan->ste_tools_channel) {
+		*dev = users->ste_tools;
+	} else if (h4_channel == chan->hci_logger_channel) {
+		*dev = users->hci_logger;
+	} else if (h4_channel == chan->us_ctrl_channel) {
+		*dev = users->us_ctrl;
+	} else if (h4_channel == chan->core_channel) {
+		*dev = users->core;
+	} else {
+		*dev = NULL;
+		CG2900_ERR("Bad H:4 channel supplied: 0x%X", h4_channel);
+		return -EINVAL;
+	}
+
+	return err;
+}
+
+/**
+ * add_h4_user() - Add H4 user to user storage based on supplied H4 channel.
+ * @dev:	Stored CG2900 device.
+ * @name:	Device name to identify different devices that are using
+ *		the same H4 channel.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -EINVAL if NULL pointer or bad channel is supplied.
+ *   -EBUSY if there already is a user for supplied channel.
+ */
+static int add_h4_user(struct cg2900_device *dev, const char * const name)
+{
+	int err = 0;
+	struct cg2900_users *users = &(core_info->users);
+	struct cg2900_hci_logger_config	*config =
+			&(core_info->hci_logger_config);
+	struct cg2900_h4_channels *chan = &(core_info->h4_channels);
+
+	if (!dev) {
+		CG2900_ERR("NULL device supplied");
+		return -EINVAL;
+	}
+
+	if (dev->h4_channel == chan->bt_cmd_channel) {
+		if (!users->bt_cmd &&
+		    0 == strncmp(name, CG2900_BT_CMD, CG2900_MAX_NAME_SIZE)) {
+			users->bt_cmd = dev;
+			users->bt_cmd->logger_enabled = config->bt_cmd_enable;
+			(users->nbr_of_users)++;
+		} else if (!users->bt_audio &&
+			   0 == strncmp(name, CG2900_BT_AUDIO,
+					CG2900_MAX_NAME_SIZE)) {
+			users->bt_audio = dev;
+			users->bt_audio->logger_enabled =
+					config->bt_audio_enable;
+			(users->nbr_of_users)++;
+		} else {
+			err = -EBUSY;
+			CG2900_ERR("name %s bt_cmd 0x%X  bt_audio 0x%X",
+				     name, (int)users->bt_cmd,
+				     (int)users->bt_audio);
+		}
+	} else if (dev->h4_channel == chan->bt_acl_channel) {
+		if (!users->bt_acl) {
+			users->bt_acl = dev;
+			users->bt_acl->logger_enabled = config->bt_acl_enable;
+			(users->nbr_of_users)++;
+		} else {
+			err = -EBUSY;
+		}
+	} else if (dev->h4_channel == chan->bt_evt_channel) {
+		if (!users->bt_evt) {
+			users->bt_evt = dev;
+			users->bt_evt->logger_enabled = config->bt_evt_enable;
+			(users->nbr_of_users)++;
+		} else {
+			err = -EBUSY;
+		}
+	} else if (dev->h4_channel == chan->gnss_channel) {
+		if (!users->gnss) {
+			users->gnss = dev;
+			users->gnss->logger_enabled = config->gnss_enable;
+			(users->nbr_of_users)++;
+		} else {
+			err = -EBUSY;
+		}
+	} else if (dev->h4_channel == chan->fm_radio_channel) {
+		if (!users->fm_radio &&
+		    0 == strncmp(name, CG2900_FM_RADIO,
+				 CG2900_MAX_NAME_SIZE)) {
+			users->fm_radio = dev;
+			users->fm_radio->logger_enabled =
+					config->fm_radio_enable;
+			(users->nbr_of_users)++;
+		} else if (!users->fm_radio_audio &&
+			   0 == strncmp(name, CG2900_FM_RADIO_AUDIO,
+					CG2900_MAX_NAME_SIZE)) {
+			users->fm_radio_audio = dev;
+			users->fm_radio_audio->logger_enabled =
+					config->fm_radio_audio_enable;
+			(users->nbr_of_users)++;
+		} else {
+			err = -EBUSY;
+		}
+	} else if (dev->h4_channel == chan->debug_channel) {
+		if (!users->debug)
+			users->debug = dev;
+		else
+			err = -EBUSY;
+	} else if (dev->h4_channel == chan->ste_tools_channel) {
+		if (!users->ste_tools)
+			users->ste_tools = dev;
+		else
+			err = -EBUSY;
+	} else if (dev->h4_channel == chan->hci_logger_channel) {
+		if (!users->hci_logger)
+			users->hci_logger = dev;
+		else
+			err = -EBUSY;
+	} else if (dev->h4_channel == chan->us_ctrl_channel) {
+		if (!users->us_ctrl)
+			users->us_ctrl = dev;
+		else
+			err = -EBUSY;
+	} else if (dev->h4_channel == chan->core_channel) {
+		if (!users->core) {
+			(users->nbr_of_users)++;
+			users->core = dev;
+		} else {
+			err = -EBUSY;
+		}
+	} else {
+		err = -EINVAL;
+		CG2900_ERR("Bad H:4 channel supplied: 0x%X", dev->h4_channel);
+	}
+
+	if (err)
+		CG2900_ERR("H:4 channel 0x%X, not registered (%d)",
+			   dev->h4_channel, err);
+
+	return err;
+}
+
+/**
+ * remove_h4_user() - Remove H4 user from user storage.
+ * @dev:	Stored CG2900 device.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -EINVAL if NULL pointer is supplied, bad channel is supplied, or if there
+ *   is no user for supplied channel.
+ */
+static int remove_h4_user(struct cg2900_device **dev)
+{
+	int err = 0;
+	struct cg2900_users *users = &(core_info->users);
+	struct cg2900_h4_channels *chan = &(core_info->h4_channels);
+	struct cg2900_chip_callbacks *cb = &(core_info->chip_dev.cb);
+
+	if (!dev || !(*dev)) {
+		CG2900_ERR("NULL device supplied");
+		return -EINVAL;
+	}
+
+	if ((*dev)->h4_channel == chan->bt_cmd_channel) {
+		CG2900_DBG("bt_cmd 0x%X bt_audio 0x%X dev 0x%X",
+			     (int)users->bt_cmd,
+			     (int)users->bt_audio, (int)*dev);
+
+		if (*dev == users->bt_cmd) {
+			users->bt_cmd = NULL;
+			(users->nbr_of_users)--;
+		} else if (*dev == users->bt_audio) {
+			users->bt_audio = NULL;
+			(users->nbr_of_users)--;
+		} else
+			err = -EINVAL;
+
+		CG2900_DBG("bt_cmd 0x%X bt_audio 0x%X dev 0x%X",
+			     (int)users->bt_cmd,
+			     (int)users->bt_audio, (int)*dev);
+
+		/*
+		 * If both BT Command channel users are de-registered we
+		 * inform the chip handler.
+		 */
+		if (!users->bt_cmd && !users->bt_audio &&
+		    cb->last_bt_user_removed)
+			cb->last_bt_user_removed(&(core_info->chip_dev));
+	} else if ((*dev)->h4_channel == chan->bt_acl_channel) {
+		if (*dev == users->bt_acl) {
+			users->bt_acl = NULL;
+			(users->nbr_of_users)--;
+		} else
+			err = -EINVAL;
+	} else if ((*dev)->h4_channel == chan->bt_evt_channel) {
+		if (*dev == users->bt_evt) {
+			users->bt_evt = NULL;
+			(users->nbr_of_users)--;
+		} else
+			err = -EINVAL;
+	} else if ((*dev)->h4_channel == chan->gnss_channel) {
+		if (*dev == users->gnss) {
+			users->gnss = NULL;
+			(users->nbr_of_users)--;
+		} else
+			err = -EINVAL;
+
+		/*
+		 * If the GNSS channel user is de-registered we inform
+		 * the chip handler.
+		 */
+		if (users->gnss == NULL && cb->last_gnss_user_removed)
+			cb->last_gnss_user_removed(&(core_info->chip_dev));
+	} else if ((*dev)->h4_channel == chan->fm_radio_channel) {
+		if (*dev == users->fm_radio) {
+			users->fm_radio = NULL;
+			(users->nbr_of_users)--;
+		} else if (*dev == users->fm_radio_audio) {
+			users->fm_radio_audio = NULL;
+			(users->nbr_of_users)--;
+		} else
+			err = -EINVAL;
+
+		/*
+		 * If both FM Radio channel users are de-registered we inform
+		 * the chip handler.
+		 */
+		if (!users->fm_radio && !users->fm_radio_audio &&
+		    cb->last_fm_user_removed)
+			cb->last_fm_user_removed(&(core_info->chip_dev));
+	} else if ((*dev)->h4_channel == chan->debug_channel) {
+		if (*dev == users->debug)
+			users->debug = NULL;
+		else
+			err = -EINVAL;
+	} else if ((*dev)->h4_channel == chan->ste_tools_channel) {
+		if (*dev == users->ste_tools)
+			users->ste_tools = NULL;
+		else
+			err = -EINVAL;
+	} else if ((*dev)->h4_channel == chan->hci_logger_channel) {
+		if (*dev == users->hci_logger)
+			users->hci_logger = NULL;
+		else
+			err = -EINVAL;
+	} else if ((*dev)->h4_channel == chan->us_ctrl_channel) {
+		if (*dev == users->us_ctrl)
+			users->us_ctrl = NULL;
+		else
+			err = -EINVAL;
+	} else if ((*dev)->h4_channel == chan->core_channel) {
+		if (*dev == users->core) {
+			users->core = NULL;
+			(users->nbr_of_users)--;
+		} else
+			err = -EINVAL;
+	} else {
+		CG2900_ERR("Bad H:4 channel supplied: 0x%X",
+			     (*dev)->h4_channel);
+		return -EINVAL;
+	}
+
+	if (err)
+		CG2900_ERR("Trying to remove device that was not registered");
+
+	/*
+	 * Free the device even if there is an error with the device.
+	 * Also set to NULL to inform caller about the free.
+	 */
+	free_user_dev(dev);
+
+	return err;
+}
+
+/**
+ * chip_startup() - Start the connectivity controller and download
patches and settings.
+ */
+static void chip_startup(void)
+{
+	struct hci_command_hdr cmd;
+
+	CG2900_INFO("chip_startup");
+
+	SET_MAIN_STATE(CORE_BOOTING);
+	SET_BOOT_STATE(BOOT_NOT_STARTED);
+
+	/*
+	 * Transmit HCI reset command to ensure the chip is using
+	 * the correct transport
+	 */
+	cmd.opcode = cpu_to_le16(HCI_OP_RESET);
+	cmd.plen = 0; /* No parameters for HCI reset */
+	create_and_send_bt_cmd(&cmd, sizeof(cmd));
+}
+
+/**
+ * chip_shutdown() - Reset and power the chip off.
+ */
+static void chip_shutdown(void)
+{
+	int err = 0;
+	struct trans_info *trans_info = core_info->trans_info;
+	struct cg2900_chip_callbacks *cb = &(core_info->chip_dev.cb);
+
+	CG2900_INFO("chip_shutdown");
+
+	/* First do a quick power switch of the chip to assure a good state */
+	if (trans_info && trans_info->cb.set_chip_power)
+		trans_info->cb.set_chip_power(false);
+
+	/*
+	 * Wait 50ms before continuing to be sure that the chip detects
+	 * chip power off.
+	 */
+	schedule_timeout_interruptible(
+			msecs_to_jiffies(LINE_TOGGLE_DETECT_TIMEOUT));
+
+	if (trans_info && trans_info->cb.set_chip_power)
+		trans_info->cb.set_chip_power(true);
+
+	/* Wait 100ms before continuing to be sure that the chip is ready */
+	schedule_timeout_interruptible(msecs_to_jiffies(CHIP_READY_TIMEOUT));
+
+	/*
+	 * Let the chip handler finish the reset if any callback is registered.
+	 * Otherwise we are finished.
+	 */
+	if (!cb->chip_shutdown) {
+		CG2900_DBG("No registered handler. Finishing shutdown.");
+		cg2900_chip_shutdown_finished(err);
+		return;
+	}
+
+	err = cb->chip_shutdown(&(core_info->chip_dev));
+	if (err) {
+		CG2900_ERR("chip_shutdown failed (%d). Finishing shutdown.",
+			   err);
+		cg2900_chip_shutdown_finished(err);
+	}
+}
+
+/**
+ * handle_reset_cmd_complete_evt() - Handle a received HCI Command
Complete event for a Reset command.
+ * @data:	Pointer to received HCI data packet.
+ *
+ * Returns:
+ *   True,  if packet was handled internally,
+ *   False, otherwise.
+ */
+static bool handle_reset_cmd_complete_evt(u8 *data)
+{
+	bool pkt_handled = false;
+	u8 status = data[0];
+	struct hci_command_hdr cmd;
+
+	CG2900_INFO("Received Reset complete event with status 0x%X", status);
+
+	if ((core_info->main_state == CORE_BOOTING ||
+	     core_info->main_state == CORE_INITIALIZING) &&
+	    core_info->boot_state == BOOT_NOT_STARTED) {
+		/* Transmit HCI Read Local Version Information command */
+		SET_BOOT_STATE(BOOT_READ_LOCAL_VERSION_INFORMATION);
+		cmd.opcode = cpu_to_le16(HCI_OP_READ_LOCAL_VERSION);
+		cmd.plen = 0; /* No parameters for HCI reset */
+		create_and_send_bt_cmd(&cmd, sizeof(cmd));
+
+		pkt_handled = true;
+	}
+
+	return pkt_handled;
+}
+
+/**
+ * handle_read_local_version_info_cmd_complete_evt() - Handle a
received HCI Command Complete event for a ReadLocalVersionInformation
command.
+ * @data:	Pointer to received HCI data packet.
+ *
+ * Returns:
+ *   True,  if packet was handled internally,
+ *   False, otherwise.
+ */
+static bool handle_read_local_version_info_cmd_complete_evt(u8 *data)
+{
+	bool chip_handled = false;
+	struct list_head *cursor;
+	struct chip_handler_item *tmp;
+	struct local_chip_info *chip;
+	struct cg2900_chip_info *chip_info;
+	struct cg2900_chip_callbacks *cb;
+	int err;
+	struct hci_rp_read_local_version *evt;
+	struct cg2900_platform_data *pf_data;
+
+	/* Check we're in the right state */
+	if ((core_info->main_state != CORE_BOOTING &&
+	     core_info->main_state != CORE_INITIALIZING) ||
+	     core_info->boot_state != BOOT_READ_LOCAL_VERSION_INFORMATION)
+		return false;
+
+	/* We got an answer for our HCI command. Extract data */
+	evt = (struct hci_rp_read_local_version *)data;
+
+	/* We will handle the packet */
+	if (HCI_BT_ERROR_NO_ERROR != evt->status) {
+		CG2900_ERR("Received Read Local Version Information with "
+			     "status 0x%X", evt->status);
+		SET_BOOT_STATE(BOOT_FAILED);
+		cg2900_reset(NULL);
+		return true;
+	}
+
+	/* The command worked. Store the data */
+	chip = &(core_info->local_chip_info);
+	chip->version_set = true;
+	chip->hci_version = evt->hci_ver;
+	chip->hci_revision = le16_to_cpu(evt->hci_rev);
+	chip->lmp_pal_version = evt->lmp_ver;
+	chip->manufacturer = le16_to_cpu(evt->manufacturer);
+	chip->lmp_pal_subversion = le16_to_cpu(evt->lmp_subver);
+	CG2900_DBG("Received Read Local Version Information with:\n"
+		   "\thci_version:  0x%X\n"
+		   "\thci_revision: 0x%X\n"
+		   "\tlmp_pal_version: 0x%X\n"
+		   "\tmanufacturer: 0x%X\n"
+		   "\tlmp_pal_subversion: 0x%X",
+		   chip->hci_version, chip->hci_revision,
+		   chip->lmp_pal_version, chip->manufacturer,
+		   chip->lmp_pal_subversion);
+
+	pf_data = dev_get_platdata(core_info->dev);
+	if (pf_data->set_hci_revision)
+		pf_data->set_hci_revision(chip->hci_version,
+					  chip->hci_revision,
+					  chip->lmp_pal_version,
+					  chip->lmp_pal_subversion,
+					  chip->manufacturer);
+
+	/* Received good confirmation. Find handler for the chip. */
+	chip_info = &(core_info->chip_dev.chip);
+	chip_info->hci_revision = chip->hci_revision;
+	chip_info->hci_sub_version = chip->lmp_pal_subversion;
+	chip_info->manufacturer = chip->manufacturer;
+
+	memset(&(core_info->chip_dev.cb), 0, sizeof(core_info->chip_dev.cb));
+
+	list_for_each(cursor, &chip_handlers) {
+		tmp = list_entry(cursor, struct chip_handler_item, list);
+		chip_handled = tmp->cb.check_chip_support(
+				&(core_info->chip_dev));
+		if (chip_handled) {
+			CG2900_INFO("Chip handler found");
+			break;
+		}
+	}
+
+	if (core_info->main_state == CORE_INITIALIZING) {
+		/*
+		 * We are now finished with the start-up during HwRegistered
+		 * operation.
+		 */
+		SET_BOOT_STATE(BOOT_READY);
+		wake_up_interruptible(&main_wait_queue);
+	} else if (!chip_handled) {
+		CG2900_INFO("No chip handler found. Start-up complete");
+		SET_BOOT_STATE(BOOT_READY);
+		cg2900_chip_startup_finished(0);
+	} else {
+		cb = &(core_info->chip_dev.cb);
+		if (!cb->chip_startup)
+			cg2900_chip_startup_finished(0);
+		else {
+			err = cb->chip_startup(&(core_info->chip_dev));
+			if (err)
+				cg2900_chip_startup_finished(err);
+		}
+	}
+
+	return true;
+}
+
+/**
+ * handle_rx_data_bt_evt() - Check if data should be handled in CG2900 Core.
+ * @skb:	Data packet
+ *
+ * The handle_rx_data_bt_evt() function checks if received data should be
+ * handled in CG2900 Core. If so handle it correctly.
+ * Received data is always HCI BT Event.
+ *
+ * Returns:
+ *   True,  if packet was handled internally,
+ *   False, otherwise.
+ */
+static bool handle_rx_data_bt_evt(struct sk_buff *skb)
+{
+	bool pkt_handled = false;
+	u8 *data = &(skb->data[CG2900_SKB_RESERVE]);
+	struct hci_event_hdr *evt;
+	struct hci_ev_cmd_complete *cmd_complete;
+	u16 op_code;
+
+	evt = (struct hci_event_hdr *)data;
+
+	/* First check the event code */
+	if (HCI_EV_CMD_COMPLETE != evt->evt)
+		return false;
+
+	data += sizeof(*evt);
+	cmd_complete = (struct hci_ev_cmd_complete *)data;
+
+	op_code = le16_to_cpu(cmd_complete->opcode);
+
+	CG2900_DBG_DATA("Received Command Complete: op_code = 0x%04X", op_code);
+	data += sizeof(*cmd_complete); /* Move to first byte after OCF */
+
+	if (op_code == HCI_OP_RESET)
+		pkt_handled = handle_reset_cmd_complete_evt(data);
+	else if (op_code == HCI_OP_READ_LOCAL_VERSION)
+		pkt_handled =
+			handle_read_local_version_info_cmd_complete_evt(data);
+
+	if (pkt_handled)
+		kfree_skb(skb);
+
+	return pkt_handled;
+}
+
+/**
+ * test_char_dev_tx_received() - Handle data received from CG2900 Core.
+ * @dev:	Current transport device information.
+ * @skb:	Buffer with data coming form device.
+ */
+static int test_char_dev_tx_received(struct cg2900_trans_dev *dev,
+				     struct sk_buff *skb)
+{
+	skb_queue_tail(&core_info->test_char_dev->rx_queue, skb);
+	wake_up_interruptible(&char_wait_queue);
+	return 0;
+}
+
+/**
+ * test_char_dev_open() - User space char device has been opened.
+ * @inode:	Device driver information.
+ * @filp:	Pointer to the file struct.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -EACCES if transport already exists.
+ *   -ENOMEM if allocation fails.
+ *   Errors from create_work_item.
+ */
+static int test_char_dev_open(struct inode *inode, struct file *filp)
+{
+	struct cg2900_trans_callbacks cb = {
+		.write = test_char_dev_tx_received,
+		.open = NULL,
+		.close = NULL,
+		.set_chip_power = NULL
+	};
+
+	CG2900_INFO("test_char_dev_open");
+	return cg2900_register_trans_driver(&cb, NULL);
+}
+
+/**
+ * test_char_dev_release() - User space char device has been closed.
+ * @inode:	Device driver information.
+ * @filp:	Pointer to the file struct.
+ *
+ * Returns:
+ *   0 if there is no error.
+ */
+static int test_char_dev_release(struct inode *inode, struct file *filp)
+{
+	/* Clean the message queue */
+	skb_queue_purge(&core_info->test_char_dev->rx_queue);
+	return cg2900_deregister_trans_driver();
+}
+
+/**
+ * test_char_dev_read() - Queue and copy buffer to user space char device.
+ * @filp:	Pointer to the file struct.
+ * @buf:	Received buffer.
+ * @count:	Count of received data in bytes.
+ * @f_pos:	Position in buffer.
+ *
+ * Returns:
+ *   >= 0 is number of bytes read.
+ *   -EFAULT if copy_to_user fails.
+ */
+static ssize_t test_char_dev_read(struct file *filp, char __user *buf,
+				  size_t count, loff_t *f_pos)
+{
+	struct sk_buff *skb;
+	int bytes_to_copy;
+	int err;
+	struct sk_buff_head *rx_queue = &core_info->test_char_dev->rx_queue;
+
+	CG2900_INFO("test_char_dev_read");
+
+	if (skb_queue_empty(rx_queue))
+		wait_event_interruptible(char_wait_queue,
+					 !(skb_queue_empty(rx_queue)));
+
+	skb = skb_dequeue(rx_queue);
+	if (!skb) {
+		CG2900_INFO("skb queue is empty - return with zero bytes");
+		bytes_to_copy = 0;
+		goto finished;
+	}
+
+	bytes_to_copy = min(count, skb->len);
+	err = copy_to_user(buf, skb->data, bytes_to_copy);
+	if (err) {
+		skb_queue_head(rx_queue, skb);
+		return -EFAULT;
+	}
+
+	skb_pull(skb, bytes_to_copy);
+
+	if (skb->len > 0)
+		skb_queue_head(rx_queue, skb);
+	else
+		kfree_skb(skb);
+
+finished:
+	return bytes_to_copy;
+}
+
+/**
+ * test_char_dev_write() - Copy buffer from user and write to CG2900 Core.
+ * @filp:	Pointer to the file struct.
+ * @buf:	Read buffer.
+ * @count:	Size of the buffer write.
+ * @f_pos:	Position in buffer.
+ *
+ * Returns:
+ *   >= 0 is number of bytes written.
+ *   -EFAULT if copy_from_user fails.
+ */
+static ssize_t test_char_dev_write(struct file *filp, const char __user *buf,
+				   size_t count, loff_t *f_pos)
+{
+	struct sk_buff *skb;
+
+	CG2900_INFO("test_char_dev_write count %d", count);
+
+	/* Allocate the SKB and reserve space for the header */
+	skb = alloc_skb(count + RX_SKB_RESERVE, GFP_ATOMIC);
+	if (!skb) {
+		CG2900_ERR("Failed to alloc skb");
+		return -ENOMEM;
+	}
+	skb_reserve(skb, RX_SKB_RESERVE);
+
+	if (copy_from_user(skb_put(skb, count), buf, count)) {
+		kfree_skb(skb);
+		return -EFAULT;
+	}
+	cg2900_data_from_chip(skb);
+
+	return count;
+}
+
+/**
+ * test_char_dev_poll() - Handle POLL call to the interface.
+ * @filp:	Pointer to the file struct.
+ * @wait:	Poll table supplied to caller.
+ *
+ * Returns:
+ *   Mask of current set POLL values (0 or (POLLIN | POLLRDNORM))
+ */
+static unsigned int test_char_dev_poll(struct file *filp, poll_table *wait)
+{
+	unsigned int mask = 0;
+
+	poll_wait(filp, &char_wait_queue, wait);
+
+	if (!(skb_queue_empty(&core_info->test_char_dev->rx_queue)))
+		mask |= POLLIN | POLLRDNORM;
+
+	return mask;
+}
+
+/*
+ * struct test_char_dev_fops - Test char devices file operations.
+ * @read:	Function that reads from the char device.
+ * @write:	Function that writes to the char device.
+ * @poll:	Function that handles poll call to the fd.
+ */
+static const struct file_operations test_char_dev_fops = {
+	.open = test_char_dev_open,
+	.release = test_char_dev_release,
+	.read = test_char_dev_read,
+	.write = test_char_dev_write,
+	.poll = test_char_dev_poll
+};
+
+/**
+ * test_char_dev_create() - Create a char device for testing.
+ *
+ * Creates a separate char device that will interact directly with userspace
+ * test application.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -ENOMEM if allocation fails.
+ *   -EBUSY if device has already been allocated.
+ *   Error codes from misc_register.
+ */
+static int test_char_dev_create(void)
+{
+	int err;
+
+	if (core_info->test_char_dev) {
+		CG2900_ERR("Trying to allocate test_char_dev twice");
+		return -EBUSY;
+	}
+
+	core_info->test_char_dev = kzalloc(sizeof(*(core_info->test_char_dev)),
+					   GFP_KERNEL);
+	if (!core_info->test_char_dev) {
+		CG2900_ERR("Couldn't allocate test_char_dev");
+		return -ENOMEM;
+	}
+
+	/* Initialize the RX queue */
+	skb_queue_head_init(&core_info->test_char_dev->rx_queue);
+
+	/* Prepare miscdevice struct before registering the device */
+	core_info->test_char_dev->test_miscdev.minor = MISC_DYNAMIC_MINOR;
+	core_info->test_char_dev->test_miscdev.name = CG2900_CDEV_NAME;
+	core_info->test_char_dev->test_miscdev.fops = &test_char_dev_fops;
+	core_info->test_char_dev->test_miscdev.parent = core_info->dev;
+
+	err = misc_register(&core_info->test_char_dev->test_miscdev);
+	if (err) {
+		CG2900_ERR("Error %d registering misc dev!", err);
+		kfree(core_info->test_char_dev);
+		core_info->test_char_dev = NULL;
+		return err;
+	}
+
+	return 0;
+}
+
+/**
+ * test_char_dev_destroy() - Clean up after test_char_dev_create().
+ */
+static void test_char_dev_destroy(void)
+{
+	int err;
+
+	if (!core_info->test_char_dev)
+		return;
+
+	err = misc_deregister(&core_info->test_char_dev->test_miscdev);
+	if (err)
+		CG2900_ERR("Error %d deregistering misc dev!", err);
+
+	/* Clean the message queue */
+	skb_queue_purge(&core_info->test_char_dev->rx_queue);
+
+	kfree(core_info->test_char_dev);
+	core_info->test_char_dev = NULL;
+}
+
+/**
+ * open_transport() - Open the CG2900 transport for data transfers.
+ *
+ * Returns:
+ *   0 if there is no error,
+ *   -EACCES if write to transport failed,
+ *   -EIO if transport has not been selected or chip did not answer
to commands.
+ */
+static int open_transport(void)
+{
+	int err = 0;
+	struct trans_info *trans_info = core_info->trans_info;
+
+	CG2900_INFO("open_transport");
+
+	if (trans_info && trans_info->cb.open) {
+		err = trans_info->cb.open(&trans_info->dev);
+		if (err)
+			CG2900_ERR("Transport open failed (%d)", err);
+	}
+
+	if (!err)
+		SET_TRANSPORT_STATE(TRANS_OPENED);
+
+	return err;
+}
+
+/**
+ * close_transport() - Close the CG2900 transport for data transfers.
+ */
+static void close_transport(void)
+{
+	struct trans_info *trans_info = core_info->trans_info;
+
+	CG2900_INFO("close_transport");
+
+	/* Check so transport has not already been removed */
+	if (TRANS_OPENED == core_info->transport_state)
+		SET_TRANSPORT_STATE(TRANS_CLOSED);
+
+	if (trans_info && trans_info->cb.close) {
+		int err = trans_info->cb.close(&trans_info->dev);
+		if (err)
+			CG2900_ERR("Transport close failed (%d)", err);
+	}
+}
+
+/**
+ * create_work_item() - Create work item and add it to the work queue.
+ * @wq:		work queue struct where the work will be added.
+ * @work_func:	Work function.
+ * @data:	Private data for the work.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -EBUSY if not possible to queue work.
+ *   -ENOMEM if allocation fails.
+ */
+static int create_work_item(struct workqueue_struct *wq, work_func_t work_func,
+			    void *data)
+{
+	struct cg2900_work_struct *new_work;
+	int err;
+
+	new_work = kmalloc(sizeof(*new_work), GFP_ATOMIC);
+	if (!new_work) {
+		CG2900_ERR("Failed to alloc memory for cg2900_work_struct!");
+		return -ENOMEM;
+	}
+
+	new_work->data = data;
+	INIT_WORK(&new_work->work, work_func);
+
+	err = queue_work(wq, &new_work->work);
+	if (!err) {
+		CG2900_ERR("Failed to queue work_struct because it's already "
+			   "in the queue!");
+		kfree(new_work);
+		return -EBUSY;
+	}
+
+	return 0;
+}
+
+/**
+ * work_hw_registered() - Called when the interface to HW has been established.
+ * @work:	Reference to work data.
+ *
+ * Since there now is a transport identify the connected chip and decide which
+ * chip handler to use.
+ */
+static void work_hw_registered(struct work_struct *work)
+{
+	struct cg2900_work_struct *current_work = NULL;
+	bool run_shutdown = true;
+	struct cg2900_chip_callbacks *cb;
+	struct cg2900_h4_channels *chan;
+	struct trans_info *trans_info = core_info->trans_info;
+	struct hci_command_hdr cmd;
+
+	CG2900_INFO("work_hw_registered");
+
+	if (!work) {
+		CG2900_ERR("work == NULL");
+		return;
+	}
+
+	current_work = container_of(work, struct cg2900_work_struct, work);
+
+	SET_MAIN_STATE(CORE_INITIALIZING);
+	SET_BOOT_STATE(BOOT_NOT_STARTED);
+
+	/*
+	 * This might look strange, but we need to read out
+	 * the revision info in order to be able to shutdown the chip properly.
+	 */
+	if (trans_info && trans_info->cb.set_chip_power)
+		trans_info->cb.set_chip_power(true);
+
+	/* Wait 100ms before continuing to be sure that the chip is ready */
+	schedule_timeout_interruptible(msecs_to_jiffies(CHIP_READY_TIMEOUT));
+
+	/*
+	 * Transmit HCI reset command to ensure the chip is using
+	 * the correct transport
+	 */
+	cmd.opcode = cpu_to_le16(HCI_OP_RESET);
+	cmd.plen = 0; /* No parameters for HCI reset */
+	create_and_send_bt_cmd(&cmd, sizeof(cmd));
+
+	/* Wait up to 500 milliseconds for revision to be read out */
+	CG2900_DBG("Wait up to 500 milliseconds for revision to be read.");
+	wait_event_interruptible_timeout(main_wait_queue,
+		(BOOT_READY == core_info->boot_state),
+		msecs_to_jiffies(REVISION_READOUT_TIMEOUT));
+
+	/*
+	 * If we are in BOOT_READY we have a good revision.
+	 * Otherwise handle this as an error and switch to default handler.
+	 */
+	if (BOOT_READY != core_info->boot_state) {
+		chip_not_detected();
+		run_shutdown = false;
+	}
+
+	/* Read out the channels for connected chip */
+	cb = &(core_info->chip_dev.cb);
+	chan = &(core_info->h4_channels);
+	if (cb->get_h4_channel) {
+		/* Get the H4 channel ID for all channels */
+		cb->get_h4_channel(CG2900_BT_CMD, &(chan->bt_cmd_channel));
+		cb->get_h4_channel(CG2900_BT_ACL, &(chan->bt_acl_channel));
+		cb->get_h4_channel(CG2900_BT_EVT, &(chan->bt_evt_channel));
+		cb->get_h4_channel(CG2900_GNSS, &(chan->gnss_channel));
+		cb->get_h4_channel(CG2900_FM_RADIO, &(chan->fm_radio_channel));
+		cb->get_h4_channel(CG2900_DEBUG, &(chan->debug_channel));
+		cb->get_h4_channel(CG2900_STE_TOOLS,
+				   &(chan->ste_tools_channel));
+		cb->get_h4_channel(CG2900_HCI_LOGGER,
+				   &(chan->hci_logger_channel));
+		cb->get_h4_channel(CG2900_US_CTRL, &(chan->us_ctrl_channel));
+		cb->get_h4_channel(CG2900_CORE, &(chan->core_channel));
+	}
+
+	/*
+	 * Now it is time to shutdown the controller to reduce
+	 * power consumption until any users register
+	 */
+	if (run_shutdown)
+		chip_shutdown();
+	else
+		cg2900_chip_shutdown_finished(0);
+
+	kfree(current_work);
+}
+
+/**
+ * cg2900_register_user() - Register CG2900 user.
+ * @name:	Name of HCI H:4 channel to register to.
+ * @cb:		Callback structure to use for the H:4 channel.
+ *
+ * Returns:
+ *   Pointer to CG2900 device structure if successful.
+ *   NULL upon failure.
+ */
+struct cg2900_device *cg2900_register_user(char  *name,
+					   struct cg2900_callbacks *cb)
+{
+	struct cg2900_device *current_dev;
+	int err;
+	struct trans_info *trans_info = core_info->trans_info;
+
+	CG2900_INFO("cg2900_register_user %s", name);
+
+	BUG_ON(!core_info);
+
+	/* Wait for state CORE_IDLE or CORE_ACTIVE. */
+	err = wait_event_interruptible_timeout(main_wait_queue,
+			(CORE_IDLE == core_info->main_state ||
+			 CORE_ACTIVE == core_info->main_state),
+			msecs_to_jiffies(LINE_TOGGLE_DETECT_TIMEOUT));
+
+	if (err <= 0) {
+		if (CORE_INITIALIZING == core_info->main_state)
+			CG2900_ERR("Transport not opened");
+		else
+			CG2900_ERR("cg2900_register_user currently busy (0x%X)."
+				   " Try again.", core_info->main_state);
+		return NULL;
+	}
+
+	/* Allocate device */
+	current_dev = kzalloc(sizeof(*current_dev), GFP_ATOMIC);
+	if (!current_dev) {
+		CG2900_ERR("Couldn't allocate current dev");
+		goto error_handling;
+	}
+
+	if (!core_info->chip_dev.cb.get_h4_channel) {
+		CG2900_ERR("No channel handler registered");
+		goto error_handling;
+	}
+	err = core_info->chip_dev.cb.get_h4_channel(name,
+						    &(current_dev->h4_channel));
+	if (err) {
+		CG2900_ERR("Couldn't find H4 channel for %s", name);
+		goto error_handling;
+	}
+	current_dev->dev = core_info->dev;
+	current_dev->cb = kmalloc(sizeof(*(current_dev->cb)),
+					 GFP_ATOMIC);
+	if (!current_dev->cb) {
+		CG2900_ERR("Couldn't allocate cb ");
+		goto error_handling;
+	}
+	memcpy((char *)current_dev->cb, (char *)cb,
+	       sizeof(*(current_dev->cb)));
+
+	/* Retrieve pointer to the correct CG2900 Core user structure */
+	err = add_h4_user(current_dev, name);
+
+	if (!err) {
+		CG2900_DBG("H:4 channel 0x%X registered",
+			   current_dev->h4_channel);
+	} else {
+		CG2900_ERR("H:4 channel 0x%X already registered "
+			   "or other error (%d)",
+			   current_dev->h4_channel, err);
+		goto error_handling;
+	}
+
+	if (CORE_ACTIVE != core_info->main_state &&
+	    core_info->users.nbr_of_users == 1) {
+		/* Open transport and start-up the chip */
+		if (trans_info && trans_info->cb.set_chip_power)
+			trans_info->cb.set_chip_power(true);
+
+		/* Wait 100ms to be sure that the chip is ready */
+		schedule_timeout_interruptible(
+				msecs_to_jiffies(CHIP_READY_TIMEOUT));
+
+		err = open_transport();
+		if (err) {
+			/*
+			 * Remove the user. If there is no error it will be
+			 * freed as well.
+			 */
+			remove_h4_user(&current_dev);
+			goto finished;
+		}
+
+		chip_startup();
+
+		/* Wait up to 15 seconds for chip to start */
+		CG2900_DBG("Wait up to 15 seconds for chip to start..");
+		wait_event_interruptible_timeout(main_wait_queue,
+			(CORE_ACTIVE == core_info->main_state ||
+			 CORE_IDLE   == core_info->main_state),
+			msecs_to_jiffies(CHIP_STARTUP_TIMEOUT));
+		if (CORE_ACTIVE != core_info->main_state) {
+			CG2900_ERR("ST-Ericsson CG2900 driver failed to "
+				     "start");
+
+			/* Close the transport and power off the chip */
+			close_transport();
+
+			/*
+			 * Remove the user. If there is no error it will be
+			 * freed as well.
+			 */
+			remove_h4_user(&current_dev);
+
+			/* Chip shut-down finished, set correct state. */
+			SET_MAIN_STATE(CORE_IDLE);
+		}
+	}
+	goto finished;
+
+error_handling:
+	free_user_dev(&current_dev);
+finished:
+	return current_dev;
+}
+EXPORT_SYMBOL(cg2900_register_user);
+
+/**
+ * cg2900_deregister_user() - Remove registration of CG2900 user.
+ * @dev:	CG2900 device.
+ */
+void cg2900_deregister_user(struct cg2900_device *dev)
+{
+	int h4_channel;
+	int err = 0;
+
+	CG2900_INFO("cg2900_deregister_user");
+
+	BUG_ON(!core_info);
+
+	if (!dev) {
+		CG2900_ERR("Calling with NULL pointer");
+		return;
+	}
+
+	h4_channel = dev->h4_channel;
+
+	/* Remove the user. If there is no error it will be freed as well */
+	err = remove_h4_user(&dev);
+	if (err) {
+		CG2900_ERR("Trying to deregister non-registered "
+			   "H:4 channel 0x%X or other error %d",
+			   h4_channel, err);
+		return;
+	}
+
+	CG2900_DBG("H:4 channel 0x%X deregistered", h4_channel);
+
+	if (0 != core_info->users.nbr_of_users)
+		/* This was not the last user, we're done. */
+		return;
+
+	if (CORE_IDLE == core_info->main_state)
+		/* Chip has already been shut down. */
+		return;
+
+	SET_MAIN_STATE(CORE_CLOSING);
+	chip_shutdown();
+
+	/* Wait up to 15 seconds for chip to shut-down */
+	CG2900_DBG("Wait up to 15 seconds for chip to shut-down..");
+	wait_event_interruptible_timeout(main_wait_queue,
+				(CORE_IDLE == core_info->main_state),
+				msecs_to_jiffies(CHIP_SHUTDOWN_TIMEOUT));
+
+	/* Force shutdown if we timed out */
+	if (CORE_IDLE != core_info->main_state) {
+		CG2900_ERR("ST-Ericsson CG2900 Core Driver was shut-down with "
+			   "problems.");
+
+		/* Close the transport and power off the chip */
+		close_transport();
+
+		/* Chip shut-down finished, set correct state. */
+		SET_MAIN_STATE(CORE_IDLE);
+	}
+}
+EXPORT_SYMBOL(cg2900_deregister_user);
+
+/**
+ * cg2900_reset() - Reset the CG2900 controller.
+ * @dev:	CG2900 device.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -EACCES if driver has not been initialized.
+ */
+int cg2900_reset(struct cg2900_device *dev)
+{
+	CG2900_INFO("cg2900_reset");
+
+	BUG_ON(!core_info);
+
+	SET_MAIN_STATE(CORE_RESETING);
+
+	/* Shutdown the chip */
+	chip_shutdown();
+
+	/*
+	 * Inform all registered users about the reset and free the user devices
+	 * Don't send reset for debug and logging channels
+	 */
+	handle_reset_of_user(&(core_info->users.bt_cmd));
+	handle_reset_of_user(&(core_info->users.bt_audio));
+	handle_reset_of_user(&(core_info->users.bt_acl));
+	handle_reset_of_user(&(core_info->users.bt_evt));
+	handle_reset_of_user(&(core_info->users.fm_radio));
+	handle_reset_of_user(&(core_info->users.fm_radio_audio));
+	handle_reset_of_user(&(core_info->users.gnss));
+	handle_reset_of_user(&(core_info->users.core));
+
+	core_info->users.nbr_of_users = 0;
+
+	/* Reset finished. We are now idle until first user is registered */
+	SET_MAIN_STATE(CORE_IDLE);
+
+	/*
+	 * Send wake-up since this might have been called from a failed boot.
+	 * No harm done if it is a CG2900 Core user who called.
+	 */
+	wake_up_interruptible(&main_wait_queue);
+
+	return 0;
+}
+EXPORT_SYMBOL(cg2900_reset);
+
+/**
+ * cg2900_alloc_skb() - Alloc an sk_buff structure for CG2900 handling.
+ * @size:	Size in number of octets.
+ * @priority:	Allocation priority, e.g. GFP_KERNEL.
+ *
+ * Returns:
+ *   Pointer to sk_buff buffer structure if successful.
+ *   NULL upon allocation failure.
+ */
+struct sk_buff *cg2900_alloc_skb(unsigned int size, gfp_t priority)
+{
+	struct sk_buff *skb;
+
+	CG2900_INFO("cg2900_alloc_skb");
+	CG2900_DBG("size %d bytes", 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;
+}
+EXPORT_SYMBOL(cg2900_alloc_skb);
+
+/**
+ * cg2900_write() - Send data to the connectivity controller.
+ * @dev: CG2900 device.
+ * @skb: Data packet.
+ *
+ * The cg2900_write() function sends data to the connectivity controller.
+ * If the return value is 0 the skb will be freed by the driver,
+ * otherwise it won't be freed.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -EACCES if driver has not been initialized or trying to write while driver
+ *   is not active.
+ *   -EINVAL if NULL pointer was supplied.
+ *   -EPERM if operation is not permitted, e.g. trying to write to a channel
+ *   that doesn't handle write operations.
+ *   Error codes returned from core_enable_hci_logger.
+ */
+int cg2900_write(struct cg2900_device *dev, struct sk_buff *skb)
+{
+	int err = 0;
+	u8 *h4_header;
+	struct cg2900_chip_callbacks *cb;
+
+	CG2900_DBG_DATA("cg2900_write");
+
+	BUG_ON(!core_info);
+
+	if (!dev) {
+		CG2900_ERR("cg2900_write with no device");
+		return -EINVAL;
+	}
+
+	if (!skb) {
+		CG2900_ERR("cg2900_write with no sk_buffer");
+		return -EINVAL;
+	}
+
+	CG2900_DBG_DATA("Length %d bytes", skb->len);
+
+	if (core_info->h4_channels.hci_logger_channel == dev->h4_channel) {
+		/*
+		 * Treat the HCI logger write differently.
+		 * A write can only mean a change of configuration.
+		 */
+		err = enable_hci_logger(skb);
+	} else if (core_info->h4_channels.core_channel == dev->h4_channel) {
+		CG2900_ERR("Not possible to write data on core channel, "
+			   "it only supports enable / disable chip");
+		err = -EPERM;
+	} else if (CORE_ACTIVE == core_info->main_state) {
+		/*
+		 * 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)dev->h4_channel;
+
+		/*
+		 * Check if the chip handler wants to handle this packet.
+		 * If not, send it to the transport.
+		 */
+		cb = &(core_info->chip_dev.cb);
+		if (!cb->data_to_chip ||
+		    !(cb->data_to_chip(&(core_info->chip_dev), dev, skb)))
+			transmit_skb_to_chip(skb, dev->logger_enabled);
+	} else {
+		CG2900_ERR("Trying to transmit data when CG2900 Core is not "
+			     "active");
+		err = -EACCES;
+	}
+
+	return err;
+}
+EXPORT_SYMBOL(cg2900_write);
+
+/**
+ * cg2900_get_local_revision() - Read revision of the connected controller.
+ * @rev_data:	Revision data structure to fill. Must be allocated by caller.
+ *
+ * The cg2900_get_local_revision() function returns the revision data of the
+ * local controller if available. If data is not available, e.g. because the
+ * controller has not yet been started this function will return false.
+ *
+ * Returns:
+ *   true if revision data is available.
+ *   false if no revision data is available.
+ */
+bool cg2900_get_local_revision(struct cg2900_rev_data *rev_data)
+{
+	BUG_ON(!core_info);
+
+	if (!rev_data) {
+		CG2900_ERR("Calling with rev_data NULL");
+		return false;
+	}
+
+	if (!core_info->local_chip_info.version_set)
+		return false;
+
+	rev_data->revision = core_info->local_chip_info.hci_revision;
+	rev_data->sub_version = core_info->local_chip_info.lmp_pal_subversion;
+
+	return true;
+}
+EXPORT_SYMBOL(cg2900_get_local_revision);
+
+int cg2900_register_chip_driver(struct cg2900_id_callbacks *cb)
+{
+	struct chip_handler_item *item;
+
+	CG2900_INFO("cg2900_register_chip_driver");
+
+	if (!cb) {
+		CG2900_ERR("NULL supplied as cb");
+		return -EINVAL;
+	}
+
+	item = kzalloc(sizeof(*item), GFP_ATOMIC);
+	if (!item) {
+		CG2900_ERR("Failed to alloc memory!");
+		return -ENOMEM;
+	}
+
+	memcpy(&(item->cb), cb, sizeof(cb));
+	list_add_tail(&item->list, &chip_handlers);
+	return 0;
+}
+EXPORT_SYMBOL(cg2900_register_chip_driver);
+
+int cg2900_register_trans_driver(struct cg2900_trans_callbacks *cb, void *data)
+{
+	int err;
+
+	BUG_ON(!core_info);
+
+	CG2900_INFO("cg2900_register_trans_driver");
+
+	if (core_info->trans_info) {
+		CG2900_ERR("trans_info already exists");
+		return -EACCES;
+	}
+
+	core_info->trans_info = kzalloc(sizeof(*(core_info->trans_info)),
+					GFP_KERNEL);
+	if (!core_info->trans_info) {
+		CG2900_ERR("Could not allocate trans_info");
+		return -ENOMEM;
+	}
+
+	memcpy(&(core_info->trans_info->cb), cb, sizeof(*cb));
+	core_info->trans_info->dev.dev = core_info->dev;
+	core_info->trans_info->dev.user_data = data;
+
+	err = create_work_item(core_info->wq, work_hw_registered, NULL);
+	if (err) {
+		CG2900_ERR("Could not create work item (%d) "
+			     "work_hw_registered", err);
+	}
+
+	return err;
+}
+EXPORT_SYMBOL(cg2900_register_trans_driver);
+
+int cg2900_deregister_trans_driver(void)
+{
+	BUG_ON(!core_info);
+
+	CG2900_INFO("cg2900_deregister_trans_driver");
+
+	SET_MAIN_STATE(CORE_INITIALIZING);
+	SET_TRANSPORT_STATE(TRANS_INITIALIZING);
+
+	if (!core_info->trans_info)
+		return -EACCES;
+
+	kfree(core_info->trans_info);
+	core_info->trans_info = NULL;
+
+	return 0;
+}
+EXPORT_SYMBOL(cg2900_deregister_trans_driver);
+
+int cg2900_chip_startup_finished(int err)
+{
+	CG2900_INFO("cg2900_chip_startup_finished (%d)", err);
+
+	if (err)
+		/* Shutdown the chip */
+		chip_shutdown();
+	else
+		SET_MAIN_STATE(CORE_ACTIVE);
+
+	wake_up_interruptible(&main_wait_queue);
+
+	if (!core_info->trans_info->cb.chip_startup_finished)
+		CG2900_ERR("chip_startup_finished callback not found.");
+	else
+		core_info->trans_info->cb.chip_startup_finished();
+	return 0;
+}
+EXPORT_SYMBOL(cg2900_chip_startup_finished);
+
+int cg2900_chip_shutdown_finished(int err)
+{
+	CG2900_INFO("cg2900_chip_shutdown_finished (%d)", err);
+
+	/* Close the transport, which will power off the chip */
+	close_transport();
+
+	/* Chip shut-down finished, set correct state and wake up the chip. */
+	SET_MAIN_STATE(CORE_IDLE);
+	wake_up_interruptible(&main_wait_queue);
+
+	return 0;
+}
+EXPORT_SYMBOL(cg2900_chip_shutdown_finished);
+
+int cg2900_send_to_chip(struct sk_buff *skb, bool use_logger)
+{
+	transmit_skb_to_chip(skb, use_logger);
+	return 0;
+}
+EXPORT_SYMBOL(cg2900_send_to_chip);
+
+struct cg2900_device *cg2900_get_bt_cmd_dev(void)
+{
+	if (core_info)
+		return core_info->users.bt_cmd;
+	else
+		return NULL;
+}
+EXPORT_SYMBOL(cg2900_get_bt_cmd_dev);
+
+struct cg2900_device *cg2900_get_fm_radio_dev(void)
+{
+	if (core_info)
+		return core_info->users.fm_radio;
+	else
+		return NULL;
+}
+EXPORT_SYMBOL(cg2900_get_fm_radio_dev);
+
+struct cg2900_device *cg2900_get_bt_audio_dev(void)
+{
+	if (core_info)
+		return core_info->users.bt_audio;
+	else
+		return NULL;
+}
+EXPORT_SYMBOL(cg2900_get_bt_audio_dev);
+
+struct cg2900_device *cg2900_get_fm_audio_dev(void)
+{
+	if (core_info)
+		return core_info->users.fm_radio_audio;
+	else
+		return NULL;
+}
+EXPORT_SYMBOL(cg2900_get_fm_audio_dev);
+
+struct cg2900_hci_logger_config *cg2900_get_hci_logger_config(void)
+{
+	if (core_info)
+		return &(core_info->hci_logger_config);
+	else
+		return NULL;
+}
+EXPORT_SYMBOL(cg2900_get_hci_logger_config);
+
+unsigned long cg2900_get_sleep_timeout(void)
+{
+	if (CORE_ACTIVE != core_info->main_state || !sleep_timeout_ms)
+		return 0;
+
+	return msecs_to_jiffies(sleep_timeout_ms);
+}
+EXPORT_SYMBOL(cg2900_get_sleep_timeout);
+
+void cg2900_data_from_chip(struct sk_buff *skb)
+{
+	struct cg2900_device *dev = NULL;
+	u8 h4_channel;
+	int err = 0;
+	struct cg2900_chip_callbacks *cb;
+	struct sk_buff *skb_log;
+	struct cg2900_device *logger;
+
+	CG2900_INFO("cg2900_data_from_chip");
+
+	if (!skb) {
+		CG2900_ERR("No data supplied");
+		return;
+	}
+
+	h4_channel = *(skb->data);
+
+	/*
+	 * First check if this is the response for something
+	 * we have sent internally.
+	 */
+	if ((core_info->main_state == CORE_BOOTING ||
+	     core_info->main_state == CORE_INITIALIZING) &&
+	    (HCI_BT_EVT_H4_CHANNEL == h4_channel) &&
+	    handle_rx_data_bt_evt(skb)) {
+		CG2900_DBG("Received packet handled internally");
+		return;
+	}
+
+	/* Find out where to route the data */
+	err = find_h4_user(h4_channel, &dev, skb);
+
+	/* Check if the chip handler wants to deal with the packet. */
+	cb = &(core_info->chip_dev.cb);
+	if (!err && cb->data_from_chip &&
+	    cb->data_from_chip(&(core_info->chip_dev), dev, skb))
+		return;
+
+	if (err || !dev) {
+		CG2900_ERR("H:4 channel: 0x%X,  does not match device",
+			     h4_channel);
+		kfree_skb(skb);
+		return;
+	}
+
+	/*
+	 * If HCI logging is enabled for this channel, copy the data to
+	 * the HCI logging output.
+	 */
+	logger = core_info->users.hci_logger;
+	if (!logger || !dev->logger_enabled)
+		goto transmit;
+
+	/*
+	 * Alloc a new sk_buffer and copy the data into it.
+	 * Then send it to the HCI logger.
+	 */
+	skb_log = alloc_skb(skb->len + 1, GFP_ATOMIC);
+	if (!skb_log) {
+		CG2900_ERR("Couldn't allocate skb_log");
+		goto transmit;
+	}
+
+	memcpy(skb_put(skb_log, skb->len), skb->data, skb->len);
+	skb_log->data[0] = (u8) LOGGER_DIRECTION_RX;
+
+	if (logger->cb->read_cb)
+		logger->cb->read_cb(logger, skb_log);
+
+transmit:
+	/* Remove the H4 header */
+	(void)skb_pull(skb, CG2900_SKB_RESERVE);
+
+	/* Call the Read callback */
+	if (dev->cb->read_cb)
+		dev->cb->read_cb(dev, skb);
+}
+EXPORT_SYMBOL(cg2900_data_from_chip);
+
+static struct cg2900_bt_platform_data cg2900_bt_data = {
+	.bus = HCI_UART,
+};
+
+static struct mfd_cell cg2900_devs[] = {
+	{
+		.name = "cg2900-bt",
+		.platform_data = &cg2900_bt_data,
+		.data_size = sizeof(cg2900_bt_data),
+	},
+	{
+		.name = "cg2900-fm",
+	},
+	{
+		.name = "cg2900-gnss",
+	},
+	{
+		.name = "cg2900-audio",
+	},
+	{
+		.name = "cg2900-core",
+	},
+	{
+		.name = "cg2900-chardev",
+	},
+	{
+		.name = "cg2900-uart",
+	},
+	{
+		.name = "cg2900-chip",
+	},
+	{
+		.name = "stlc2690-chip",
+	},
+};
+
+/**
+ * cg2900_probe() - Initialize module.
+ *
+ * @pdev:	Platform device.
+ *
+ * This function initialize the transport and CG2900 Core, then
+ * register to the transport framework.
+ *
+ * Returns:
+ *   0 if success.
+ *   -ENOMEM for failed alloc or structure creation.
+ *   Error codes generated by platform init, alloc_chrdev_region,
+ *   class_create, device_create, core_init, tty_register_ldisc,
+ *   create_work_item.
+ */
+static int __devinit cg2900_probe(struct platform_device *pdev)
+{
+	int err;
+	struct cg2900_platform_data *pf_data;
+
+	CG2900_INFO("cg2900_probe");
+
+	pf_data = dev_get_platdata(&pdev->dev);
+	if (!pf_data) {
+		CG2900_ERR("Missing platform data");
+		return -EINVAL;
+	}
+
+	if (pf_data->init) {
+		err = pf_data->init();
+		if (err) {
+			CG2900_ERR("Platform init failed (%d)", err);
+			return err;
+		}
+	}
+
+	core_info = kzalloc(sizeof(*core_info), GFP_KERNEL);
+	if (!core_info) {
+		CG2900_ERR("Couldn't allocate core_info");
+		return -ENOMEM;
+	}
+
+	core_info->dev = &pdev->dev;
+
+	/* Set the internal states */
+	core_info->main_state = CORE_INITIALIZING;
+	core_info->boot_state = BOOT_NOT_STARTED;
+	core_info->transport_state = TRANS_INITIALIZING;
+
+	/* Get the H4 channel ID for all channels */
+	core_info->h4_channels.bt_cmd_channel = HCI_BT_CMD_H4_CHANNEL;
+	core_info->h4_channels.bt_acl_channel = HCI_BT_ACL_H4_CHANNEL;
+	core_info->h4_channels.bt_evt_channel = HCI_BT_EVT_H4_CHANNEL;
+	core_info->h4_channels.gnss_channel = HCI_FM_RADIO_H4_CHANNEL;
+	core_info->h4_channels.fm_radio_channel = HCI_GNSS_H4_CHANNEL;
+
+	core_info->wq = create_singlethread_workqueue(CORE_WQ_NAME);
+	if (!core_info->wq) {
+		CG2900_ERR("Could not create workqueue");
+		err = -ENOMEM;
+		goto error_handling;
+	}
+
+	/* Create and add test char device. */
+	err = test_char_dev_create();
+	if (err)
+		goto error_handling_deregister;
+
+	cg2900_bt_data.bus = pf_data->bus;
+
+	err = mfd_add_devices(core_info->dev, 0, cg2900_devs,
+			      ARRAY_SIZE(cg2900_devs), NULL, 0);
+	if (err) {
+		CG2900_ERR("Failed to add MFD devices (%d)", err);
+		goto error_handling_test_destroy;
+	}
+
+	return 0;
+
+error_handling_test_destroy:
+	test_char_dev_destroy();
+error_handling_deregister:
+	destroy_workqueue(core_info->wq);
+error_handling:
+	core_info->dev = NULL;
+	kfree(core_info);
+	core_info = NULL;
+	return err;
+}
+
+/**
+ * cg2900_remove() - Remove module.
+ *
+ * @pdev:	Platform device.
+ *
+ * Returns:
+ *   0 if success.
+ *   -ENOMEM if core_info does not exist.
+ *   -EINVAL if platform data does not exist in the device.
+ */
+static int __devexit cg2900_remove(struct platform_device *pdev)
+{
+	struct cg2900_platform_data *pf_data;
+
+	CG2900_INFO("cg2900_remove");
+
+	if (!core_info) {
+		CG2900_ERR("CG2900 Core not initiated");
+		return -ENOMEM;
+	}
+
+	mfd_remove_devices(core_info->dev);
+
+	test_char_dev_destroy();
+
+	/* Free the user devices */
+	free_user_dev(&(core_info->users.bt_cmd));
+	free_user_dev(&(core_info->users.bt_acl));
+	free_user_dev(&(core_info->users.bt_evt));
+	free_user_dev(&(core_info->users.fm_radio));
+	free_user_dev(&(core_info->users.gnss));
+	free_user_dev(&(core_info->users.debug));
+	free_user_dev(&(core_info->users.ste_tools));
+	free_user_dev(&(core_info->users.hci_logger));
+	free_user_dev(&(core_info->users.us_ctrl));
+	free_user_dev(&(core_info->users.core));
+
+	core_info->dev = NULL;
+
+	destroy_workqueue(core_info->wq);
+
+	kfree(core_info);
+	core_info = NULL;
+
+	pf_data = dev_get_platdata(&pdev->dev);
+	if (!pf_data) {
+		CG2900_ERR("Missing platform data");
+		return -EINVAL;
+	}
+
+	if (pf_data->exit)
+		pf_data->exit();
+
+	return 0;
+}
+
+static struct platform_driver cg2900_driver = {
+	.driver = {
+		.name	= "cg2900",
+		.owner	= THIS_MODULE,
+	},
+	.probe	= cg2900_probe,
+	.remove	= __devexit_p(cg2900_remove),
+};
+
+/**
+ * cg2900_init() - Initialize module.
+ *
+ * Registers platform driver.
+ */
+static int __init cg2900_init(void)
+{
+	CG2900_INFO("cg2900_init");
+	return platform_driver_register(&cg2900_driver);
+}
+
+/**
+ * cg2900_exit() - Remove module.
+ *
+ * Unregisters platform driver.
+ */
+static void __exit cg2900_exit(void)
+{
+	CG2900_INFO("cg2900_exit");
+	platform_driver_unregister(&cg2900_driver);
+}
+
+module_init(cg2900_init);
+module_exit(cg2900_exit);
+
+module_param(sleep_timeout_ms, int, S_IRUGO | S_IWUSR | S_IWGRP);
+MODULE_PARM_DESC(sleep_timeout_ms,
+		 "Sleep timeout for data transmissions:\n"
+		 "\t0 = disable <default>\n"
+		 "\t>0 = sleep timeout in milliseconds");
+
+module_param(cg2900_debug_level, int, S_IRUGO | S_IWUSR | S_IWGRP);
+MODULE_PARM_DESC(cg2900_debug_level,
+		 "Debug level. Default 1. Possible values:\n"
+		 "\t0  = No debug\n"
+		 "\t1  = Error prints\n"
+		 "\t10 = General info, e.g. function entries\n"
+		 "\t20 = Debug info, e.g. steps in a functionality\n"
+		 "\t25 = Data info, i.e. prints when data is transferred\n"
+		 "\t30 = Data content, i.e. contents of the transferred data");
+
+module_param_array(bd_address, byte, &bd_addr_count,
+		   S_IRUGO | S_IWUSR | S_IWGRP);
+MODULE_PARM_DESC(bd_address,
+		 "Bluetooth Device address. "
+		 "Default 0x00 0x80 0xDE 0xAD 0xBE 0xEF. "
+		 "Enter as comma separated value.");
+
+module_param(default_hci_revision, int, S_IRUGO | S_IWUSR | S_IWGRP);
+MODULE_PARM_DESC(default_hci_revision,
+		 "Default HCI revision according to Bluetooth Assigned "
+		 "Numbers.");
+
+module_param(default_manufacturer, int, S_IRUGO | S_IWUSR | S_IWGRP);
+MODULE_PARM_DESC(default_manufacturer,
+		 "Default Manufacturer according to Bluetooth Assigned "
+		 "Numbers.");
+
+module_param(default_sub_version, int, S_IRUGO | S_IWUSR | S_IWGRP);
+MODULE_PARM_DESC(default_sub_version,
+		 "Default HCI sub-version according to Bluetooth Assigned "
+		 "Numbers.");
+
+MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Linux Bluetooth HCI H:4 CG2900 Connectivity
Device Driver");
diff --git a/drivers/mfd/cg2900/cg2900_core.h b/drivers/mfd/cg2900/cg2900_core.h
new file mode 100644
index 0000000..d200d45
--- /dev/null
+++ b/drivers/mfd/cg2900/cg2900_core.h
@@ -0,0 +1,303 @@
+/*
+ * drivers/mfd/cg2900/cg2900_core.h
+ *
+ * Copyright (C) ST-Ericsson SA 2010
+ * Authors:
+ * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for
ST-Ericsson.
+ * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson.
+ * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson.
+ * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson.
+ * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson.
+ * License terms:  GNU General Public License (GPL), version 2
+ *
+ * Linux Bluetooth HCI H:4 Driver for ST-Ericsson CG2900 GPS/BT/FM controller.
+ */
+
+#ifndef _CG2900_CORE_H_
+#define _CG2900_CORE_H_
+
+#include <linux/device.h>
+#include <linux/skbuff.h>
+
+/* Reserve 1 byte for the HCI H:4 header */
+#define CG2900_SKB_RESERVE			1
+
+#define BT_BDADDR_SIZE				6
+
+struct cg2900_h4_channels {
+	int	bt_cmd_channel;
+	int	bt_acl_channel;
+	int	bt_evt_channel;
+	int	gnss_channel;
+	int	fm_radio_channel;
+	int	debug_channel;
+	int	ste_tools_channel;
+	int	hci_logger_channel;
+	int	us_ctrl_channel;
+	int	core_channel;
+};
+
+/**
+  * struct cg2900_hci_logger_config - Configures the HCI logger.
+  * @bt_cmd_enable: Enable BT command logging.
+  * @bt_acl_enable: Enable BT ACL logging.
+  * @bt_evt_enable: Enable BT event logging.
+  * @gnss_enable: Enable GNSS logging.
+  * @fm_radio_enable: Enable FM radio logging.
+  * @bt_audio_enable: Enable BT audio command logging.
+  * @fm_radio_audio_enable: Enable FM radio audio command logging.
+  *
+  * Set using cg2900_write on CHANNEL_HCI_LOGGER H4 channel.
+  */
+struct cg2900_hci_logger_config {
+	bool bt_cmd_enable;
+	bool bt_acl_enable;
+	bool bt_evt_enable;
+	bool gnss_enable;
+	bool fm_radio_enable;
+	bool bt_audio_enable;
+	bool fm_radio_audio_enable;
+};
+
+/**
+ * struct cg2900_chip_info - Chip info structure.
+ * @manufacturer:	Chip manufacturer.
+ * @hci_revision:	Chip revision, i.e. which chip is this.
+ * @hci_sub_version:	Chip sub-version, i.e. which tape-out is this.
+ *
+ * Note that these values match the Bluetooth Assigned Numbers,
+ * see http://www.bluetooth.org/
+ */
+struct cg2900_chip_info {
+	int manufacturer;
+	int hci_revision;
+	int hci_sub_version;
+};
+
+struct cg2900_chip_dev;
+
+/**
+ * struct cg2900_chip_callbacks - Callback functions registered by
chip handler.
+ * @chip_startup:		Called when chip is started up.
+ * @chip_shutdown:		Called when chip is shut down.
+ * @data_to_chip:		Called when data shall be transmitted to chip.
+ *				Return true when CG2900 Core shall not send it
+ *				to chip.
+ * @data_from_chip:		Called when data shall be transmitted to user.
+ *				Return true when packet is taken care of by
+ *				Return chip return handler.
+ * @get_h4_channel:		Connects channel name with H:4 channel number.
+ * @is_bt_audio_user:		Return true if current packet is for
+ *				the BT audio user.
+ * @is_fm_audio_user:		Return true if current packet is for
+ *				the FM audio user.
+ * @last_bt_user_removed:	Last BT channel user has been removed.
+ * @last_fm_user_removed:	Last FM channel user has been removed.
+ * @last_gnss_user_removed:	Last GNSS channel user has been removed.
+ *
+ * Note that some callbacks may be NULL. They must always be NULL
checked before
+ * calling.
+ */
+struct cg2900_chip_callbacks {
+	int (*chip_startup)(struct cg2900_chip_dev *dev);
+	int (*chip_shutdown)(struct cg2900_chip_dev *dev);
+	bool (*data_to_chip)(struct cg2900_chip_dev *dev,
+			     struct cg2900_device *cg2900_dev,
+			     struct sk_buff *skb);
+	bool (*data_from_chip)(struct cg2900_chip_dev *dev,
+			       struct cg2900_device *cg2900_dev,
+			       struct sk_buff *skb);
+	int (*get_h4_channel)(char *name, int *h4_channel);
+	bool (*is_bt_audio_user)(int h4_channel,
+				 const struct sk_buff * const skb);
+	bool (*is_fm_audio_user)(int h4_channel,
+				 const struct sk_buff * const skb);
+	void (*last_bt_user_removed)(struct cg2900_chip_dev *dev);
+	void (*last_fm_user_removed)(struct cg2900_chip_dev *dev);
+	void (*last_gnss_user_removed)(struct cg2900_chip_dev *dev);
+};
+
+/**
+ * struct cg2900_chip_dev - Chip handler info structure.
+ * @chip:	Chip info such as manufacturer.
+ * @cb:		Callback structure for the chip handler.
+ * @user_data:	Arbitrary data set by chip handler.
+ */
+struct cg2900_chip_dev {
+	struct cg2900_chip_info		chip;
+	struct cg2900_chip_callbacks	cb;
+	void				*user_data;
+};
+
+/**
+ * struct cg2900_id_callbacks - Chip handler identification callbacks.
+ * @check_chip_support:	Called when chip is connected. If chip is supported by
+ *			driver, return true and fill in @callbacks in @dev.
+ *
+ * Note that the callback may be NULL. It must always be NULL checked before
+ * calling.
+ */
+struct cg2900_id_callbacks {
+	bool (*check_chip_support)(struct cg2900_chip_dev *dev);
+};
+
+/**
+ * struct cg2900_trans_dev - CG2900 transport info structure.
+ * @dev:	Parent device from CG2900 Core.
+ * @user_data:	Arbitrary data set by chip handler.
+ */
+struct cg2900_trans_dev {
+	struct device	*dev;
+	void		*user_data;
+};
+
+/**
+ * struct cg2900_trans_callbacks - Callback functions registered by transport.
+ * @open:		CG2900 Core needs a transport.
+ * @close:		CG2900 Core does not need a transport.
+ * @write:		CG2900 Core transmits to the chip.
+ * @set_chip_power:	CG2900 Core enables or disables the chip.
+ * @chip_startup_finished:	CG2900 Chip startup finished notification.
+ *
+ * Note that some callbacks may be NULL. They must always be NULL
checked before
+ * calling.
+ */
+struct cg2900_trans_callbacks {
+	int (*open)(struct cg2900_trans_dev *dev);
+	int (*close)(struct cg2900_trans_dev *dev);
+	int (*write)(struct cg2900_trans_dev *dev, struct sk_buff *skb);
+	void (*set_chip_power)(bool chip_on);
+	void (*chip_startup_finished)(void);
+};
+
+/**
+ * cg2900_register_chip_driver() - Register a chip handler.
+ * @cb:	Callbacks to call when chip is connected.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -EINVAL if NULL is supplied as @cb.
+ *   -ENOMEM if allocation fails or work queue can't be created.
+ */
+extern int cg2900_register_chip_driver(struct cg2900_id_callbacks *cb);
+
+/**
+ * cg2900_register_trans_driver() - Register a transport driver.
+ * @cb:		Callbacks to call when chip is connected.
+ * @data:	Arbitrary data used by the transport driver.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -EINVAL if NULL is supplied as @cb.
+ *   -ENOMEM if allocation fails or work queue can't be created.
+ */
+extern int cg2900_register_trans_driver(struct cg2900_trans_callbacks *cb,
+					void *data);
+
+/**
+ * cg2900_deregister_trans_driver() - Deregister a transport driver.
+ *
+ * Returns:
+ *   0 if there is no error.
+ *   -EINVAL if NULL is supplied as @cb.
+ *   -ENOMEM if allocation fails or work queue can't be created.
+ */
+extern int cg2900_deregister_trans_driver(void);
+
+/**
+ * cg2900_chip_startup_finished() - Called from chip handler when
start-up is finished.
+ * @err:	Result of the start-up.
+ *
+ * Returns:
+ *   0 if there is no error.
+ */
+extern int cg2900_chip_startup_finished(int err);
+
+/**
+ * cg2900_chip_shutdown_finished() - Called from chip handler when
shutdown is finished.
+ * @err:	Result of the shutdown.
+ *
+ * Returns:
+ *   0 if there is no error.
+ */
+extern int cg2900_chip_shutdown_finished(int err);
+
+/**
+ * cg2900_send_to_chip() - Send data to chip.
+ * @skb:	Packet to transmit.
+ * @use_logger:	true if hci_logger should copy data content.
+ *
+ * Returns:
+ *   0 if there is no error.
+ */
+extern int cg2900_send_to_chip(struct sk_buff *skb, bool use_logger);
+
+/**
+ * cg2900_get_bt_cmd_dev() - Return user of the BT command H:4 channel.
+ *
+ * Returns:
+ *   User of the BT command H:4 channel.
+ *   NULL if no user is registered.
+ */
+extern struct cg2900_device *cg2900_get_bt_cmd_dev(void);
+
+/**
+ * cg2900_get_fm_radio_dev() - Return user of the FM radio H:4 channel.
+ *
+ * Returns:
+ *   User of the FM radio H:4 channel.
+ *   NULL if no user is registered.
+ */
+extern struct cg2900_device *cg2900_get_fm_radio_dev(void);
+
+/**
+ * cg2900_get_bt_audio_dev() - Return user of the BT audio H:4 channel.
+ *
+ * Returns:
+ *   User of the BT audio H:4 channel.
+ *   NULL if no user is registered.
+ */
+extern struct cg2900_device *cg2900_get_bt_audio_dev(void);
+
+/**
+ * cg2900_get_fm_audio_dev() - Return user of the FM audio H:4 channel.
+ *
+ * Returns:
+ *   User of the FM audio H:4 channel.
+ *   NULL if no user is registered.
+ */
+extern struct cg2900_device *cg2900_get_fm_audio_dev(void);
+
+/**
+ * cg2900_get_hci_logger_config() - Return HCI Logger configuration.
+ *
+ * Returns:
+ *   HCI logger configuration.
+ *   NULL if CG2900 Core has not yet been started.
+ */
+extern struct cg2900_hci_logger_config *cg2900_get_hci_logger_config(void);
+
+/**
+ * cg2900_get_sleep_timeout() - Return sleep timeout in jiffies.
+ *
+ * Returns:
+ *   Sleep timeout in jiffies. 0 means that sleep timeout shall not be used.
+ */
+extern unsigned long cg2900_get_sleep_timeout(void);
+
+/**
+ * cg2900_data_from_chip() - Data received from connectivity controller.
+ * @skb: Data packet
+ *
+ * The cg2900_data_from_chip() function checks which channel
+ * the data was received on and send to the right user.
+ */
+extern void cg2900_data_from_chip(struct sk_buff *skb);
+
+/* module_param declared in cg2900_core.c */
+extern u8 bd_address[BT_BDADDR_SIZE];
+extern int default_manufacturer;
+extern int default_hci_revision;
+extern int default_sub_version;
+
+#endif /* _CG2900_CORE_H_ */
diff --git a/drivers/mfd/cg2900/cg2900_debug.h
b/drivers/mfd/cg2900/cg2900_debug.h
new file mode 100644
index 0000000..c3d8fc8
--- /dev/null
+++ b/drivers/mfd/cg2900/cg2900_debug.h
@@ -0,0 +1,76 @@
+/*
+ * drivers/mfd/cg2900/cg2900_debug.h
+ *
+ * Copyright (C) ST-Ericsson SA 2010
+ * Authors:
+ * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for
ST-Ericsson.
+ * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson.
+ * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson.
+ * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson.
+ * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson.
+ * License terms:  GNU General Public License (GPL), version 2
+ *
+ * Debug functionality for the Linux Bluetooth HCI H:4 Driver for ST-Ericsson
+ * CG2900 connectivity controller.
+ */
+
+#ifndef _CG2900_DEBUG_H_
+#define _CG2900_DEBUG_H_
+
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+
+#define CG2900_DEFAULT_DEBUG_LEVEL 1
+
+/* module_param declared in cg2900_core.c */
+extern int cg2900_debug_level;
+
+#if defined(NDEBUG) || CG2900_DEFAULT_DEBUG_LEVEL == 0
+	#define CG2900_DBG_DATA_CONTENT(__prefix, __buf, __len)
+	#define CG2900_DBG_DATA(fmt, arg...)
+	#define CG2900_DBG(fmt, arg...)
+	#define CG2900_INFO(fmt, arg...)
+	#define CG2900_ERR(fmt, arg...)
+#else
+
+	#define CG2900_DBG_DATA_CONTENT(__prefix, __buf, __len)		\
+	do {								\
+		if (cg2900_debug_level >= 30)				\
+			print_hex_dump_bytes("CG2900 " __prefix ": " ,	\
+					     DUMP_PREFIX_NONE, __buf, __len); \
+	} while (0)
+
+	#define CG2900_DBG_DATA(fmt, arg...)				\
+	do {								\
+		if (cg2900_debug_level >= 25)				\
+			pr_debug("CG2900 %s: " fmt "\n" , __func__ , ## arg); \
+	} while (0)
+
+	#define CG2900_DBG(fmt, arg...)					\
+	do {								\
+		if (cg2900_debug_level >= 20)				\
+			pr_debug("CG2900 %s: " fmt "\n" , __func__ , ## arg); \
+	} while (0)
+
+	#define CG2900_INFO(fmt, arg...)				\
+	do {								\
+		if (cg2900_debug_level >= 10)				\
+			pr_info("CG2900: " fmt "\n" , ## arg);		\
+	} while (0)
+
+	#define CG2900_ERR(fmt, arg...)					\
+	do {								\
+		if (cg2900_debug_level >= 1)				\
+			pr_err("CG2900 %s: " fmt "\n" , __func__ , ## arg); \
+	} while (0)
+
+#endif /* NDEBUG */
+
+#define CG2900_SET_STATE(__name, __var, __new_state)			\
+do {									\
+	CG2900_DBG("New %s: 0x%X", __name, (uint32_t)__new_state);	\
+	__var = __new_state;						\
+} while (0)
+
+#endif /* _CG2900_DEBUG_H_ */
diff --git a/drivers/mfd/cg2900/hci_defines.h b/drivers/mfd/cg2900/hci_defines.h
new file mode 100644
index 0000000..6210e1b
--- /dev/null
+++ b/drivers/mfd/cg2900/hci_defines.h
@@ -0,0 +1,81 @@
+/*
+ * drivers/mfd/cg2900/hci_defines.h
+ *
+ * Copyright (C) ST-Ericsson SA 2010
+ * Authors:
+ * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for
ST-Ericsson.
+ * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson.
+ * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson.
+ * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson.
+ * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson.
+ * License terms:  GNU General Public License (GPL), version 2
+ *
+ * Linux Bluetooth HCI defines for ST-Ericsson CG2900 connectivity controller.
+ */
+
+#ifndef _BLUETOOTH_DEFINES_H_
+#define _BLUETOOTH_DEFINES_H_
+
+#include <linux/types.h>
+
+/* H:4 offset in an HCI packet */
+#define HCI_H4_POS				0
+#define HCI_H4_SIZE				1
+
+/* Standardized Bluetooth H:4 channels */
+#define HCI_BT_CMD_H4_CHANNEL			0x01
+#define HCI_BT_ACL_H4_CHANNEL			0x02
+#define HCI_BT_SCO_H4_CHANNEL			0x03
+#define HCI_BT_EVT_H4_CHANNEL			0x04
+
+/* Bluetooth Opcode Group Field (OGF) */
+#define HCI_BT_OGF_LINK_CTRL			0x01
+#define HCI_BT_OGF_LINK_POLICY			0x02
+#define HCI_BT_OGF_CTRL_BB			0x03
+#define HCI_BT_OGF_LINK_INFO			0x04
+#define HCI_BT_OGF_LINK_STATUS			0x05
+#define HCI_BT_OGF_LINK_TESTING			0x06
+#define HCI_BT_OGF_VS				0x3F
+
+/* Bluetooth Opcode Command Field (OCF) */
+#define HCI_BT_OCF_READ_LOCAL_VERSION_INFO	0x0001
+#define HCI_BT_OCF_RESET			0x0003
+
+/* Bluetooth HCI command OpCodes in LSB/MSB fashion */
+#define HCI_BT_RESET_CMD_LSB			0x03
+#define HCI_BT_RESET_CMD_MSB			0x0C
+#define HCI_BT_READ_LOCAL_VERSION_CMD_LSB	0x01
+#define HCI_BT_READ_LOCAL_VERSION_CMD_MSB	0x10
+
+/* Bluetooth Event OpCodes */
+#define HCI_BT_EVT_CMD_COMPLETE			0x0E
+#define HCI_BT_EVT_CMD_STATUS			0x0F
+
+/* Bluetooth Command offsets */
+#define HCI_BT_CMD_ID_POS			1
+#define HCI_BT_CMD_PARAM_LEN_POS		3
+#define HCI_BT_CMD_PARAM_POS			4
+#define HCI_BT_CMD_HDR_SIZE			4
+
+/* Bluetooth Event offsets for CG2900 users, i.e. not including H:4 channel */
+#define HCI_BT_EVT_ID_POS			0
+#define HCI_BT_EVT_LEN_POS			1
+#define HCI_BT_EVT_CMD_COMPL_ID_POS		3
+#define HCI_BT_EVT_CMD_STATUS_ID_POS		4
+#define HCI_BT_EVT_CMD_COMPL_STATUS_POS		5
+#define HCI_BT_EVT_CMD_STATUS_STATUS_POS	2
+#define HCI_BT_EVT_CMD_COMPL_NR_OF_PKTS_POS	2
+#define HCI_BT_EVT_CMD_STATUS_NR_OF_PKTS_POS	3
+
+/* Bluetooth error codes */
+#define HCI_BT_ERROR_NO_ERROR			0x00
+#define HCI_BT_ERROR_CMD_DISALLOWED		0x0C
+
+/* Bluetooth lengths */
+#define HCI_BT_SEND_FILE_MAX_CHUNK_SIZE		254
+
+#define HCI_BT_RESET_LEN			3
+#define HCI_BT_RESET_PARAM_LEN			0
+#define HCI_BT_CMD_COMPLETE_NO_PARAM_LEN	4
+
+#endif /* _BLUETOOTH_DEFINES_H_ */
diff --git a/include/linux/mfd/cg2900.h b/include/linux/mfd/cg2900.h
new file mode 100644
index 0000000..ca7d81b
--- /dev/null
+++ b/include/linux/mfd/cg2900.h
@@ -0,0 +1,187 @@
+/*
+ * include/linux/mfd/cg2900.h
+ *
+ * Copyright (C) ST-Ericsson SA 2010
+ * Authors:
+ * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for
ST-Ericsson.
+ * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson.
+ * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson.
+ * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson.
+ * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson.
+ * License terms:  GNU General Public License (GPL), version 2
+ *
+ * Linux Bluetooth HCI H:4 Driver for ST-Ericsson CG2900 connectivity
+ * controller.
+ */
+
+#ifndef _CG2900_H_
+#define _CG2900_H_
+
+#include <linux/skbuff.h>
+
+#define CG2900_MAX_NAME_SIZE 30
+
+/*
+ * Channel names to use when registering to CG2900 driver
+ */
+
+/** CG2900_BT_CMD - Bluetooth HCI H4 channel for Bluetooth commands.
+ */
+#define CG2900_BT_CMD		"cg2900_bt_cmd"
+
+/** CG2900_BT_ACL - Bluetooth HCI H4 channel for Bluetooth ACL data.
+ */
+#define CG2900_BT_ACL		"cg2900_bt_acl"
+
+/** CG2900_BT_EVT - Bluetooth HCI H4 channel for Bluetooth events.
+ */
+#define CG2900_BT_EVT		"cg2900_bt_evt"
+
+/** CG2900_FM_RADIO - Bluetooth HCI H4 channel for FM radio.
+ */
+#define CG2900_FM_RADIO		"cg2900_fm_radio"
+
+/** CG2900_GNSS - Bluetooth HCI H4 channel for GNSS.
+ */
+#define CG2900_GNSS		"cg2900_gnss"
+
+/** CG2900_DEBUG - Bluetooth HCI H4 channel for internal debug data.
+ */
+#define CG2900_DEBUG		"cg2900_debug"
+
+/** CG2900_STE_TOOLS - Bluetooth HCI H4 channel for development tools data.
+ */
+#define CG2900_STE_TOOLS	"cg2900_ste_tools"
+
+/** CG2900_HCI_LOGGER - BT channel for logging all transmitted H4 packets.
+ * Data read is copy of all data transferred on the other channels.
+ * Only write allowed is configuration of the HCI Logger.
+ */
+#define CG2900_HCI_LOGGER	"cg2900_hci_logger"
+
+/** CG2900_US_CTRL - Channel for user space init and control of CG2900.
+ */
+#define CG2900_US_CTRL		"cg2900_us_ctrl"
+
+/** CG2900_BT_AUDIO - HCI Channel for BT audio configuration commands.
+ * Maps to Bluetooth command and event channels.
+ */
+#define CG2900_BT_AUDIO		"cg2900_bt_audio"
+
+/** CG2900_FM_RADIO_AUDIO - HCI channel for FM audio configuration commands.
+ * Maps to FM Radio channel.
+ */
+#define CG2900_FM_RADIO_AUDIO	"cg2900_fm_audio"
+
+/** CG2900_CORE- Channel for keeping ST-Ericsson CG2900 enabled.
+ * Opening this channel forces the chip to stay powered.
+ * No data can be written to or read from this channel.
+ */
+#define CG2900_CORE		"cg2900_core"
+
+struct cg2900_callbacks;
+
+/**
+ * struct cg2900_device - Device structure for CG2900 user.
+ * @h4_channel:		HCI H:4 channel used by this device.
+ * @cb:			Callback functions registered by this device.
+ * @logger_enabled:	true if HCI logger is enabled for this channel,
+ *			false otherwise.
+ * @user_data:		Arbitrary data used by caller.
+ * @dev:		Parent device this driver is connected to.
+ *
+ * Defines data needed to access an HCI channel.
+ */
+struct cg2900_device {
+	int				h4_channel;
+	struct cg2900_callbacks		*cb;
+	bool				logger_enabled;
+	void				*user_data;
+	struct device			*dev;
+};
+
+/**
+ * struct cg2900_callbacks - Callback structure for CG2900 user.
+ * @read_cb:	Callback function called when data is received from
+ *		the connectivity controller.
+ * @reset_cb:	Callback function called when the connectivity controller has
+ *		been reset.
+ *
+ * Defines the callback functions provided from the caller.
+ */
+struct cg2900_callbacks {
+	void (*read_cb) (struct cg2900_device *dev, struct sk_buff *skb);
+	void (*reset_cb) (struct cg2900_device *dev);
+};
+
+/**
+ * struct cg2900_rev_data - Contains revision data for the local controller.
+ * @revision:		Revision of the controller, e.g. to indicate that it is
+ *			a CG2900 controller.
+ * @sub_version:	Subversion of the controller, e.g. to indicate a certain
+ *			tape-out of the controller.
+ *
+ * The values to match retrieved values to each controller may be
retrieved from
+ * the manufacturer.
+ */
+struct cg2900_rev_data {
+	int revision;
+	int sub_version;
+};
+
+/**
+ * struct cg2900_platform_data - Contains platform data for CG2900.
+ * @init:		Callback called upon system start.
+ * @exit:		Callback called upon system shutdown.
+ * @enable_chip:	Callback called for enabling CG2900 chip.
+ * @disable_chip:	Callback called for disabling CG2900 chip.
+ * @set_hci_revision:	Callback called when HCI revision has been detected.
+ * @get_power_switch_off_cmd:	Callback called to retrieve
+ *				HCI VS_Power_Switch_Off command (command
+ *				HCI requires platform specific GPIO data).
+ * @bus:		Transport used, see @include/net/bluetooth/hci.h.
+ * @cts_irq:		Interrupt for the UART CTS pin.
+ * @enable_uart:	Callback called when switching from UART GPIO to
+ *			UART HW.
+ * @disable_uart:	Callback called when switching from UART HW to
+ *			UART GPIO.
+ * @uart:		Platform data structure for UART transport.
+ *
+ * Any callback may be NULL if not needed.
+ */
+struct cg2900_platform_data {
+	int (*init)(void);
+	void (*exit)(void);
+	void (*enable_chip)(void);
+	void (*disable_chip)(void);
+	void (*set_hci_revision)(u8 hci_version, u16 hci_revision,
+				 u8 lmp_version, u8 lmp_subversion,
+				 u16 manufacturer);
+	struct sk_buff* (*get_power_switch_off_cmd)(u16 *op_code);
+
+	__u8 bus;
+
+	struct {
+		int cts_irq;
+		int (*enable_uart)(void);
+		int (*disable_uart)(void);
+	} uart;
+};
+
+/**
+ * struct cg2900_bt_platform_data - Contains platform data for CG2900
Bluetooth.
+ * @bus:	Transport used, see @include/net/bluetooth/hci.h.
+ */
+struct cg2900_bt_platform_data {
+	__u8 bus;
+};
+
+extern struct cg2900_device *cg2900_register_user(char *name,
+						  struct cg2900_callbacks *cb);
+extern void cg2900_deregister_user(struct cg2900_device *dev);
+extern int cg2900_reset(struct cg2900_device *dev);
+extern struct sk_buff *cg2900_alloc_skb(unsigned int size, gfp_t priority);
+extern int cg2900_write(struct cg2900_device *dev, struct sk_buff *skb);
+extern bool cg2900_get_local_revision(struct cg2900_rev_data *rev_data);
+
+#endif /* _CG2900_H_ */
-- 
1.6.3.3

^ permalink raw reply related

* [PATCH 0/9] Add support for ST-Ericsson CG2900
From: Par-Gunnar Hjalmdahl @ 2010-10-22 10:34 UTC (permalink / raw)
  To: linux-bluetooth, linux-kernel, linus.walleij

This is the second patch set adding support to the ST-Ericsson CG2900
Connectivity Combo controller.
CG2900 is a controller supporting GPS, Bluetooth, and FM RX/TX. More info can be
found at http://www.stericsson.com/product/223489.jsp

Compared to the first patch set, this patch set includes use of MFD cells and
proper use of platform device registration. It also addresses comments received
for the first patch set.

Par-Gunnar Hjalmdahl (9):
 MFD: Add support for the ST-Ericsson CG2900.
 MFD: Add char devices for the ST-Ericsson CG2900.
 MFD: Add chip handler for the ST-Ericsson CG2900.
 MFD: Add chip handler for the ST-Ericsson STLC2690.
 MFD: Add UART support for the ST-Ericsson CG2900.
 MFD: Add support for the ST-Ericsson CG2900 Audio.
 arch/arm/mach-ux500: Add support for the ST-Ericsson CG2900.
 Bluetooth: Add support for the ST-Ericsson CG2900.
 DocBook: Add ST-Ericsson CG2900 docs

Documentation/DocBook/Makefile             |    2 +-
Documentation/DocBook/cg2900.tmpl          | 1332 ++++++++++++
arch/arm/mach-ux500/Makefile               |    1 +
arch/arm/mach-ux500/board-mop500.c         |    3 +
arch/arm/mach-ux500/devices-cg2900.c       |  261 +++
arch/arm/mach-ux500/include/mach/devices.h |    4 +
drivers/bluetooth/Kconfig                  |    7 +
drivers/bluetooth/Makefile                 |    1 +
drivers/bluetooth/btcg2900.c               |  925 +++++++++
drivers/mfd/Kconfig                        |   35 +
drivers/mfd/Makefile                       |    3 +
drivers/mfd/cg2900/Makefile                |   16 +
drivers/mfd/cg2900/cg2900_audio.c          | 3026 ++++++++++++++++++++++++++++
drivers/mfd/cg2900/cg2900_char_devices.c   |  745 +++++++
drivers/mfd/cg2900/cg2900_chip.c           | 2238 ++++++++++++++++++++
drivers/mfd/cg2900/cg2900_chip.h           |  588 ++++++
drivers/mfd/cg2900/cg2900_core.c           | 2401 ++++++++++++++++++++++
drivers/mfd/cg2900/cg2900_core.h           |  303 +++
drivers/mfd/cg2900/cg2900_debug.h          |   76 +
drivers/mfd/cg2900/cg2900_uart.c           | 1851 +++++++++++++++++
drivers/mfd/cg2900/hci_defines.h           |   81 +
drivers/mfd/cg2900/stlc2690_chip.c         | 1150 +++++++++++
drivers/mfd/cg2900/stlc2690_chip.h         |   37 +
include/linux/mfd/cg2900.h                 |  187 ++
include/linux/mfd/cg2900_audio.h           |  460 +++++
25 files changed, 15732 insertions(+), 1 deletions(-)
create mode 100644 Documentation/DocBook/cg2900.tmpl
create mode 100644 arch/arm/mach-ux500/devices-cg2900.c
create mode 100644 drivers/bluetooth/btcg2900.c
create mode 100644 drivers/mfd/cg2900/Makefile
create mode 100644 drivers/mfd/cg2900/cg2900_audio.c
create mode 100644 drivers/mfd/cg2900/cg2900_char_devices.c
create mode 100644 drivers/mfd/cg2900/cg2900_chip.c
create mode 100644 drivers/mfd/cg2900/cg2900_chip.h
create mode 100644 drivers/mfd/cg2900/cg2900_core.c
create mode 100644 drivers/mfd/cg2900/cg2900_core.h
create mode 100644 drivers/mfd/cg2900/cg2900_debug.h
create mode 100644 drivers/mfd/cg2900/cg2900_uart.c
create mode 100644 drivers/mfd/cg2900/hci_defines.h
create mode 100644 drivers/mfd/cg2900/stlc2690_chip.c
create mode 100644 drivers/mfd/cg2900/stlc2690_chip.h
create mode 100644 include/linux/mfd/cg2900.h
create mode 100644 include/linux/mfd/cg2900_audio.h

^ permalink raw reply

* [PATCH] Fix crash when GetProperties req is received before any adapters are set up
From: ext-tommi.keisala @ 2010-10-22 10:32 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Tommi Keisala
In-Reply-To: <1287743558-17002-1-git-send-email-ext-tommi.keisala@nokia.com>

From: Tommi Keisala <ext-tommi.keisala@nokia.com>

This patch avoids a crash when org.bluez.Manager GetProperties request is
received and there is not yet any adapters ready. Happens often for example
when bluetoothd and ofonod is started next ot each other.

Signed-off-by: Tommi Keisala <ext-tommi.keisala@nokia.com>
---
 src/manager.c |    6 +++++-
 1 files changed, 5 insertions(+), 1 deletions(-)

diff --git a/src/manager.c b/src/manager.c
index aff069c..8967691 100644
--- a/src/manager.c
+++ b/src/manager.c
@@ -184,6 +184,7 @@ static DBusMessage *get_properties(DBusConnection *conn,
 	GSList *list;
 	char **array;
 	int i;
+	int n_elements;
 
 	reply = dbus_message_new_method_return(msg);
 	if (!reply)
@@ -197,6 +198,7 @@ static DBusMessage *get_properties(DBusConnection *conn,
 			DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
 
 	array = g_new0(char *, g_slist_length(adapters) + 1);
+	n_elements = 0;
 	for (i = 0, list = adapters; list; list = list->next, i++) {
 		struct btd_adapter *adapter = list->data;
 
@@ -204,8 +206,10 @@ static DBusMessage *get_properties(DBusConnection *conn,
 			continue;
 
 		array[i] = (char *) adapter_get_path(adapter);
+		n_elements++;
 	}
-	dict_append_array(&dict, "Adapters", DBUS_TYPE_OBJECT_PATH, &array, i);
+	dict_append_array(&dict, "Adapters", DBUS_TYPE_OBJECT_PATH, &array,
+			n_elements);
 	g_free(array);
 
 	dbus_message_iter_close_container(&iter, &dict);
-- 
1.7.0.4


^ permalink raw reply related

* [PATCH] Fix crash when GetProperties req is received before any adapters are set up
From: ext-tommi.keisala @ 2010-10-22 10:32 UTC (permalink / raw)
  To: linux-bluetooth

In-Reply-To: 


We have observed bluetoothd crashing on startup and I traced the issue being dict_append_array call with empty array but elements_n being 1.

^ permalink raw reply

* Regarding BLUEZ WIKI
From: steven bluez @ 2010-10-22  9:56 UTC (permalink / raw)
  To: Marcel Holtmann, Gustavo F. Padovan"; +Cc: Suraj Sumangala, linux-bluetooth

Hi Marcel,
        There was supposed to be a page "wiki.bluez.org" its missing
?. Its been moved or deleted??

Regards,
   Steven

^ permalink raw reply

* [PATCH 2/2 v2] Bluetooth: Fix system crash bug of no send queue protect
From: Haijun Liu @ 2010-10-22  2:26 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: padovan, Haijun Liu
In-Reply-To: <1287714419-13545-1-git-send-email-haijun.liu@atheros.com>

During test session with another vendor's bt stack, found that
without lock protect for TX_QUEUE(sk) will cause system crash while
data transfer over AMP controller. So I just add lock protect for
TX_QUEUE(sk).

Signed-off-by: Haijun Liu <haijun.liu@atheros.com>
---
 include/net/bluetooth/l2cap.h |    1 +
 net/bluetooth/l2cap.c         |   25 +++++++++++++++++++++----
 2 files changed, 22 insertions(+), 4 deletions(-)

diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
index c819c8b..276c7ea 100644
--- a/include/net/bluetooth/l2cap.h
+++ b/include/net/bluetooth/l2cap.h
@@ -304,6 +304,7 @@ struct sock_del_list {
 #define SREJ_QUEUE(sk) (&l2cap_pi(sk)->srej_queue)
 #define BUSY_QUEUE(sk) (&l2cap_pi(sk)->busy_queue)
 #define SREJ_LIST(sk) (&l2cap_pi(sk)->srej_l.list)
+#define TX_QUEUE_LOCK(sk) (&l2cap_pi(sk)->tx_queue.lock)
 
 struct srej_list {
 	__u8	tx_seq;
diff --git a/net/bluetooth/l2cap.c b/net/bluetooth/l2cap.c
index 879f386..153614f 100644
--- a/net/bluetooth/l2cap.c
+++ b/net/bluetooth/l2cap.c
@@ -1432,16 +1432,18 @@ static void l2cap_drop_acked_frames(struct sock *sk)
 {
 	struct sk_buff *skb;
 
+	spin_lock_bh(TX_QUEUE_LOCK(sk));
 	while ((skb = skb_peek(TX_QUEUE(sk))) &&
 			l2cap_pi(sk)->unacked_frames) {
 		if (bt_cb(skb)->tx_seq == l2cap_pi(sk)->expected_ack_seq)
 			break;
 
-		skb = skb_dequeue(TX_QUEUE(sk));
+		skb = __skb_dequeue(TX_QUEUE(sk));
 		kfree_skb(skb);
 
 		l2cap_pi(sk)->unacked_frames--;
 	}
+	spin_unlock_bh(TX_QUEUE_LOCK(sk));
 
 	if (!l2cap_pi(sk)->unacked_frames)
 		del_timer(&l2cap_pi(sk)->retrans_timer);
@@ -1484,18 +1486,24 @@ static void l2cap_retransmit_one_frame(struct sock *sk, u8 tx_seq)
 	struct sk_buff *skb, *tx_skb;
 	u16 control, fcs;
 
+	spin_lock_bh(TX_QUEUE_LOCK(sk));
 	skb = skb_peek(TX_QUEUE(sk));
-	if (!skb)
+	if (!skb) {
+		spin_unlock_bh(TX_QUEUE_LOCK(sk));
 		return;
+	}
 
 	do {
 		if (bt_cb(skb)->tx_seq == tx_seq)
 			break;
 
-		if (skb_queue_is_last(TX_QUEUE(sk), skb))
+		if (skb_queue_is_last(TX_QUEUE(sk), skb)) {
+			spin_unlock_bh(TX_QUEUE_LOCK(sk));
 			return;
+		}
 
 	} while ((skb = skb_queue_next(TX_QUEUE(sk), skb)));
+	spin_unlock_bh(TX_QUEUE_LOCK(sk));
 
 	if (pi->remote_max_tx &&
 			bt_cb(skb)->retries == pi->remote_max_tx) {
@@ -1535,12 +1543,14 @@ static int l2cap_ertm_send(struct sock *sk)
 	if (sk->sk_state != BT_CONNECTED)
 		return -ENOTCONN;
 
+	spin_lock_bh(TX_QUEUE_LOCK(sk));
 	while ((skb = sk->sk_send_head) && (!l2cap_tx_window_full(sk))) {
 
 		if (pi->remote_max_tx &&
 				bt_cb(skb)->retries == pi->remote_max_tx) {
+			spin_unlock_bh(TX_QUEUE_LOCK(sk));
 			l2cap_send_disconn_req(pi->conn, sk, ECONNABORTED);
-			break;
+			return nsent;
 		}
 
 		tx_skb = skb_clone(skb, GFP_ATOMIC);
@@ -1581,6 +1591,7 @@ static int l2cap_ertm_send(struct sock *sk)
 
 		nsent++;
 	}
+	spin_unlock_bh(TX_QUEUE_LOCK(sk));
 
 	return nsent;
 }
@@ -1590,8 +1601,10 @@ static int l2cap_retransmit_frames(struct sock *sk)
 	struct l2cap_pinfo *pi = l2cap_pi(sk);
 	int ret;
 
+	spin_lock_bh(TX_QUEUE_LOCK(sk));
 	if (!skb_queue_empty(TX_QUEUE(sk)))
 		sk->sk_send_head = TX_QUEUE(sk)->next;
+	spin_unlock_bh(TX_QUEUE_LOCK(sk));
 
 	pi->next_tx_seq = pi->expected_ack_seq;
 	ret = l2cap_ertm_send(sk);
@@ -1806,9 +1819,11 @@ static inline int l2cap_sar_segment_sdu(struct sock *sk, struct msghdr *msg, siz
 		len -= buflen;
 		size += buflen;
 	}
+	spin_lock_bh(TX_QUEUE_LOCK(sk));
 	skb_queue_splice_tail(&sar_queue, TX_QUEUE(sk));
 	if (sk->sk_send_head == NULL)
 		sk->sk_send_head = sar_queue.next;
+	spin_unlock_bh(TX_QUEUE_LOCK(sk));
 
 	return size;
 }
@@ -1878,10 +1893,12 @@ static int l2cap_sock_sendmsg(struct kiocb *iocb, struct socket *sock, struct ms
 				err = PTR_ERR(skb);
 				goto done;
 			}
+			spin_lock_bh(TX_QUEUE_LOCK(sk));
 			__skb_queue_tail(TX_QUEUE(sk), skb);
 
 			if (sk->sk_send_head == NULL)
 				sk->sk_send_head = skb;
+			spin_unlock_bh(TX_QUEUE_LOCK(sk));
 
 		} else {
 		/* Segment SDU into multiples PDUs */
-- 
1.6.3.3


^ permalink raw reply related

* [PATCH 1/2 v2] Bluetooth: Fix system crash caused by del_timer()
From: Haijun Liu @ 2010-10-22  2:26 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: padovan, Haijun Liu

During test session with another vendor's bt stack, found that in
l2cap_chan_del() using del_timer() caused l2cap_monitor_timeout()
be called after the sock was freed, so it raised a system crash.
So I just replaced del_timer() with del_timer_sync() to solve it.

Signed-off-by: Haijun Liu <haijun.liu@atheros.com>
---
 net/bluetooth/l2cap.c |    6 +++---
 1 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/net/bluetooth/l2cap.c b/net/bluetooth/l2cap.c
index 6f931cc..879f386 100644
--- a/net/bluetooth/l2cap.c
+++ b/net/bluetooth/l2cap.c
@@ -283,9 +283,9 @@ static void l2cap_chan_del(struct sock *sk, int err)
 	if (l2cap_pi(sk)->mode == L2CAP_MODE_ERTM) {
 		struct srej_list *l, *tmp;
 
-		del_timer(&l2cap_pi(sk)->retrans_timer);
-		del_timer(&l2cap_pi(sk)->monitor_timer);
-		del_timer(&l2cap_pi(sk)->ack_timer);
+		del_timer_sync(&l2cap_pi(sk)->retrans_timer);
+		del_timer_sync(&l2cap_pi(sk)->monitor_timer);
+		del_timer_sync(&l2cap_pi(sk)->ack_timer);
 
 		skb_queue_purge(SREJ_QUEUE(sk));
 		skb_queue_purge(BUSY_QUEUE(sk));
-- 
1.6.3.3


^ permalink raw reply related

* Re: AVRCP 1.4 : Future on Target Role
From: David Stockwell @ 2010-10-21 21:27 UTC (permalink / raw)
  To: Waldemar.Rymarkiewicz, ag.shivendra, linux-bluetooth
In-Reply-To: <99B09243E1A5DA4898CDD8B700111448097D287CCF@EXMB04.eu.tieto.com>

Hi Waldek,

> Hi David,
>
>>Regarding an AVRCP device, I have not found any on the market.
>
> There are already devices with AVRCP 1.4 on the market. See 
> https://www.bluetooth.org/tpg/listings.cfm  for listing (use advanced 
> searching).

Thanks...I should have looked there, and will do so.

>
> Reagrads
> /Waldek
>
> 

^ permalink raw reply

* [PATCH] bluetooth: Fix NULL pointer dereference issue
From: Yuri Ershov @ 2010-10-21 16:08 UTC (permalink / raw)
  To: marcel, davem, padovan, jprvita
  Cc: linux-bluetooth, ville.tervo, andrei.emeltchenko, Yuri Ershov

This patch fixes NULL pointer dereference at running test with
connect-transfer-disconnect in loop. Sometimes sk_state is 
BT_CLOSED and sk_refcnt equal to 0, so there is oops in 
bt_accept_unlink. In normal case removed block is not used.

Signed-off-by: Yuri Ershov <ext-yuri.ershov@nokia.com>
---
 net/bluetooth/af_bluetooth.c |    7 -------
 1 files changed, 0 insertions(+), 7 deletions(-)

diff --git a/net/bluetooth/af_bluetooth.c b/net/bluetooth/af_bluetooth.c
index 421c45b..47c107e 100644
--- a/net/bluetooth/af_bluetooth.c
+++ b/net/bluetooth/af_bluetooth.c
@@ -204,13 +204,6 @@ struct sock *bt_accept_dequeue(struct sock *parent, struct socket *newsock)
 
 		lock_sock(sk);
 
-		/* FIXME: Is this check still needed */
-		if (sk->sk_state == BT_CLOSED) {
-			release_sock(sk);
-			bt_accept_unlink(sk);
-			continue;
-		}
-
 		if (sk->sk_state == BT_CONNECTED || !newsock ||
 						bt_sk(parent)->defer_setup) {
 			bt_accept_unlink(sk);
-- 
1.6.3.3

^ permalink raw reply related

* [PATCH 11/22] pcmcia: use autoconfiguration feature for ioports and iomem
From: Dominik Brodowski @ 2010-10-21 15:35 UTC (permalink / raw)
  To: linux-pcmcia
  Cc: Dominik Brodowski, netdev, linux-wireless, linux-ide, linux-usb,
	laforge, linux-mtd, linux-bluetooth, alsa-devel, linux-serial,
	Jiri Kosina, linux-scsi
In-Reply-To: <20101021153450.GA12405@comet.dominikbrodowski.net>

When CONF_AUTO_SET_IO or CONF_AUTO_SET_IOMEM are set, the corresponding
fields in struct pcmcia_device *p_dev->resource[0,1,2] are set
accordinly. Drivers wishing to override certain settings may do so in
the callback function, but they no longer need to parse the CIS entries
stored in cistpl_cftable_entry_t themselves.

CC: netdev@vger.kernel.org
CC: linux-wireless@vger.kernel.org
CC: linux-ide@vger.kernel.org
CC: linux-usb@vger.kernel.org
CC: laforge@gnumonks.org
CC: linux-mtd@lists.infradead.org
CC: linux-bluetooth@vger.kernel.org
CC: alsa-devel@alsa-project.org
CC: linux-serial@vger.kernel.org
CC: Jiri Kosina <jkosina@suse.cz>
CC: linux-scsi@vger.kernel.org
Tested-by: Wolfram Sang <w.sang@pengutronix.de>
Signed-off-by: Dominik Brodowski <linux@dominikbrodowski.net>
---
 Documentation/pcmcia/driver-changes.txt          |   12 ++
 drivers/ata/pata_pcmcia.c                        |   81 +++++---------
 drivers/bluetooth/bt3c_cs.c                      |   53 +++++-----
 drivers/bluetooth/btuart_cs.c                    |   51 +++++----
 drivers/bluetooth/dtl1_cs.c                      |   18 +--
 drivers/char/pcmcia/cm4000_cs.c                  |   15 +--
 drivers/char/pcmcia/cm4040_cs.c                  |   22 +---
 drivers/char/pcmcia/ipwireless/main.c            |   26 +---
 drivers/char/pcmcia/synclink_cs.c                |   16 +--
 drivers/ide/ide-cs.c                             |   82 ++++---------
 drivers/isdn/hardware/avm/avm_cs.c               |   19 +---
 drivers/isdn/hisax/avma1_cs.c                    |   22 +---
 drivers/isdn/hisax/elsa_cs.c                     |   23 +---
 drivers/isdn/hisax/sedlbauer_cs.c                |   38 +-----
 drivers/isdn/hisax/teles_cs.c                    |   16 +--
 drivers/net/pcmcia/axnet_cs.c                    |   32 +----
 drivers/net/pcmcia/fmvj18x_cs.c                  |    5 +-
 drivers/net/pcmcia/pcnet_cs.c                    |   38 ++-----
 drivers/net/pcmcia/smc91c92_cs.c                 |   39 +++----
 drivers/net/pcmcia/xirc2ps_cs.c                  |   69 ++++++-----
 drivers/net/wireless/airo_cs.c                   |   34 +-----
 drivers/net/wireless/atmel_cs.c                  |   28 +----
 drivers/net/wireless/hostap/hostap_cs.c          |   43 +-------
 drivers/net/wireless/libertas/if_cs.c            |   16 +--
 drivers/net/wireless/orinoco/orinoco_cs.c        |   41 +------
 drivers/net/wireless/orinoco/spectrum_cs.c       |   45 +-------
 drivers/parport/parport_cs.c                     |   34 ++----
 drivers/pcmcia/pcmcia_cis.c                      |   78 +++++++++++--
 drivers/scsi/pcmcia/aha152x_stub.c               |   34 +++---
 drivers/scsi/pcmcia/fdomain_stub.c               |   13 +--
 drivers/scsi/pcmcia/nsp_cs.c                     |   47 ++------
 drivers/scsi/pcmcia/qlogic_stub.c                |   13 +--
 drivers/scsi/pcmcia/sym53c500_cs.c               |   13 +--
 drivers/serial/serial_cs.c                       |  132 +++++++++++-----------
 drivers/staging/comedi/drivers/cb_das16_cs.c     |   31 +-----
 drivers/staging/comedi/drivers/das08_cs.c        |   33 +-----
 drivers/staging/comedi/drivers/ni_daq_700.c      |   32 +-----
 drivers/staging/comedi/drivers/ni_daq_dio24.c    |   32 +-----
 drivers/staging/comedi/drivers/ni_labpc_cs.c     |   32 +-----
 drivers/staging/comedi/drivers/ni_mio_cs.c       |   14 +--
 drivers/staging/comedi/drivers/quatech_daqp_cs.c |   32 +-----
 drivers/telephony/ixj_pcmcia.c                   |   32 ++----
 drivers/usb/host/sl811_cs.c                      |   28 +----
 include/pcmcia/ds.h                              |   30 ++----
 44 files changed, 517 insertions(+), 1027 deletions(-)

diff --git a/Documentation/pcmcia/driver-changes.txt b/Documentation/pcmcia/driver-changes.txt
index 62a029f..dd04361 100644
--- a/Documentation/pcmcia/driver-changes.txt
+++ b/Documentation/pcmcia/driver-changes.txt
@@ -1,4 +1,16 @@
 This file details changes in 2.6 which affect PCMCIA card driver authors:
+* pcmcia_loop_config() and autoconfiguration (as of 2.6.36)
+   If struct pcmcia_device *p_dev->config_flags is set accordingly,
+   pcmcia_loop_config() now sets up certain configuration values
+   automatically, though the driver may still override the settings
+   in the callback function. The following autoconfiguration options
+   are provided at the moment:
+	CONF_AUTO_CHECK_VCC : check for matching Vcc
+	CONF_AUTO_SET_VPP   : set Vpp
+	CONF_AUTO_AUDIO     : auto-enable audio line, if required
+	CONF_AUTO_SET_IO    : set ioport resources (->resource[0,1])
+	CONF_AUTO_SET_IOMEM : set first iomem resource (->resource[2])
+
 * pcmcia_request_configuration -> pcmcia_enable_device (as of 2.6.36)
    pcmcia_request_configuration() got renamed to pcmcia_enable_device(),
    as it mirrors pcmcia_disable_device(). Configuration settings are now
diff --git a/drivers/ata/pata_pcmcia.c b/drivers/ata/pata_pcmcia.c
index 954f43c..88cb03c 100644
--- a/drivers/ata/pata_pcmcia.c
+++ b/drivers/ata/pata_pcmcia.c
@@ -167,45 +167,26 @@ static struct ata_port_operations pcmcia_8bit_port_ops = {
 };
 
 
-struct pcmcia_config_check {
-	unsigned long ctl_base;
-	int is_kme;
-};
-
-static int pcmcia_check_one_config(struct pcmcia_device *pdev,
-				   cistpl_cftable_entry_t *cfg,
-				   cistpl_cftable_entry_t *dflt,
-				   void *priv_data)
+static int pcmcia_check_one_config(struct pcmcia_device *pdev, void *priv_data)
 {
-	struct pcmcia_config_check *stk = priv_data;
-
-	if ((cfg->io.nwin > 0) || (dflt->io.nwin > 0)) {
-		cistpl_io_t *io = (cfg->io.nwin) ? &cfg->io : &dflt->io;
-		pdev->io_lines = io->flags & CISTPL_IO_LINES_MASK;
-		pdev->resource[0]->start = io->win[0].base;
-		if (!(io->flags & CISTPL_IO_16BIT)) {
-			pdev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
-			pdev->resource[0]->flags |= IO_DATA_PATH_WIDTH_8;
-		}
-		if (io->nwin == 2) {
-			pdev->resource[0]->end = 8;
-			pdev->resource[1]->start = io->win[1].base;
-			pdev->resource[1]->end = (stk->is_kme) ? 2 : 1;
-			if (pcmcia_request_io(pdev) != 0)
-				return -ENODEV;
-			stk->ctl_base = pdev->resource[1]->start;
-		} else if ((io->nwin == 1) && (io->win[0].len >= 16)) {
-			pdev->resource[0]->end = io->win[0].len;
-			pdev->resource[1]->end = 0;
-			if (pcmcia_request_io(pdev) != 0)
-				return -ENODEV;
-			stk->ctl_base = pdev->resource[0]->start + 0x0e;
-		} else
+	int *is_kme = priv_data;
+
+	if (!(pdev->resource[0]->flags & IO_DATA_PATH_WIDTH_8)) {
+		pdev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
+		pdev->resource[0]->flags |= IO_DATA_PATH_WIDTH_AUTO;
+	}
+	pdev->resource[1]->flags &= ~IO_DATA_PATH_WIDTH;
+	pdev->resource[1]->flags |= IO_DATA_PATH_WIDTH_8;
+
+	if (pdev->resource[1]->end) {
+		pdev->resource[0]->end = 8;
+		pdev->resource[1]->end = (*is_kme) ? 2 : 1;
+	} else {
+		if (pdev->resource[0]->end < 16)
 			return -ENODEV;
-		/* If we've got this far, we're done */
-		return 0;
 	}
-	return -ENODEV;
+
+	return pcmcia_request_io(pdev);
 }
 
 /**
@@ -220,7 +201,6 @@ static int pcmcia_init_one(struct pcmcia_device *pdev)
 {
 	struct ata_host *host;
 	struct ata_port *ap;
-	struct pcmcia_config_check *stk = NULL;
 	int is_kme = 0, ret = -ENOMEM, p;
 	unsigned long io_base, ctl_base;
 	void __iomem *io_addr, *ctl_addr;
@@ -228,10 +208,8 @@ static int pcmcia_init_one(struct pcmcia_device *pdev)
 	struct ata_port_operations *ops = &pcmcia_port_ops;
 
 	/* Set up attributes in order to probe card and get resources */
-	pdev->resource[0]->flags |= IO_DATA_PATH_WIDTH_AUTO;
-	pdev->resource[1]->flags |= IO_DATA_PATH_WIDTH_8;
-	pdev->config_flags |= CONF_ENABLE_IRQ;
-	pdev->config_flags |= CONF_AUTO_SET_VPP | CONF_AUTO_CHECK_VCC;
+	pdev->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_IO |
+		CONF_AUTO_SET_VPP | CONF_AUTO_CHECK_VCC;
 
 	/* See if we have a manufacturer identifier. Use it to set is_kme for
 	   vendor quirks */
@@ -239,21 +217,17 @@ static int pcmcia_init_one(struct pcmcia_device *pdev)
 		  ((pdev->card_id == PRODID_KME_KXLC005_A) ||
 		   (pdev->card_id == PRODID_KME_KXLC005_B)));
 
-	/* Allocate resoure probing structures */
-
-	stk = kzalloc(sizeof(*stk), GFP_KERNEL);
-	if (!stk)
-		goto out1;
-	stk->is_kme = is_kme;
-	io_base = ctl_base = 0;
-
-	if (pcmcia_loop_config(pdev, pcmcia_check_one_config, stk)) {
+	if (pcmcia_loop_config(pdev, pcmcia_check_one_config, &is_kme)) {
 		pdev->config_flags &= ~CONF_AUTO_CHECK_VCC;
-		if (pcmcia_loop_config(pdev, pcmcia_check_one_config, stk))
+		if (pcmcia_loop_config(pdev, pcmcia_check_one_config, &is_kme))
 			goto failed; /* No suitable config found */
 	}
 	io_base = pdev->resource[0]->start;
-	ctl_base = stk->ctl_base;
+	if (pdev->resource[1]->end)
+		ctl_base = pdev->resource[1]->start;
+	else
+		ctl_base = pdev->resource[0]->start + 0x0e;
+
 	if (!pdev->irq)
 		goto failed;
 
@@ -310,13 +284,10 @@ static int pcmcia_init_one(struct pcmcia_device *pdev)
 		goto failed;
 
 	pdev->priv = host;
-	kfree(stk);
 	return 0;
 
 failed:
-	kfree(stk);
 	pcmcia_disable_device(pdev);
-out1:
 	return ret;
 }
 
diff --git a/drivers/bluetooth/bt3c_cs.c b/drivers/bluetooth/bt3c_cs.c
index 97338a3..8b8be35 100644
--- a/drivers/bluetooth/bt3c_cs.c
+++ b/drivers/bluetooth/bt3c_cs.c
@@ -656,10 +656,8 @@ static int bt3c_probe(struct pcmcia_device *link)
 	info->p_dev = link;
 	link->priv = info;
 
-	link->resource[0]->flags |= IO_DATA_PATH_WIDTH_8;
-	link->resource[0]->end = 8;
-
-	link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_VPP;
+	link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_VPP |
+		CONF_AUTO_SET_IO;
 
 	return bt3c_config(link);
 }
@@ -673,38 +671,41 @@ static void bt3c_detach(struct pcmcia_device *link)
 	kfree(info);
 }
 
-static int bt3c_check_config(struct pcmcia_device *p_dev,
-			     cistpl_cftable_entry_t *cf,
-			     cistpl_cftable_entry_t *dflt,
-			     void *priv_data)
+static int bt3c_check_config(struct pcmcia_device *p_dev, void *priv_data)
 {
-	unsigned long try = (unsigned long) priv_data;
-	p_dev->io_lines = (try == 0) ? 16 : cf->io.flags & CISTPL_IO_LINES_MASK;
+	int *try = priv_data;
 
-	if ((cf->io.nwin > 0) && (cf->io.win[0].len == 8) &&
-	    (cf->io.win[0].base != 0)) {
-		p_dev->resource[0]->start = cf->io.win[0].base;
-		if (!pcmcia_request_io(p_dev))
-			return 0;
-	}
-	return -ENODEV;
+	if (try == 0)
+		p_dev->io_lines = 16;
+
+	if ((p_dev->resource[0]->end != 8) || (p_dev->resource[0]->start == 0))
+		return -EINVAL;
+
+	p_dev->resource[0]->end = 8;
+	p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
+	p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_8;
+
+	return pcmcia_request_io(p_dev);
 }
 
 static int bt3c_check_config_notpicky(struct pcmcia_device *p_dev,
-				      cistpl_cftable_entry_t *cf,
-				      cistpl_cftable_entry_t *dflt,
 				      void *priv_data)
 {
 	static unsigned int base[5] = { 0x3f8, 0x2f8, 0x3e8, 0x2e8, 0x0 };
 	int j;
 
-	if ((cf->io.nwin > 0) && ((cf->io.flags & CISTPL_IO_LINES_MASK) <= 3)) {
-		for (j = 0; j < 5; j++) {
-			p_dev->resource[0]->start = base[j];
-			p_dev->io_lines = base[j] ? 16 : 3;
-			if (!pcmcia_request_io(p_dev))
-				return 0;
-		}
+	if (p_dev->io_lines > 3)
+		return -ENODEV;
+
+	p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
+	p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_8;
+	p_dev->resource[0]->end = 8;
+
+	for (j = 0; j < 5; j++) {
+		p_dev->resource[0]->start = base[j];
+		p_dev->io_lines = base[j] ? 16 : 3;
+		if (!pcmcia_request_io(p_dev))
+			return 0;
 	}
 	return -ENODEV;
 }
diff --git a/drivers/bluetooth/btuart_cs.c b/drivers/bluetooth/btuart_cs.c
index 8a6864f..9f9bb69 100644
--- a/drivers/bluetooth/btuart_cs.c
+++ b/drivers/bluetooth/btuart_cs.c
@@ -585,10 +585,8 @@ static int btuart_probe(struct pcmcia_device *link)
 	info->p_dev = link;
 	link->priv = info;
 
-	link->resource[0]->flags |= IO_DATA_PATH_WIDTH_8;
-	link->resource[0]->end = 8;
-
-	link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_VPP;
+	link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_VPP |
+		CONF_AUTO_SET_IO;
 
 	return btuart_config(link);
 }
@@ -602,38 +600,41 @@ static void btuart_detach(struct pcmcia_device *link)
 	kfree(info);
 }
 
-static int btuart_check_config(struct pcmcia_device *p_dev,
-			       cistpl_cftable_entry_t *cf,
-			       cistpl_cftable_entry_t *dflt,
-			       void *priv_data)
+static int btuart_check_config(struct pcmcia_device *p_dev, void *priv_data)
 {
 	int *try = priv_data;
-	p_dev->io_lines = (try == 0) ? 16 : cf->io.flags & CISTPL_IO_LINES_MASK;
 
-	if ((cf->io.nwin > 0) && (cf->io.win[0].len == 8) &&
-	    (cf->io.win[0].base != 0)) {
-		p_dev->resource[0]->start = cf->io.win[0].base;
-		if (!pcmcia_request_io(p_dev))
-			return 0;
-	}
-	return -ENODEV;
+	if (try == 0)
+		p_dev->io_lines = 16;
+
+	if ((p_dev->resource[0]->end != 8) || (p_dev->resource[0]->start == 0))
+		return -EINVAL;
+
+	p_dev->resource[0]->end = 8;
+	p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
+	p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_8;
+
+	return pcmcia_request_io(p_dev);
 }
 
 static int btuart_check_config_notpicky(struct pcmcia_device *p_dev,
-					cistpl_cftable_entry_t *cf,
-					cistpl_cftable_entry_t *dflt,
 					void *priv_data)
 {
 	static unsigned int base[5] = { 0x3f8, 0x2f8, 0x3e8, 0x2e8, 0x0 };
 	int j;
 
-	if ((cf->io.nwin > 0) && ((cf->io.flags & CISTPL_IO_LINES_MASK) <= 3)) {
-		for (j = 0; j < 5; j++) {
-			p_dev->resource[0]->start = base[j];
-			p_dev->io_lines = base[j] ? 16 : 3;
-			if (!pcmcia_request_io(p_dev))
-				return 0;
-		}
+	if (p_dev->io_lines > 3)
+		return -ENODEV;
+
+	p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
+	p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_8;
+	p_dev->resource[0]->end = 8;
+
+	for (j = 0; j < 5; j++) {
+		p_dev->resource[0]->start = base[j];
+		p_dev->io_lines = base[j] ? 16 : 3;
+		if (!pcmcia_request_io(p_dev))
+			return 0;
 	}
 	return -ENODEV;
 }
diff --git a/drivers/bluetooth/dtl1_cs.c b/drivers/bluetooth/dtl1_cs.c
index 4620cc3..12cd177 100644
--- a/drivers/bluetooth/dtl1_cs.c
+++ b/drivers/bluetooth/dtl1_cs.c
@@ -571,10 +571,7 @@ static int dtl1_probe(struct pcmcia_device *link)
 	info->p_dev = link;
 	link->priv = info;
 
-	link->resource[0]->flags |= IO_DATA_PATH_WIDTH_8;
-	link->resource[0]->end = 8;
-
-	link->config_flags |= CONF_ENABLE_IRQ;
+	link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_IO;
 
 	return dtl1_config(link);
 }
@@ -589,17 +586,14 @@ static void dtl1_detach(struct pcmcia_device *link)
 	kfree(info);
 }
 
-static int dtl1_confcheck(struct pcmcia_device *p_dev,
-			  cistpl_cftable_entry_t *cf,
-			  cistpl_cftable_entry_t *dflt,
-			  void *priv_data)
+static int dtl1_confcheck(struct pcmcia_device *p_dev, void *priv_data)
 {
-	if ((cf->io.nwin != 1) || (cf->io.win[0].len <= 8))
+	if ((p_dev->resource[1]->end) || (p_dev->resource[1]->end < 8))
 		return -ENODEV;
 
-	p_dev->resource[0]->start = cf->io.win[0].base;
-	p_dev->resource[0]->end = cf->io.win[0].len;	/*yo */
-	p_dev->io_lines = cf->io.flags & CISTPL_IO_LINES_MASK;
+	p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
+	p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_8;
+
 	return pcmcia_request_io(p_dev);
 }
 
diff --git a/drivers/char/pcmcia/cm4000_cs.c b/drivers/char/pcmcia/cm4000_cs.c
index 0b2f3b9..79de9cc 100644
--- a/drivers/char/pcmcia/cm4000_cs.c
+++ b/drivers/char/pcmcia/cm4000_cs.c
@@ -1741,19 +1741,8 @@ static void cmm_cm4000_release(struct pcmcia_device * link)
 
 /*==== Interface to PCMCIA Layer =======================================*/
 
-static int cm4000_config_check(struct pcmcia_device *p_dev,
-			       cistpl_cftable_entry_t *cfg,
-			       cistpl_cftable_entry_t *dflt,
-			       void *priv_data)
+static int cm4000_config_check(struct pcmcia_device *p_dev, void *priv_data)
 {
-	if (!cfg->io.nwin)
-		return -ENODEV;
-
-	p_dev->resource[0]->start = cfg->io.win[0].base;
-	p_dev->resource[0]->end = cfg->io.win[0].len;
-	p_dev->resource[0]->flags |= pcmcia_io_cfg_data_width(cfg->io.flags);
-	p_dev->io_lines = cfg->io.flags & CISTPL_IO_LINES_MASK;
-
 	return pcmcia_request_io(p_dev);
 }
 
@@ -1761,6 +1750,8 @@ static int cm4000_config(struct pcmcia_device * link, int devno)
 {
 	struct cm4000_dev *dev;
 
+	link->config_flags |= CONF_AUTO_SET_IO;
+
 	/* read the config-tuples */
 	if (pcmcia_loop_config(link, cm4000_config_check, NULL))
 		goto cs_release;
diff --git a/drivers/char/pcmcia/cm4040_cs.c b/drivers/char/pcmcia/cm4040_cs.c
index acf88d5..bf012d2 100644
--- a/drivers/char/pcmcia/cm4040_cs.c
+++ b/drivers/char/pcmcia/cm4040_cs.c
@@ -515,25 +515,9 @@ static void cm4040_reader_release(struct pcmcia_device *link)
 	return;
 }
 
-static int cm4040_config_check(struct pcmcia_device *p_dev,
-			       cistpl_cftable_entry_t *cfg,
-			       cistpl_cftable_entry_t *dflt,
-			       void *priv_data)
+static int cm4040_config_check(struct pcmcia_device *p_dev, void *priv_data)
 {
-	int rc;
-	if (!cfg->io.nwin)
-		return -ENODEV;
-
-	/* Get the IOaddr */
-	p_dev->resource[0]->start = cfg->io.win[0].base;
-	p_dev->resource[0]->end = cfg->io.win[0].len;
-	p_dev->resource[0]->flags |= pcmcia_io_cfg_data_width(cfg->io.flags);
-	p_dev->io_lines = cfg->io.flags & CISTPL_IO_LINES_MASK;
-	rc = pcmcia_request_io(p_dev);
-
-	dev_printk(KERN_INFO, &p_dev->dev,
-		   "pcmcia_request_io returned 0x%x\n", rc);
-	return rc;
+	return pcmcia_request_io(p_dev);
 }
 
 
@@ -542,6 +526,8 @@ static int reader_config(struct pcmcia_device *link, int devno)
 	struct reader_dev *dev;
 	int fail_rc;
 
+	link->config_flags |= CONF_AUTO_SET_IO;
+
 	if (pcmcia_loop_config(link, cm4040_config_check, NULL))
 		goto cs_release;
 
diff --git a/drivers/char/pcmcia/ipwireless/main.c b/drivers/char/pcmcia/ipwireless/main.c
index 1b7f092..594c23b 100644
--- a/drivers/char/pcmcia/ipwireless/main.c
+++ b/drivers/char/pcmcia/ipwireless/main.c
@@ -75,22 +75,18 @@ static void signalled_reboot_callback(void *callback_data)
 	schedule_work(&ipw->work_reboot);
 }
 
-static int ipwireless_probe(struct pcmcia_device *p_dev,
-			    cistpl_cftable_entry_t *cfg,
-			    cistpl_cftable_entry_t *dflt,
-			    void *priv_data)
+static int ipwireless_probe(struct pcmcia_device *p_dev, void *priv_data)
 {
 	struct ipw_dev *ipw = priv_data;
 	struct resource *io_resource;
 	int ret;
 
+	p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
 	p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_AUTO;
-	p_dev->resource[0]->start = cfg->io.win[0].base;
-	p_dev->resource[0]->end = cfg->io.win[0].len;
 
 	/* 0x40 causes it to generate level mode interrupts. */
 	/* 0x04 enables IREQ pin. */
-	p_dev->config_index = cfg->index | 0x44;
+	p_dev->config_index |= 0x44;
 	p_dev->io_lines = 16;
 	ret = pcmcia_request_io(p_dev);
 	if (ret)
@@ -100,26 +96,18 @@ static int ipwireless_probe(struct pcmcia_device *p_dev,
 				resource_size(p_dev->resource[0]),
 				IPWIRELESS_PCCARD_NAME);
 
-	if (cfg->mem.nwin == 0)
-		return 0;
-
 	p_dev->resource[2]->flags |=
 		WIN_DATA_WIDTH_16 | WIN_MEMORY_TYPE_CM | WIN_ENABLE;
-	p_dev->resource[2]->start = cfg->mem.win[0].host_addr;
-	p_dev->resource[2]->end = cfg->mem.win[0].len;
-	if (p_dev->resource[2]->end < 0x1000)
-		p_dev->resource[2]->end = 0x1000;
 
 	ret = pcmcia_request_window(p_dev, p_dev->resource[2], 0);
 	if (ret != 0)
 		goto exit1;
 
-	ret = pcmcia_map_mem_page(p_dev, p_dev->resource[2],
-				cfg->mem.win[0].card_addr);
+	ret = pcmcia_map_mem_page(p_dev, p_dev->resource[2], p_dev->card_addr);
 	if (ret != 0)
 		goto exit2;
 
-	ipw->is_v2_card = cfg->mem.win[0].len == 0x100;
+	ipw->is_v2_card = resource_size(p_dev->resource[2]) == 0x100;
 
 	ipw->attr_memory = ioremap(p_dev->resource[2]->start,
 				resource_size(p_dev->resource[2]));
@@ -165,13 +153,13 @@ static int config_ipwireless(struct ipw_dev *ipw)
 	int ret = 0;
 
 	ipw->is_v2_card = 0;
+	link->config_flags |= CONF_AUTO_SET_IO | CONF_AUTO_SET_IOMEM |
+		CONF_ENABLE_IRQ;
 
 	ret = pcmcia_loop_config(link, ipwireless_probe, ipw);
 	if (ret != 0)
 		return ret;
 
-	link->config_flags |= CONF_ENABLE_IRQ;
-
 	INIT_WORK(&ipw->work_reboot, signalled_reboot_work);
 
 	ipwireless_init_hardware_v1(ipw->hardware, link->resource[0]->start,
diff --git a/drivers/char/pcmcia/synclink_cs.c b/drivers/char/pcmcia/synclink_cs.c
index c701434..a343b8f 100644
--- a/drivers/char/pcmcia/synclink_cs.c
+++ b/drivers/char/pcmcia/synclink_cs.c
@@ -561,19 +561,8 @@ static int mgslpc_probe(struct pcmcia_device *link)
 /* Card has been inserted.
  */
 
-static int mgslpc_ioprobe(struct pcmcia_device *p_dev,
-			  cistpl_cftable_entry_t *cfg,
-			  cistpl_cftable_entry_t *dflt,
-			  void *priv_data)
+static int mgslpc_ioprobe(struct pcmcia_device *p_dev, void *priv_data)
 {
-	if (!cfg->io.nwin)
-		return -ENODEV;
-
-	p_dev->resource[0]->start = cfg->io.win[0].base;
-	p_dev->resource[0]->end = cfg->io.win[0].len;
-	p_dev->resource[0]->flags |= pcmcia_io_cfg_data_width(cfg->io.flags);
-	p_dev->io_lines = cfg->io.flags & CISTPL_IO_LINES_MASK;
-
 	return pcmcia_request_io(p_dev);
 }
 
@@ -585,11 +574,12 @@ static int mgslpc_config(struct pcmcia_device *link)
     if (debug_level >= DEBUG_LEVEL_INFO)
 	    printk("mgslpc_config(0x%p)\n", link);
 
+    link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_IO;
+
     ret = pcmcia_loop_config(link, mgslpc_ioprobe, NULL);
     if (ret != 0)
 	    goto failed;
 
-    link->config_flags |= CONF_ENABLE_IRQ;
     link->config_index = 8;
     link->config_regs = PRESENT_OPTION;
 
diff --git a/drivers/ide/ide-cs.c b/drivers/ide/ide-cs.c
index 25b8a10..c389d9a 100644
--- a/drivers/ide/ide-cs.c
+++ b/drivers/ide/ide-cs.c
@@ -96,10 +96,8 @@ static int ide_probe(struct pcmcia_device *link)
     info->p_dev = link;
     link->priv = info;
 
-    link->resource[0]->flags |= IO_DATA_PATH_WIDTH_AUTO;
-    link->resource[1]->flags |= IO_DATA_PATH_WIDTH_8;
-    link->config_flags |= CONF_ENABLE_IRQ;
-    link->config_flags |= CONF_AUTO_SET_VPP | CONF_AUTO_CHECK_VCC;
+    link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_IO |
+	    CONF_AUTO_SET_VPP | CONF_AUTO_CHECK_VCC;
 
     return ide_config(link);
 } /* ide_attach */
@@ -194,52 +192,31 @@ out_release:
 
 ======================================================================*/
 
-struct pcmcia_config_check {
-	unsigned long ctl_base;
-	int is_kme;
-};
-
-static int pcmcia_check_one_config(struct pcmcia_device *pdev,
-				   cistpl_cftable_entry_t *cfg,
-				   cistpl_cftable_entry_t *dflt,
-				   void *priv_data)
+static int pcmcia_check_one_config(struct pcmcia_device *pdev, void *priv_data)
 {
-	struct pcmcia_config_check *stk = priv_data;
-
-	if ((cfg->io.nwin > 0) || (dflt->io.nwin > 0)) {
-		cistpl_io_t *io = (cfg->io.nwin) ? &cfg->io : &dflt->io;
-		pdev->io_lines = io->flags & CISTPL_IO_LINES_MASK;
-		pdev->config_index = cfg->index;
-		pdev->resource[0]->start = io->win[0].base;
-		if (!(io->flags & CISTPL_IO_16BIT)) {
-			pdev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
-			pdev->resource[0]->flags |= IO_DATA_PATH_WIDTH_8;
-		}
-		if (io->nwin == 2) {
-			pdev->resource[0]->end = 8;
-			pdev->resource[1]->start = io->win[1].base;
-			pdev->resource[1]->end = (stk->is_kme) ? 2 : 1;
-			if (pcmcia_request_io(pdev) != 0)
-				return -ENODEV;
-			stk->ctl_base = pdev->resource[1]->start;
-		} else if ((io->nwin == 1) && (io->win[0].len >= 16)) {
-			pdev->resource[0]->end = io->win[0].len;
-			pdev->resource[1]->end = 0;
-			if (pcmcia_request_io(pdev) != 0)
-				return -ENODEV;
-			stk->ctl_base = pdev->resource[0]->start + 0x0e;
-		} else
+	int *is_kme = priv_data;
+
+	if (!(pdev->resource[0]->flags & IO_DATA_PATH_WIDTH_8)) {
+		pdev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
+		pdev->resource[0]->flags |= IO_DATA_PATH_WIDTH_AUTO;
+	}
+	pdev->resource[1]->flags &= ~IO_DATA_PATH_WIDTH;
+	pdev->resource[1]->flags |= IO_DATA_PATH_WIDTH_8;
+
+	if (pdev->resource[1]->end) {
+		pdev->resource[0]->end = 8;
+		pdev->resource[1]->end = (*is_kme) ? 2 : 1;
+	} else {
+		if (pdev->resource[0]->end < 16)
 			return -ENODEV;
-		/* If we've got this far, we're done */
-		return 0;
 	}
-	return -ENODEV;
+
+	return pcmcia_request_io(pdev);
 }
 
 static int ide_config(struct pcmcia_device *link)
 {
     ide_info_t *info = link->priv;
-    struct pcmcia_config_check *stk = NULL;
     int ret = 0, is_kme = 0;
     unsigned long io_base, ctl_base;
     struct ide_host *host;
@@ -250,19 +227,16 @@ static int ide_config(struct pcmcia_device *link)
 	      ((link->card_id == PRODID_KME_KXLC005_A) ||
 	       (link->card_id == PRODID_KME_KXLC005_B)));
 
-    stk = kzalloc(sizeof(*stk), GFP_KERNEL);
-    if (!stk)
-	    goto err_mem;
-    stk->is_kme = is_kme;
-    io_base = ctl_base = 0;
-
-    if (pcmcia_loop_config(link, pcmcia_check_one_config, stk)) {
+    if (pcmcia_loop_config(link, pcmcia_check_one_config, &is_kme)) {
 	    link->config_flags &= ~CONF_AUTO_CHECK_VCC;
-	    if (pcmcia_loop_config(link, pcmcia_check_one_config, stk))
+	    if (pcmcia_loop_config(link, pcmcia_check_one_config, &is_kme))
 		    goto failed; /* No suitable config found */
     }
     io_base = link->resource[0]->start;
-    ctl_base = stk->ctl_base;
+    if (link->resource[1]->end)
+	    ctl_base = link->resource[1]->start;
+    else
+	    ctl_base = link->resource[0]->start + 0x0e;
 
     if (!link->irq)
 	    goto failed;
@@ -294,15 +268,9 @@ static int ide_config(struct pcmcia_device *link)
 	    'a' + host->ports[0]->index * 2,
 	    link->vpp / 10, link->vpp % 10);
 
-    kfree(stk);
     return 0;
 
-err_mem:
-    printk(KERN_NOTICE "ide-cs: ide_config failed memory allocation\n");
-    goto failed;
-
 failed:
-    kfree(stk);
     ide_release(link);
     return -ENODEV;
 } /* ide_config */
diff --git a/drivers/isdn/hardware/avm/avm_cs.c b/drivers/isdn/hardware/avm/avm_cs.c
index 9dbab9c..403a995 100644
--- a/drivers/isdn/hardware/avm/avm_cs.c
+++ b/drivers/isdn/hardware/avm/avm_cs.c
@@ -72,13 +72,8 @@ static void avmcs_detach(struct pcmcia_device *p_dev);
 
 static int avmcs_probe(struct pcmcia_device *p_dev)
 {
-
-    /* The io structure describes IO port mapping */
-    p_dev->resource[0]->end = 16;
-    p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_8;
-
     /* General socket configuration */
-    p_dev->config_flags |= CONF_ENABLE_IRQ;
+    p_dev->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_IO;
     p_dev->config_index = 1;
     p_dev->config_regs = PRESENT_OPTION;
 
@@ -107,16 +102,12 @@ static void avmcs_detach(struct pcmcia_device *link)
     
 ======================================================================*/
 
-static int avmcs_configcheck(struct pcmcia_device *p_dev,
-			     cistpl_cftable_entry_t *cf,
-			     cistpl_cftable_entry_t *dflt,
-			     void *priv_data)
+static int avmcs_configcheck(struct pcmcia_device *p_dev, void *priv_data)
 {
-	if (cf->io.nwin <= 0)
-		return -ENODEV;
+	p_dev->resource[0]->end = 16;
+	p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
+	p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_8;
 
-	p_dev->resource[0]->start = cf->io.win[0].base;
-	p_dev->resource[0]->end = cf->io.win[0].len;
 	return pcmcia_request_io(p_dev);
 }
 
diff --git a/drivers/isdn/hisax/avma1_cs.c b/drivers/isdn/hisax/avma1_cs.c
index 2f2b000..cb09f0c 100644
--- a/drivers/isdn/hisax/avma1_cs.c
+++ b/drivers/isdn/hisax/avma1_cs.c
@@ -76,14 +76,8 @@ static int __devinit avma1cs_probe(struct pcmcia_device *p_dev)
 {
     dev_dbg(&p_dev->dev, "avma1cs_attach()\n");
 
-    /* The io structure describes IO port mapping */
-    p_dev->resource[0]->end = 16;
-    p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_8;
-    p_dev->resource[1]->end = 16;
-    p_dev->resource[1]->flags |= IO_DATA_PATH_WIDTH_16;
-
     /* General socket configuration */
-    p_dev->config_flags |= CONF_ENABLE_IRQ;
+    p_dev->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_IO;
     p_dev->config_index = 1;
     p_dev->config_regs = PRESENT_OPTION;
 
@@ -114,17 +108,13 @@ static void __devexit avma1cs_detach(struct pcmcia_device *link)
     
 ======================================================================*/
 
-static int avma1cs_configcheck(struct pcmcia_device *p_dev,
-			       cistpl_cftable_entry_t *cf,
-			       cistpl_cftable_entry_t *dflt,
-			       void *priv_data)
+static int avma1cs_configcheck(struct pcmcia_device *p_dev, void *priv_data)
 {
-	if (cf->io.nwin <= 0)
-		return -ENODEV;
-
-	p_dev->resource[0]->start = cf->io.win[0].base;
-	p_dev->resource[0]->end = cf->io.win[0].len;
+	p_dev->resource[0]->end = 16;
+	p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
+	p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_8;
 	p_dev->io_lines = 5;
+
 	return pcmcia_request_io(p_dev);
 }
 
diff --git a/drivers/isdn/hisax/elsa_cs.c b/drivers/isdn/hisax/elsa_cs.c
index 0a65280..f203a52 100644
--- a/drivers/isdn/hisax/elsa_cs.c
+++ b/drivers/isdn/hisax/elsa_cs.c
@@ -118,16 +118,6 @@ static int __devinit elsa_cs_probe(struct pcmcia_device *link)
 
     local->cardnr = -1;
 
-    /*
-      General socket configuration defaults can go here.  In this
-      client, we assume very little, and rely on the CIS for almost
-      everything.  In most clients, many details (i.e., number, sizes,
-      and attributes of IO windows) are fixed by the nature of the
-      device, and can be hard-wired here.
-    */
-    link->resource[0]->end = 8;
-    link->resource[0]->flags |= IO_DATA_PATH_WIDTH_AUTO;
-
     return elsa_cs_config(link);
 } /* elsa_cs_attach */
 
@@ -160,18 +150,17 @@ static void __devexit elsa_cs_detach(struct pcmcia_device *link)
 
 ======================================================================*/
 
-static int elsa_cs_configcheck(struct pcmcia_device *p_dev,
-			       cistpl_cftable_entry_t *cf,
-			       cistpl_cftable_entry_t *dflt,
-			       void *priv_data)
+static int elsa_cs_configcheck(struct pcmcia_device *p_dev, void *priv_data)
 {
 	int j;
 
 	p_dev->io_lines = 3;
+	p_dev->resource[0]->end = 8;
+	p_dev->resource[0]->flags &= IO_DATA_PATH_WIDTH;
+	p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_AUTO;
 
-	if ((cf->io.nwin > 0) && cf->io.win[0].base) {
+	if ((p_dev->resource[0]->end) && p_dev->resource[0]->start) {
 		printk(KERN_INFO "(elsa_cs: looks like the 96 model)\n");
-		p_dev->resource[0]->start = cf->io.win[0].base;
 		if (!pcmcia_request_io(p_dev))
 			return 0;
 	} else {
@@ -194,6 +183,8 @@ static int __devinit elsa_cs_config(struct pcmcia_device *link)
     dev_dbg(&link->dev, "elsa_config(0x%p)\n", link);
     dev = link->priv;
 
+    link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_IO;
+
     i = pcmcia_loop_config(link, elsa_cs_configcheck, NULL);
     if (i != 0)
 	goto failed;
diff --git a/drivers/isdn/hisax/sedlbauer_cs.c b/drivers/isdn/hisax/sedlbauer_cs.c
index b69eccf..a88c88f 100644
--- a/drivers/isdn/hisax/sedlbauer_cs.c
+++ b/drivers/isdn/hisax/sedlbauer_cs.c
@@ -128,8 +128,6 @@ static int __devinit sedlbauer_probe(struct pcmcia_device *link)
     /* from old sedl_cs 
     */
     /* The io structure describes IO port mapping */
-    link->resource[0]->end = 8;
-    link->resource[0]->flags |= IO_DATA_PATH_WIDTH_8;
 
     return sedlbauer_config(link);
 } /* sedlbauer_attach */
@@ -161,35 +159,13 @@ static void __devexit sedlbauer_detach(struct pcmcia_device *link)
     device available to the system.
     
 ======================================================================*/
-static int sedlbauer_config_check(struct pcmcia_device *p_dev,
-				  cistpl_cftable_entry_t *cfg,
-				  cistpl_cftable_entry_t *dflt,
-				  void *priv_data)
+static int sedlbauer_config_check(struct pcmcia_device *p_dev, void *priv_data)
 {
-	if (cfg->index == 0)
-		return -ENODEV;
-
-	/* IO window settings */
-	p_dev->resource[0]->end = p_dev->resource[1]->end = 0;
-	if ((cfg->io.nwin > 0) || (dflt->io.nwin > 0)) {
-		cistpl_io_t *io = (cfg->io.nwin) ? &cfg->io : &dflt->io;
-		p_dev->resource[0]->start = io->win[0].base;
-		p_dev->resource[0]->end = io->win[0].len;
-		p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
-		p_dev->resource[0]->flags |=
-					pcmcia_io_cfg_data_width(io->flags);
-		if (io->nwin > 1) {
-			p_dev->resource[1]->flags = p_dev->resource[0]->flags;
-			p_dev->resource[1]->start = io->win[1].base;
-			p_dev->resource[1]->end = io->win[1].len;
-		}
-		/* This reserves IO space but doesn't actually enable it */
-		p_dev->io_lines = 3;
-		if (pcmcia_request_io(p_dev) != 0)
-			return -ENODEV;
-	}
+	if (p_dev->config_index == 0)
+		return -EINVAL;
 
-	return 0;
+	p_dev->io_lines = 3;
+	return pcmcia_request_io(p_dev);
 }
 
 
@@ -202,7 +178,7 @@ static int __devinit sedlbauer_config(struct pcmcia_device *link)
     dev_dbg(&link->dev, "sedlbauer_config(0x%p)\n", link);
 
     link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_CHECK_VCC |
-	    CONF_AUTO_SET_VPP | CONF_AUTO_AUDIO;
+	    CONF_AUTO_SET_VPP | CONF_AUTO_AUDIO | CONF_AUTO_SET_IO;
 
     /*
       In this loop, we scan the CIS for configuration table entries,
diff --git a/drivers/isdn/hisax/teles_cs.c b/drivers/isdn/hisax/teles_cs.c
index 6605480..05a5631 100644
--- a/drivers/isdn/hisax/teles_cs.c
+++ b/drivers/isdn/hisax/teles_cs.c
@@ -105,10 +105,7 @@ static int __devinit teles_probe(struct pcmcia_device *link)
       and attributes of IO windows) are fixed by the nature of the
       device, and can be hard-wired here.
     */
-    link->resource[0]->end = 96;
-    link->resource[0]->flags |= IO_DATA_PATH_WIDTH_AUTO;
-
-    link->config_flags |= CONF_ENABLE_IRQ;
+    link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_IO;
 
     return teles_cs_config(link);
 } /* teles_attach */
@@ -142,18 +139,17 @@ static void __devexit teles_detach(struct pcmcia_device *link)
 
 ======================================================================*/
 
-static int teles_cs_configcheck(struct pcmcia_device *p_dev,
-				cistpl_cftable_entry_t *cf,
-				cistpl_cftable_entry_t *dflt,
-				void *priv_data)
+static int teles_cs_configcheck(struct pcmcia_device *p_dev, void *priv_data)
 {
 	int j;
 
 	p_dev->io_lines = 5;
+	p_dev->resource[0]->end = 96;
+	p_dev->resource[0]->flags &= IO_DATA_PATH_WIDTH;
+	p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_AUTO;
 
-	if ((cf->io.nwin > 0) && cf->io.win[0].base) {
+	if ((p_dev->resource[0]->end) && p_dev->resource[0]->start) {
 		printk(KERN_INFO "(teles_cs: looks like the 96 model)\n");
-		p_dev->resource[0]->start = cf->io.win[0].base;
 		if (!pcmcia_request_io(p_dev))
 			return 0;
 	} else {
diff --git a/drivers/net/pcmcia/axnet_cs.c b/drivers/net/pcmcia/axnet_cs.c
index 17f1040..9d9d997 100644
--- a/drivers/net/pcmcia/axnet_cs.c
+++ b/drivers/net/pcmcia/axnet_cs.c
@@ -284,34 +284,16 @@ static int try_io_port(struct pcmcia_device *link)
     }
 }
 
-static int axnet_configcheck(struct pcmcia_device *p_dev,
-			     cistpl_cftable_entry_t *cfg,
-			     cistpl_cftable_entry_t *dflt,
-			     void *priv_data)
+static int axnet_configcheck(struct pcmcia_device *p_dev, void *priv_data)
 {
-	int i;
-	cistpl_io_t *io = &cfg->io;
-
-	if (cfg->index == 0 || cfg->io.nwin == 0)
-		return -ENODEV;
+	if (p_dev->config_index == 0)
+		return -EINVAL;
 
 	p_dev->config_index = 0x05;
-	/* For multifunction cards, by convention, we configure the
-	   network function with window 0, and serial with window 1 */
-	if (io->nwin > 1) {
-		i = (io->win[1].len > io->win[0].len);
-		p_dev->resource[1]->start = io->win[1-i].base;
-		p_dev->resource[1]->end = io->win[1-i].len;
-	} else {
-		i = p_dev->resource[1]->end = 0;
-	}
-	p_dev->resource[0]->start = io->win[i].base;
-	p_dev->resource[0]->end = io->win[i].len;
-	p_dev->io_lines = io->flags & CISTPL_IO_LINES_MASK;
-	if (p_dev->resource[0]->end + p_dev->resource[1]->end >= 32)
-		return try_io_port(p_dev);
+	if (p_dev->resource[0]->end + p_dev->resource[1]->end < 32)
+		return -ENODEV;
 
-	return -ENODEV;
+	return try_io_port(p_dev);
 }
 
 static int axnet_config(struct pcmcia_device *link)
@@ -324,6 +306,7 @@ static int axnet_config(struct pcmcia_device *link)
 
     /* don't trust the CIS on this; Linksys got it wrong */
     link->config_regs = 0x63;
+    link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_IO;
     ret = pcmcia_loop_config(link, axnet_configcheck, NULL);
     if (ret != 0)
 	goto failed;
@@ -331,7 +314,6 @@ static int axnet_config(struct pcmcia_device *link)
     if (!link->irq)
 	    goto failed;
 
-    link->config_flags |= CONF_ENABLE_IRQ;
     if (resource_size(link->resource[1]) == 8)
 	link->config_flags |= CONF_ENABLE_SPKR;
     
diff --git a/drivers/net/pcmcia/fmvj18x_cs.c b/drivers/net/pcmcia/fmvj18x_cs.c
index c1479e3..792ab38 100644
--- a/drivers/net/pcmcia/fmvj18x_cs.c
+++ b/drivers/net/pcmcia/fmvj18x_cs.c
@@ -319,10 +319,7 @@ static int ungermann_try_io_port(struct pcmcia_device *link)
     return ret;	/* RequestIO failed */
 }
 
-static int fmvj18x_ioprobe(struct pcmcia_device *p_dev,
-			   cistpl_cftable_entry_t *cfg,
-			   cistpl_cftable_entry_t *dflt,
-			   void *priv_data)
+static int fmvj18x_ioprobe(struct pcmcia_device *p_dev, void *priv_data)
 {
 	return 0; /* strange, but that's what the code did already before... */
 }
diff --git a/drivers/net/pcmcia/pcnet_cs.c b/drivers/net/pcmcia/pcnet_cs.c
index 12b028c..ffe2587 100644
--- a/drivers/net/pcmcia/pcnet_cs.c
+++ b/drivers/net/pcmcia/pcnet_cs.c
@@ -259,7 +259,7 @@ static int pcnet_probe(struct pcmcia_device *link)
     info->p_dev = link;
     link->priv = dev;
 
-    link->config_flags |= CONF_ENABLE_IRQ;
+    link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_IO;
 
     dev->netdev_ops = &pcnet_netdev_ops;
 
@@ -500,42 +500,22 @@ static int try_io_port(struct pcmcia_device *link)
     }
 }
 
-static int pcnet_confcheck(struct pcmcia_device *p_dev,
-			   cistpl_cftable_entry_t *cfg,
-			   cistpl_cftable_entry_t *dflt,
-			   void *priv_data)
+static int pcnet_confcheck(struct pcmcia_device *p_dev, void *priv_data)
 {
 	int *priv = priv_data;
 	int try = (*priv & 0x1);
-	int i;
-	cistpl_io_t *io = &cfg->io;
 
-	if (cfg->index == 0 || cfg->io.nwin == 0)
-		return -EINVAL;
+	*priv &= (p_dev->resource[2]->end >= 0x4000) ? 0x10 : ~0x10;
 
-	/* For multifunction cards, by convention, we configure the
-	   network function with window 0, and serial with window 1 */
-	if (io->nwin > 1) {
-		i = (io->win[1].len > io->win[0].len);
-		p_dev->resource[1]->start = io->win[1-i].base;
-		p_dev->resource[1]->end = io->win[1-i].len;
-	} else {
-		i = p_dev->resource[1]->end = 0;
-	}
+	if (p_dev->config_index == 0)
+		return -EINVAL;
 
-	*priv &= ((cfg->mem.nwin == 1) &&
-		  (cfg->mem.win[0].len >= 0x4000)) ? 0x10 : ~0x10;
+	if (p_dev->resource[0]->end + p_dev->resource[1]->end < 32)
+		return -EINVAL;
 
-	p_dev->resource[0]->start = io->win[i].base;
-	p_dev->resource[0]->end = io->win[i].len;
-	if (!try)
-		p_dev->io_lines = io->flags & CISTPL_IO_LINES_MASK;
-	else
+	if (try)
 		p_dev->io_lines = 16;
-	if (p_dev->resource[0]->end + p_dev->resource[1]->end >= 32)
-		return try_io_port(p_dev);
-
-	return -EINVAL;
+	return try_io_port(p_dev);
 }
 
 static hw_info_t *pcnet_try_config(struct pcmcia_device *link,
diff --git a/drivers/net/pcmcia/smc91c92_cs.c b/drivers/net/pcmcia/smc91c92_cs.c
index e127d2b..a8cef28 100644
--- a/drivers/net/pcmcia/smc91c92_cs.c
+++ b/drivers/net/pcmcia/smc91c92_cs.c
@@ -323,9 +323,6 @@ static int smc91c92_probe(struct pcmcia_device *link)
     link->priv = dev;
 
     spin_lock_init(&smc->lock);
-    link->resource[0]->end = 16;
-    link->resource[0]->flags |= IO_DATA_PATH_WIDTH_AUTO;
-    link->config_flags |= CONF_ENABLE_IRQ;
 
     /* The SMC91c92-specific entries in the device structure. */
     dev->netdev_ops = &smc_netdev_ops;
@@ -417,18 +414,21 @@ static int mhz_3288_power(struct pcmcia_device *link)
     return 0;
 }
 
-static int mhz_mfc_config_check(struct pcmcia_device *p_dev,
-				cistpl_cftable_entry_t *cf,
-				cistpl_cftable_entry_t *dflt,
-				void *priv_data)
+static int mhz_mfc_config_check(struct pcmcia_device *p_dev, void *priv_data)
 {
 	int k;
-	p_dev->resource[1]->start = cf->io.win[0].base;
+	p_dev->io_lines = 16;
+	p_dev->resource[1]->start = p_dev->resource[0]->start;
+	p_dev->resource[1]->end = 8;
+	p_dev->resource[1]->flags &= ~IO_DATA_PATH_WIDTH;
+	p_dev->resource[1]->flags |= IO_DATA_PATH_WIDTH_8;
+	p_dev->resource[0]->end = 16;
+	p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
+	p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_AUTO;
 	for (k = 0; k < 0x400; k += 0x10) {
 		if (k & 0x80)
 			continue;
 		p_dev->resource[0]->start = k ^ 0x300;
-		p_dev->io_lines = 16;
 		if (!pcmcia_request_io(p_dev))
 			return 0;
 	}
@@ -442,9 +442,8 @@ static int mhz_mfc_config(struct pcmcia_device *link)
     unsigned int offset;
     int i;
 
-    link->config_flags |= CONF_ENABLE_SPKR;
-    link->resource[1]->flags |= IO_DATA_PATH_WIDTH_8;
-    link->resource[1]->end = 8;
+    link->config_flags |= CONF_ENABLE_SPKR | CONF_ENABLE_IRQ |
+	    CONF_AUTO_SET_IO;
 
     /* The Megahertz combo cards have modem-like CIS entries, so
        we have to explicitly try a bunch of port combinations. */
@@ -586,13 +585,12 @@ static int mot_setup(struct pcmcia_device *link)
 
 /*====================================================================*/
 
-static int smc_configcheck(struct pcmcia_device *p_dev,
-			   cistpl_cftable_entry_t *cf,
-			   cistpl_cftable_entry_t *dflt,
-			   void *priv_data)
+static int smc_configcheck(struct pcmcia_device *p_dev, void *priv_data)
 {
-	p_dev->resource[0]->start = cf->io.win[0].base;
-	p_dev->io_lines = cf->io.flags & CISTPL_IO_LINES_MASK;
+	p_dev->resource[0]->end = 16;
+	p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
+	p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_AUTO;
+
 	return pcmcia_request_io(p_dev);
 }
 
@@ -601,7 +599,8 @@ static int smc_config(struct pcmcia_device *link)
     struct net_device *dev = link->priv;
     int i;
 
-    link->resource[0]->end = 16;
+    link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_IO;
+
     i = pcmcia_loop_config(link, smc_configcheck, NULL);
     if (!i)
 	    dev->base_addr = link->resource[0]->start;
@@ -634,7 +633,7 @@ static int osi_config(struct pcmcia_device *link)
     static const unsigned int com[4] = { 0x3f8, 0x2f8, 0x3e8, 0x2e8 };
     int i, j;
 
-    link->config_flags |= CONF_ENABLE_SPKR;
+    link->config_flags |= CONF_ENABLE_SPKR | CONF_ENABLE_IRQ;
     link->resource[0]->end = 64;
     link->resource[1]->flags |= IO_DATA_PATH_WIDTH_8;
     link->resource[1]->end = 8;
diff --git a/drivers/net/pcmcia/xirc2ps_cs.c b/drivers/net/pcmcia/xirc2ps_cs.c
index 2bc2eb8..cecc074 100644
--- a/drivers/net/pcmcia/xirc2ps_cs.c
+++ b/drivers/net/pcmcia/xirc2ps_cs.c
@@ -528,7 +528,6 @@ xirc2ps_probe(struct pcmcia_device *link)
     link->priv = dev;
 
     /* General socket configuration */
-    link->config_flags |= CONF_ENABLE_IRQ;
     link->config_index = 1;
 
     /* Fill in card specific entries */
@@ -665,42 +664,53 @@ has_ce2_string(struct pcmcia_device * p_dev)
 }
 
 static int
-xirc2ps_config_modem(struct pcmcia_device *p_dev,
-		     cistpl_cftable_entry_t *cf,
-		     cistpl_cftable_entry_t *dflt,
-		     void *priv_data)
+xirc2ps_config_modem(struct pcmcia_device *p_dev, void *priv_data)
 {
 	unsigned int ioaddr;
 
-	if (cf->io.nwin > 0  &&  (cf->io.win[0].base & 0xf) == 8) {
-		for (ioaddr = 0x300; ioaddr < 0x400; ioaddr += 0x10) {
-			p_dev->resource[1]->start = cf->io.win[0].base;
-			p_dev->resource[0]->start = ioaddr;
-			if (!pcmcia_request_io(p_dev))
-				return 0;
-		}
+	if ((p_dev->resource[0]->start & 0xf) == 8)
+		return -ENODEV;
+
+	p_dev->resource[0]->end = 16;
+	p_dev->resource[1]->end = 8;
+	p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
+	p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_16;
+	p_dev->resource[1]->flags &= ~IO_DATA_PATH_WIDTH;
+	p_dev->resource[1]->flags |= IO_DATA_PATH_WIDTH_8;
+	p_dev->io_lines = 10;
+
+	p_dev->resource[1]->start = p_dev->resource[0]->start;
+	for (ioaddr = 0x300; ioaddr < 0x400; ioaddr += 0x10) {
+		p_dev->resource[0]->start = ioaddr;
+		if (!pcmcia_request_io(p_dev))
+			return 0;
 	}
 	return -ENODEV;
 }
 
 static int
-xirc2ps_config_check(struct pcmcia_device *p_dev,
-		     cistpl_cftable_entry_t *cf,
-		     cistpl_cftable_entry_t *dflt,
-		     void *priv_data)
+xirc2ps_config_check(struct pcmcia_device *p_dev, void *priv_data)
 {
 	int *pass = priv_data;
+	resource_size_t tmp = p_dev->resource[1]->start;
 
-	if (cf->io.nwin > 0 && (cf->io.win[0].base & 0xf) == 8) {
-		p_dev->resource[1]->start = cf->io.win[0].base;
-		p_dev->resource[0]->start = p_dev->resource[1]->start
-			+ (*pass ? (cf->index & 0x20 ? -24:8)
-			   : (cf->index & 0x20 ?   8:-24));
-		if (!pcmcia_request_io(p_dev))
-			return 0;
-	}
-	return -ENODEV;
+	tmp += (*pass ? (p_dev->config_index & 0x20 ? -24 : 8)
+		: (p_dev->config_index & 0x20 ?   8 : -24));
+
+	if ((p_dev->resource[0]->start & 0xf) == 8)
+		return -ENODEV;
+
+	p_dev->resource[0]->end = 18;
+	p_dev->resource[1]->end = 8;
+	p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
+	p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_16;
+	p_dev->resource[1]->flags &= ~IO_DATA_PATH_WIDTH;
+	p_dev->resource[1]->flags |= IO_DATA_PATH_WIDTH_8;
+	p_dev->io_lines = 10;
 
+	p_dev->resource[1]->start = p_dev->resource[0]->start;
+	p_dev->resource[0]->start = tmp;
+	return pcmcia_request_io(p_dev);
 }
 
 
@@ -803,21 +813,16 @@ xirc2ps_config(struct pcmcia_device * link)
 	goto failure;
     }
 
-    link->resource[0]->flags |= IO_DATA_PATH_WIDTH_16;
-    link->io_lines = 10;
     if (local->modem) {
 	int pass;
+	link->config_flags |= CONF_AUTO_SET_IO;
 
-	link->resource[1]->end = 8;
-	link->resource[1]->flags |= IO_DATA_PATH_WIDTH_8;
 	if (local->dingo) {
 	    /* Take the Modem IO port from the CIS and scan for a free
 	     * Ethernet port */
-	    link->resource[0]->end = 16; /* no Mako stuff anymore */
 	    if (!pcmcia_loop_config(link, xirc2ps_config_modem, NULL))
 		    goto port_found;
 	} else {
-	    link->resource[0]->end = 18;
 	    /* We do 2 passes here: The first one uses the regular mapping and
 	     * the second tries again, thereby considering that the 32 ports are
 	     * mirrored every 32 bytes. Actually we use a mirrored port for
@@ -833,7 +838,9 @@ xirc2ps_config(struct pcmcia_device * link)
 	}
 	printk(KNOT_XIRC "no ports available\n");
     } else {
+	link->io_lines = 10;
 	link->resource[0]->end = 16;
+	link->resource[0]->flags |= IO_DATA_PATH_WIDTH_16;
 	for (ioaddr = 0x300; ioaddr < 0x400; ioaddr += 0x10) {
 	    link->resource[0]->start = ioaddr;
 	    if (!(err = pcmcia_request_io(link)))
diff --git a/drivers/net/wireless/airo_cs.c b/drivers/net/wireless/airo_cs.c
index 63bf662..77682f2 100644
--- a/drivers/net/wireless/airo_cs.c
+++ b/drivers/net/wireless/airo_cs.c
@@ -137,36 +137,12 @@ static void airo_detach(struct pcmcia_device *link)
 
   ======================================================================*/
 
-static int airo_cs_config_check(struct pcmcia_device *p_dev,
-				cistpl_cftable_entry_t *cfg,
-				cistpl_cftable_entry_t *dflt,
-				void *priv_data)
+static int airo_cs_config_check(struct pcmcia_device *p_dev, void *priv_data)
 {
-	if (cfg->index == 0)
-		return -ENODEV;
-
-	/* IO window settings */
-	p_dev->resource[0]->end = p_dev->resource[1]->end = 0;
-	if ((cfg->io.nwin > 0) || (dflt->io.nwin > 0)) {
-		cistpl_io_t *io = (cfg->io.nwin) ? &cfg->io : &dflt->io;
-		p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
-		p_dev->resource[0]->flags |=
-					pcmcia_io_cfg_data_width(io->flags);
-		p_dev->resource[0]->start = io->win[0].base;
-		p_dev->resource[0]->end = io->win[0].len;
-		if (io->nwin > 1) {
-			p_dev->resource[1]->flags = p_dev->resource[0]->flags;
-			p_dev->resource[1]->start = io->win[1].base;
-			p_dev->resource[1]->end = io->win[1].len;
-		}
-	}
-
-	/* This reserves IO space but doesn't actually enable it */
-	if (pcmcia_request_io(p_dev) != 0)
-		return -ENODEV;
+	if (p_dev->config_index == 0)
+		return -EINVAL;
 
-	/* If we got this far, we're cool! */
-	return 0;
+	return pcmcia_request_io(p_dev);
 }
 
 
@@ -180,7 +156,7 @@ static int airo_config(struct pcmcia_device *link)
 	dev_dbg(&link->dev, "airo_config\n");
 
 	link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_VPP |
-		CONF_AUTO_AUDIO;
+		CONF_AUTO_AUDIO | CONF_AUTO_SET_IO;
 
 	/*
 	 * In this loop, we scan the CIS for configuration table
diff --git a/drivers/net/wireless/atmel_cs.c b/drivers/net/wireless/atmel_cs.c
index 812decd..2029380 100644
--- a/drivers/net/wireless/atmel_cs.c
+++ b/drivers/net/wireless/atmel_cs.c
@@ -154,31 +154,11 @@ static int card_present(void *arg)
 	return 0;
 }
 
-static int atmel_config_check(struct pcmcia_device *p_dev,
-			      cistpl_cftable_entry_t *cfg,
-			      cistpl_cftable_entry_t *dflt,
-			      void *priv_data)
+static int atmel_config_check(struct pcmcia_device *p_dev, void *priv_data)
 {
-	if (cfg->index == 0)
-		return -ENODEV;
-
-	/* IO window settings */
-	p_dev->resource[0]->end = p_dev->resource[1]->end = 0;
-	if ((cfg->io.nwin > 0) || (dflt->io.nwin > 0)) {
-		cistpl_io_t *io = (cfg->io.nwin) ? &cfg->io : &dflt->io;
-		p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
-		p_dev->resource[0]->flags |=
-					pcmcia_io_cfg_data_width(io->flags);
-		p_dev->resource[0]->start = io->win[0].base;
-		p_dev->resource[0]->end = io->win[0].len;
-		if (io->nwin > 1) {
-			p_dev->resource[1]->flags = p_dev->resource[0]->flags;
-			p_dev->resource[1]->start = io->win[1].base;
-			p_dev->resource[1]->end = io->win[1].len;
-		}
-	}
+	if (p_dev->config_index == 0)
+		return -EINVAL;
 
-	/* This reserves IO space but doesn't actually enable it */
 	return pcmcia_request_io(p_dev);
 }
 
@@ -194,7 +174,7 @@ static int atmel_config(struct pcmcia_device *link)
 	dev_dbg(&link->dev, "atmel_config\n");
 
 	link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_VPP |
-		CONF_AUTO_AUDIO;
+		CONF_AUTO_AUDIO | CONF_AUTO_SET_IO;
 
 	/*
 	  In this loop, we scan the CIS for configuration table entries,
diff --git a/drivers/net/wireless/hostap/hostap_cs.c b/drivers/net/wireless/hostap/hostap_cs.c
index d4f19af..e57b201 100644
--- a/drivers/net/wireless/hostap/hostap_cs.c
+++ b/drivers/net/wireless/hostap/hostap_cs.c
@@ -469,46 +469,11 @@ static void prism2_detach(struct pcmcia_device *link)
 /* run after a CARD_INSERTION event is received to configure the PCMCIA
  * socket and make the device available to the system */
 
-static int prism2_config_check(struct pcmcia_device *p_dev,
-			       cistpl_cftable_entry_t *cfg,
-			       cistpl_cftable_entry_t *dflt,
-			       void *priv_data)
+static int prism2_config_check(struct pcmcia_device *p_dev, void *priv_data)
 {
-	if (cfg->index == 0)
-		return -ENODEV;
-
-	PDEBUG(DEBUG_EXTRA, "Checking CFTABLE_ENTRY 0x%02X "
-	       "(default 0x%02X)\n", cfg->index, dflt->index);
-
-	if (cfg->vpp1.present & (1 << CISTPL_POWER_VNOM))
-		p_dev->vpp = cfg->vpp1.param[CISTPL_POWER_VNOM] / 10000;
-	else if (dflt->vpp1.present & (1 << CISTPL_POWER_VNOM))
-		p_dev->vpp = dflt->vpp1.param[CISTPL_POWER_VNOM] / 10000;
-
-	/* Do we need to allocate an interrupt? */
-	p_dev->config_flags |= CONF_ENABLE_IRQ;
-
-	/* IO window settings */
-	PDEBUG(DEBUG_EXTRA, "IO window settings: cfg->io.nwin=%d "
-	       "dflt->io.nwin=%d\n",
-	       cfg->io.nwin, dflt->io.nwin);
-	p_dev->resource[0]->end = p_dev->resource[1]->end = 0;
-	if ((cfg->io.nwin > 0) || (dflt->io.nwin > 0)) {
-		cistpl_io_t *io = (cfg->io.nwin) ? &cfg->io : &dflt->io;
-		p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
-		p_dev->resource[0]->flags |=
-					pcmcia_io_cfg_data_width(io->flags);
-		p_dev->io_lines = io->flags & CISTPL_IO_LINES_MASK;
-		p_dev->resource[0]->start = io->win[0].base;
-		p_dev->resource[0]->end = io->win[0].len;
-		if (io->nwin > 1) {
-			p_dev->resource[1]->flags = p_dev->resource[0]->flags;
-			p_dev->resource[1]->start = io->win[1].base;
-			p_dev->resource[1]->end = io->win[1].len;
-		}
-	}
+	if (p_dev->config_index == 0)
+		return -EINVAL;
 
-	/* This reserves IO space but doesn't actually enable it */
 	return pcmcia_request_io(p_dev);
 }
 
@@ -531,7 +496,7 @@ static int prism2_config(struct pcmcia_device *link)
 
 	/* Look for an appropriate configuration table entry in the CIS */
 	link->config_flags |= CONF_AUTO_SET_VPP | CONF_AUTO_AUDIO |
-		CONF_AUTO_CHECK_VCC;
+		CONF_AUTO_CHECK_VCC | CONF_AUTO_SET_IO | CONF_ENABLE_IRQ;
 	if (ignore_cis_vcc)
 		link->config_flags &= ~CONF_AUTO_CHECK_VCC;
 	ret = pcmcia_loop_config(link, prism2_config_check, NULL);
diff --git a/drivers/net/wireless/libertas/if_cs.c b/drivers/net/wireless/libertas/if_cs.c
index 031f3e6..2c6f28a 100644
--- a/drivers/net/wireless/libertas/if_cs.c
+++ b/drivers/net/wireless/libertas/if_cs.c
@@ -794,20 +794,12 @@ static void if_cs_release(struct pcmcia_device *p_dev)
  * insertion event.
  */
 
-static int if_cs_ioprobe(struct pcmcia_device *p_dev,
-			 cistpl_cftable_entry_t *cfg,
-			 cistpl_cftable_entry_t *dflt,
-			 void *priv_data)
+static int if_cs_ioprobe(struct pcmcia_device *p_dev, void *priv_data)
 {
+	p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
 	p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_AUTO;
-	p_dev->resource[0]->start = cfg->io.win[0].base;
-	p_dev->resource[0]->end = cfg->io.win[0].len;
 
-	/* Do we need to allocate an interrupt? */
-	p_dev->config_flags |= CONF_ENABLE_IRQ;
-
-	/* IO window settings */
-	if (cfg->io.nwin != 1) {
+	if (p_dev->resource[1]->end) {
 		lbs_pr_err("wrong CIS (check number of IO windows)\n");
 		return -ENODEV;
 	}
@@ -833,6 +825,8 @@ static int if_cs_probe(struct pcmcia_device *p_dev)
 	card->p_dev = p_dev;
 	p_dev->priv = card;
 
+	p_dev->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_IO;
+
 	if (pcmcia_loop_config(p_dev, if_cs_ioprobe, NULL)) {
 		lbs_pr_err("error in pcmcia_loop_config\n");
 		goto out1;
diff --git a/drivers/net/wireless/orinoco/orinoco_cs.c b/drivers/net/wireless/orinoco/orinoco_cs.c
index b921738..263dfe9 100644
--- a/drivers/net/wireless/orinoco/orinoco_cs.c
+++ b/drivers/net/wireless/orinoco/orinoco_cs.c
@@ -142,42 +142,12 @@ static void orinoco_cs_detach(struct pcmcia_device *link)
  * device available to the system.
  */
 
-static int orinoco_cs_config_check(struct pcmcia_device *p_dev,
-				   cistpl_cftable_entry_t *cfg,
-				   cistpl_cftable_entry_t *dflt,
-				   void *priv_data)
+static int orinoco_cs_config_check(struct pcmcia_device *p_dev, void *priv_data)
 {
-	if (cfg->index == 0)
-		goto next_entry;
-
-	/* Do we need to allocate an interrupt? */
-	p_dev->config_flags |= CONF_ENABLE_IRQ;
-
-	/* IO window settings */
-	p_dev->resource[0]->end = p_dev->resource[1]->end = 0;
-	if ((cfg->io.nwin > 0) || (dflt->io.nwin > 0)) {
-		cistpl_io_t *io = (cfg->io.nwin) ? &cfg->io : &dflt->io;
-		p_dev->io_lines = io->flags & CISTPL_IO_LINES_MASK;
-		p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
-		p_dev->resource[0]->flags |=
-			pcmcia_io_cfg_data_width(io->flags);
-		p_dev->resource[0]->start = io->win[0].base;
-		p_dev->resource[0]->end = io->win[0].len;
-		if (io->nwin > 1) {
-			p_dev->resource[1]->flags = p_dev->resource[0]->flags;
-			p_dev->resource[1]->start = io->win[1].base;
-			p_dev->resource[1]->end = io->win[1].len;
-		}
-
-		/* This reserves IO space but doesn't actually enable it */
-		if (pcmcia_request_io(p_dev) != 0)
-			goto next_entry;
-	}
-	return 0;
+	if (p_dev->config_index == 0)
+		return -EINVAL;
 
-next_entry:
-	pcmcia_disable_device(p_dev);
-	return -ENODEV;
+	return pcmcia_request_io(p_dev);
 };
 
 static int
@@ -202,7 +172,8 @@ orinoco_cs_config(struct pcmcia_device *link)
 	 * and most client drivers will only use the CIS to fill in
 	 * implementation-defined details.
 	 */
-	link->config_flags |= CONF_AUTO_SET_VPP | CONF_AUTO_CHECK_VCC;
+	link->config_flags |= CONF_AUTO_SET_VPP | CONF_AUTO_CHECK_VCC |
+		CONF_AUTO_SET_IO | CONF_ENABLE_IRQ;
 	if (ignore_cis_vcc)
 		link->config_flags &= ~CONF_AUTO_CHECK_VCC;
 	ret = pcmcia_loop_config(link, orinoco_cs_config_check, NULL);
diff --git a/drivers/net/wireless/orinoco/spectrum_cs.c b/drivers/net/wireless/orinoco/spectrum_cs.c
index f462c78..7844650 100644
--- a/drivers/net/wireless/orinoco/spectrum_cs.c
+++ b/drivers/net/wireless/orinoco/spectrum_cs.c
@@ -205,48 +205,12 @@ static void spectrum_cs_detach(struct pcmcia_device *link)
  */
 
 static int spectrum_cs_config_check(struct pcmcia_device *p_dev,
-				    cistpl_cftable_entry_t *cfg,
-				    cistpl_cftable_entry_t *dflt,
 				    void *priv_data)
 {
-	if (cfg->index == 0)
-		goto next_entry;
-
-	if (cfg->vpp1.present & (1 << CISTPL_POWER_VNOM))
-		p_dev->vpp =
-			cfg->vpp1.param[CISTPL_POWER_VNOM] / 10000;
-	else if (dflt->vpp1.present & (1 << CISTPL_POWER_VNOM))
-		p_dev->vpp =
-			dflt->vpp1.param[CISTPL_POWER_VNOM] / 10000;
-
-	/* Do we need to allocate an interrupt? */
-	p_dev->config_flags |= CONF_ENABLE_IRQ;
-
-	/* IO window settings */
-	p_dev->resource[0]->end = p_dev->resource[1]->end = 0;
-	if ((cfg->io.nwin > 0) || (dflt->io.nwin > 0)) {
-		cistpl_io_t *io = (cfg->io.nwin) ? &cfg->io : &dflt->io;
-		p_dev->io_lines = io->flags & CISTPL_IO_LINES_MASK;
-		p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
-		p_dev->resource[0]->flags |=
-			pcmcia_io_cfg_data_width(io->flags);
-		p_dev->resource[0]->start = io->win[0].base;
-		p_dev->resource[0]->end = io->win[0].len;
-		if (io->nwin > 1) {
-			p_dev->resource[1]->flags = p_dev->resource[0]->flags;
-			p_dev->resource[1]->start = io->win[1].base;
-			p_dev->resource[1]->end = io->win[1].len;
-		}
-
-		/* This reserves IO space but doesn't actually enable it */
-		if (pcmcia_request_io(p_dev) != 0)
-			goto next_entry;
-	}
-	return 0;
+	if (p_dev->config_index == 0)
+		return -EINVAL;
 
-next_entry:
-	pcmcia_disable_device(p_dev);
-	return -ENODEV;
+	return pcmcia_request_io(p_dev);
 };
 
 static int
@@ -271,7 +235,8 @@ spectrum_cs_config(struct pcmcia_device *link)
 	 * and most client drivers will only use the CIS to fill in
 	 * implementation-defined details.
 	 */
-	link->config_flags |= CONF_AUTO_SET_VPP | CONF_AUTO_CHECK_VCC;
+	link->config_flags |= CONF_AUTO_SET_VPP | CONF_AUTO_CHECK_VCC |
+		CONF_AUTO_SET_IO | CONF_ENABLE_IRQ;
 	if (ignore_cis_vcc)
 		link->config_flags &= ~CONF_AUTO_CHECK_VCC;
 	ret = pcmcia_loop_config(link, spectrum_cs_config_check, NULL);
diff --git a/drivers/parport/parport_cs.c b/drivers/parport/parport_cs.c
index 8c2a473..3730184 100644
--- a/drivers/parport/parport_cs.c
+++ b/drivers/parport/parport_cs.c
@@ -100,9 +100,7 @@ static int parport_probe(struct pcmcia_device *link)
     link->priv = info;
     info->p_dev = link;
 
-    link->resource[0]->flags |= IO_DATA_PATH_WIDTH_8;
-    link->resource[1]->flags |= IO_DATA_PATH_WIDTH_8;
-    link->config_flags |= CONF_ENABLE_IRQ;
+    link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_IO;
 
     return parport_config(link);
 } /* parport_attach */
@@ -133,27 +131,14 @@ static void parport_detach(struct pcmcia_device *link)
 
 ======================================================================*/
 
-static int parport_config_check(struct pcmcia_device *p_dev,
-				cistpl_cftable_entry_t *cfg,
-				cistpl_cftable_entry_t *dflt,
-				void *priv_data)
+static int parport_config_check(struct pcmcia_device *p_dev, void *priv_data)
 {
-	if ((cfg->io.nwin > 0) || (dflt->io.nwin > 0)) {
-		cistpl_io_t *io = (cfg->io.nwin) ? &cfg->io : &dflt->io;
-		p_dev->io_lines = io->flags & CISTPL_IO_LINES_MASK;
-		if (epp_mode)
-			p_dev->config_index |= FORCE_EPP_MODE;
-		p_dev->resource[0]->start = io->win[0].base;
-		p_dev->resource[0]->end = io->win[0].len;
-		if (io->nwin == 2) {
-			p_dev->resource[1]->start = io->win[1].base;
-			p_dev->resource[1]->end = io->win[1].len;
-		}
-		if (pcmcia_request_io(p_dev) != 0)
-			return -ENODEV;
-		return 0;
-	}
-	return -ENODEV;
+	p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
+	p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_8;
+	p_dev->resource[1]->flags &= ~IO_DATA_PATH_WIDTH;
+	p_dev->resource[1]->flags |= IO_DATA_PATH_WIDTH_8;
+
+	return pcmcia_request_io(p_dev);
 }
 
 static int parport_config(struct pcmcia_device *link)
@@ -164,6 +149,9 @@ static int parport_config(struct pcmcia_device *link)
 
     dev_dbg(&link->dev, "parport_config\n");
 
+    if (epp_mode)
+	    link->config_index |= FORCE_EPP_MODE;
+
     ret = pcmcia_loop_config(link, parport_config_check, NULL);
     if (ret)
 	    goto failed;
diff --git a/drivers/pcmcia/pcmcia_cis.c b/drivers/pcmcia/pcmcia_cis.c
index 160da06..e2c9241 100644
--- a/drivers/pcmcia/pcmcia_cis.c
+++ b/drivers/pcmcia/pcmcia_cis.c
@@ -6,7 +6,7 @@
  * are Copyright (C) 1999 David A. Hinds.  All Rights Reserved.
  *
  * Copyright (C) 1999	     David A. Hinds
- * Copyright (C) 2004-2009   Dominik Brodowski
+ * Copyright (C) 2004-2010   Dominik Brodowski
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2 as
@@ -125,13 +125,24 @@ next_entry:
 	return ret;
 }
 
+
+/**
+ * pcmcia_io_cfg_data_width() - convert cfgtable to data path width parameter
+ */
+static int pcmcia_io_cfg_data_width(unsigned int flags)
+{
+	if (!(flags & CISTPL_IO_8BIT))
+		return IO_DATA_PATH_WIDTH_16;
+	if (!(flags & CISTPL_IO_16BIT))
+		return IO_DATA_PATH_WIDTH_8;
+	return IO_DATA_PATH_WIDTH_AUTO;
+}
+
+
 struct pcmcia_cfg_mem {
 	struct pcmcia_device *p_dev;
+	int (*conf_check) (struct pcmcia_device *p_dev, void *priv_data);
 	void *priv_data;
-	int (*conf_check) (struct pcmcia_device *p_dev,
-			   cistpl_cftable_entry_t *cfg,
-			   cistpl_cftable_entry_t *dflt,
-			   void *priv_data);
 	cisparse_t parse;
 	cistpl_cftable_entry_t dflt;
 };
@@ -184,16 +195,63 @@ static int pcmcia_do_loop_config(tuple_t *tuple, cisparse_t *parse, void *priv)
 	if ((flags & CONF_AUTO_AUDIO) && (cfg->flags & CISTPL_CFTABLE_AUDIO))
 		p_dev->config_flags |= CONF_ENABLE_SPKR;
 
-	return cfg_mem->conf_check(cfg_mem->p_dev, cfg, &cfg_mem->dflt,
-				   cfg_mem->priv_data);
+
+	/* IO window settings? */
+	if (flags & CONF_AUTO_SET_IO) {
+		cistpl_io_t *io = (cfg->io.nwin) ? &cfg->io : &dflt->io;
+		int i = 0;
+
+		p_dev->resource[0]->start = p_dev->resource[0]->end = 0;
+		p_dev->resource[1]->start = p_dev->resource[1]->end = 0;
+		if (io->nwin == 0)
+			return -ENODEV;
+
+		p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
+		p_dev->resource[0]->flags |=
+					pcmcia_io_cfg_data_width(io->flags);
+		if (io->nwin > 1) {
+			/* For multifunction cards, by convention, we
+			 * configure the network function with window 0,
+			 * and serial with window 1 */
+			i = (io->win[1].len > io->win[0].len);
+			p_dev->resource[1]->flags = p_dev->resource[0]->flags;
+			p_dev->resource[1]->start = io->win[1-i].base;
+			p_dev->resource[1]->end = io->win[1-i].len;
+		}
+		p_dev->resource[0]->start = io->win[i].base;
+		p_dev->resource[0]->end = io->win[i].len;
+		p_dev->io_lines = io->flags & CISTPL_IO_LINES_MASK;
+	}
+
+	/* MEM window settings? */
+	if (flags & CONF_AUTO_SET_IOMEM) {
+		/* so far, we only set one memory window */
+		cistpl_mem_t *mem = (cfg->mem.nwin) ? &cfg->mem : &dflt->mem;
+
+		p_dev->resource[2]->start = p_dev->resource[2]->end = 0;
+		if (mem->nwin == 0)
+			return -ENODEV;
+
+		p_dev->resource[2]->start = mem->win[0].host_addr;
+		p_dev->resource[2]->end = mem->win[0].len;
+		if (p_dev->resource[2]->end < 0x1000)
+			p_dev->resource[2]->end = 0x1000;
+		p_dev->card_addr = mem->win[0].card_addr;
+	}
+
+	dev_dbg(&p_dev->dev,
+		"checking configuration %x: %pr %pr %pr (%d lines)\n",
+		p_dev->config_index, p_dev->resource[0], p_dev->resource[1],
+		p_dev->resource[2], p_dev->io_lines);
+
+	return cfg_mem->conf_check(p_dev, cfg_mem->priv_data);
 }
 
 /**
  * pcmcia_loop_config() - loop over configuration options
  * @p_dev:	the struct pcmcia_device which we need to loop for.
  * @conf_check:	function to call for each configuration option.
- *		It gets passed the struct pcmcia_device, the CIS data
- *		describing the configuration option, and private data
+ *		It gets passed the struct pcmcia_device and private data
  *		being passed to pcmcia_loop_config()
  * @priv_data:	private data to be passed to the conf_check function.
  *
@@ -203,8 +261,6 @@ static int pcmcia_do_loop_config(tuple_t *tuple, cisparse_t *parse, void *priv)
  */
 int pcmcia_loop_config(struct pcmcia_device *p_dev,
 		       int	(*conf_check)	(struct pcmcia_device *p_dev,
-						 cistpl_cftable_entry_t *cfg,
-						 cistpl_cftable_entry_t *dflt,
 						 void *priv_data),
 		       void *priv_data)
 {
diff --git a/drivers/scsi/pcmcia/aha152x_stub.c b/drivers/scsi/pcmcia/aha152x_stub.c
index 0b5fc2f..bd9ce09 100644
--- a/drivers/scsi/pcmcia/aha152x_stub.c
+++ b/drivers/scsi/pcmcia/aha152x_stub.c
@@ -99,9 +99,7 @@ static int aha152x_probe(struct pcmcia_device *link)
     info->p_dev = link;
     link->priv = info;
 
-    link->resource[0]->end = 0x20;
-    link->resource[0]->flags |= IO_DATA_PATH_WIDTH_AUTO;
-    link->config_flags |= CONF_ENABLE_IRQ;
+    link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_IO;
     link->config_regs = PRESENT_OPTION;
 
     return aha152x_config_cs(link);
@@ -121,24 +119,24 @@ static void aha152x_detach(struct pcmcia_device *link)
 
 /*====================================================================*/
 
-static int aha152x_config_check(struct pcmcia_device *p_dev,
-				cistpl_cftable_entry_t *cfg,
-				cistpl_cftable_entry_t *dflt,
-				void *priv_data)
+static int aha152x_config_check(struct pcmcia_device *p_dev, void *priv_data)
 {
 	p_dev->io_lines = 10;
+
 	/* For New Media T&J, look for a SCSI window */
-	if (cfg->io.win[0].len >= 0x20)
-		p_dev->resource[0]->start = cfg->io.win[0].base;
-	else if ((cfg->io.nwin > 1) &&
-		 (cfg->io.win[1].len >= 0x20))
-		p_dev->resource[0]->start = cfg->io.win[1].base;
-	if ((cfg->io.nwin > 0) &&
-	    (p_dev->resource[0]->start < 0xffff)) {
-		if (!pcmcia_request_io(p_dev))
-			return 0;
-	}
-	return -EINVAL;
+	if ((p_dev->resource[0]->end < 0x20) &&
+		(p_dev->resource[1]->end >= 0x20))
+		p_dev->resource[0]->start = p_dev->resource[1]->start;
+
+	if (p_dev->resource[0]->start >= 0xffff)
+		return -EINVAL;
+
+	p_dev->resource[1]->start = p_dev->resource[1]->end = 0;
+	p_dev->resource[0]->end = 0x20;
+	p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
+	p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_AUTO;
+
+	return pcmcia_request_io(p_dev);
 }
 
 static int aha152x_config_cs(struct pcmcia_device *link)
diff --git a/drivers/scsi/pcmcia/fdomain_stub.c b/drivers/scsi/pcmcia/fdomain_stub.c
index 3b9f311..f2dc627 100644
--- a/drivers/scsi/pcmcia/fdomain_stub.c
+++ b/drivers/scsi/pcmcia/fdomain_stub.c
@@ -82,9 +82,7 @@ static int fdomain_probe(struct pcmcia_device *link)
 
 	info->p_dev = link;
 	link->priv = info;
-	link->resource[0]->end = 0x10;
-	link->resource[0]->flags |= IO_DATA_PATH_WIDTH_AUTO;
-	link->config_flags |= CONF_ENABLE_IRQ;
+	link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_IO;
 	link->config_regs = PRESENT_OPTION;
 
 	return fdomain_config(link);
@@ -103,13 +101,12 @@ static void fdomain_detach(struct pcmcia_device *link)
 
 /*====================================================================*/
 
-static int fdomain_config_check(struct pcmcia_device *p_dev,
-				cistpl_cftable_entry_t *cfg,
-				cistpl_cftable_entry_t *dflt,
-				void *priv_data)
+static int fdomain_config_check(struct pcmcia_device *p_dev, void *priv_data)
 {
 	p_dev->io_lines = 10;
-	p_dev->resource[0]->start = cfg->io.win[0].base;
+	p_dev->resource[0]->end = 0x10;
+	p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
+	p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_AUTO;
 	return pcmcia_request_io(p_dev);
 }
 
diff --git a/drivers/scsi/pcmcia/nsp_cs.c b/drivers/scsi/pcmcia/nsp_cs.c
index 344d499..3b90ad9 100644
--- a/drivers/scsi/pcmcia/nsp_cs.c
+++ b/drivers/scsi/pcmcia/nsp_cs.c
@@ -1556,13 +1556,6 @@ static int nsp_cs_probe(struct pcmcia_device *link)
 
 	nsp_dbg(NSP_DEBUG_INIT, "info=0x%p", info);
 
-	/* The io structure describes IO port mapping */
-	link->resource[0]->end	 = 0x10;
-	link->resource[0]->flags = IO_DATA_PATH_WIDTH_AUTO;
-
-	/* General socket configuration */
-	link->config_flags	 |= CONF_ENABLE_IRQ;
-
 	ret = nsp_cs_config(link);
 
 	nsp_dbg(NSP_DEBUG_INIT, "link=0x%p", link);
@@ -1594,50 +1587,27 @@ static void nsp_cs_detach(struct pcmcia_device *link)
     ethernet device available to the system.
 ======================================================================*/
 
-static int nsp_cs_config_check(struct pcmcia_device *p_dev,
-			       cistpl_cftable_entry_t *cfg,
-			       cistpl_cftable_entry_t *dflt,
-			       void *priv_data)
+static int nsp_cs_config_check(struct pcmcia_device *p_dev, void *priv_data)
 {
 	nsp_hw_data		*data = priv_data;
 
-	if (cfg->index == 0)
+	if (p_dev->config_index == 0)
 		return -ENODEV;
 
-	/* IO window settings */
-	p_dev->resource[0]->end = p_dev->resource[1]->end = 0;
-	if ((cfg->io.nwin > 0) || (dflt->io.nwin > 0)) {
-		cistpl_io_t *io = (cfg->io.nwin) ? &cfg->io : &dflt->io;
-		p_dev->io_lines = io->flags & CISTPL_IO_LINES_MASK;
-		p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
-		p_dev->resource[0]->flags |=
-					pcmcia_io_cfg_data_width(io->flags);
-		p_dev->resource[0]->start = io->win[0].base;
-		p_dev->resource[0]->end = io->win[0].len;
-		if (io->nwin > 1) {
-			p_dev->resource[1]->flags = p_dev->resource[0]->flags;
-			p_dev->resource[1]->start = io->win[1].base;
-			p_dev->resource[1]->end = io->win[1].len;
-		}
-		/* This reserves IO space but doesn't actually enable it */
-		if (pcmcia_request_io(p_dev) != 0)
-			goto next_entry;
-	}
+	/* This reserves IO space but doesn't actually enable it */
+	if (pcmcia_request_io(p_dev) != 0)
+		goto next_entry;
 
-	if ((cfg->mem.nwin > 0) || (dflt->mem.nwin > 0)) {
-		cistpl_mem_t	*mem =
-			(cfg->mem.nwin) ? &cfg->mem : &dflt->mem;
+	if (resource_size(p_dev->resource[2])) {
 		p_dev->resource[2]->flags |= (WIN_DATA_WIDTH_16 |
 					WIN_MEMORY_TYPE_CM |
 					WIN_ENABLE);
-		p_dev->resource[2]->start = mem->win[0].host_addr;
-		p_dev->resource[2]->end = mem->win[0].len;
 		if (p_dev->resource[2]->end < 0x1000)
 			p_dev->resource[2]->end = 0x1000;
 		if (pcmcia_request_window(p_dev, p_dev->resource[2], 0) != 0)
 			goto next_entry;
 		if (pcmcia_map_mem_page(p_dev, p_dev->resource[2],
-						mem->win[0].card_addr) != 0)
+						p_dev->card_addr) != 0)
 			goto next_entry;
 
 		data->MmioAddress = (unsigned long)
@@ -1664,7 +1634,8 @@ static int nsp_cs_config(struct pcmcia_device *link)
 	nsp_dbg(NSP_DEBUG_INIT, "in");
 
 	link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_CHECK_VCC |
-		CONF_AUTO_SET_VPP | CONF_AUTO_AUDIO;
+		CONF_AUTO_SET_VPP | CONF_AUTO_AUDIO | CONF_AUTO_SET_IOMEM |
+		CONF_AUTO_SET_IO;
 
 	ret = pcmcia_loop_config(link, nsp_cs_config_check, data);
 	if (ret)
diff --git a/drivers/scsi/pcmcia/qlogic_stub.c b/drivers/scsi/pcmcia/qlogic_stub.c
index 468fd12..e8a06e3 100644
--- a/drivers/scsi/pcmcia/qlogic_stub.c
+++ b/drivers/scsi/pcmcia/qlogic_stub.c
@@ -155,9 +155,7 @@ static int qlogic_probe(struct pcmcia_device *link)
 		return -ENOMEM;
 	info->p_dev = link;
 	link->priv = info;
-	link->resource[0]->end = 16;
-	link->resource[0]->flags |= IO_DATA_PATH_WIDTH_AUTO;
-	link->config_flags |= CONF_ENABLE_IRQ;
+	link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_IO;
 	link->config_regs = PRESENT_OPTION;
 
 	return qlogic_config(link);
@@ -176,14 +174,11 @@ static void qlogic_detach(struct pcmcia_device *link)
 
 /*====================================================================*/
 
-static int qlogic_config_check(struct pcmcia_device *p_dev,
-			       cistpl_cftable_entry_t *cfg,
-			       cistpl_cftable_entry_t *dflt,
-			       void *priv_data)
+static int qlogic_config_check(struct pcmcia_device *p_dev, void *priv_data)
 {
 	p_dev->io_lines = 10;
-	p_dev->resource[0]->start = cfg->io.win[0].base;
-	p_dev->resource[0]->end = cfg->io.win[0].len;
+	p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
+	p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_AUTO;
 
 	if (p_dev->resource[0]->start == 0)
 		return -ENODEV;
diff --git a/drivers/scsi/pcmcia/sym53c500_cs.c b/drivers/scsi/pcmcia/sym53c500_cs.c
index 7a0bb9a..6ceb57c 100644
--- a/drivers/scsi/pcmcia/sym53c500_cs.c
+++ b/drivers/scsi/pcmcia/sym53c500_cs.c
@@ -683,14 +683,11 @@ static struct scsi_host_template sym53c500_driver_template = {
      .shost_attrs		= SYM53C500_shost_attrs
 };
 
-static int SYM53C500_config_check(struct pcmcia_device *p_dev,
-				  cistpl_cftable_entry_t *cfg,
-				  cistpl_cftable_entry_t *dflt,
-				  void *priv_data)
+static int SYM53C500_config_check(struct pcmcia_device *p_dev, void *priv_data)
 {
 	p_dev->io_lines = 10;
-	p_dev->resource[0]->start = cfg->io.win[0].base;
-	p_dev->resource[0]->end = cfg->io.win[0].len;
+	p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
+	p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_AUTO;
 
 	if (p_dev->resource[0]->start == 0)
 		return -ENODEV;
@@ -857,9 +854,7 @@ SYM53C500_probe(struct pcmcia_device *link)
 		return -ENOMEM;
 	info->p_dev = link;
 	link->priv = info;
-	link->resource[0]->end = 16;
-	link->resource[0]->flags |= IO_DATA_PATH_WIDTH_AUTO;
-	link->config_flags |= CONF_ENABLE_IRQ;
+	link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_IO;
 
 	return SYM53C500_config(link);
 } /* SYM53C500_attach */
diff --git a/drivers/serial/serial_cs.c b/drivers/serial/serial_cs.c
index a796a93..4225203 100644
--- a/drivers/serial/serial_cs.c
+++ b/drivers/serial/serial_cs.c
@@ -424,41 +424,45 @@ static int pfc_config(struct pcmcia_device *p_dev)
 	return -ENODEV;
 }
 
-static int simple_config_check(struct pcmcia_device *p_dev,
-			       cistpl_cftable_entry_t *cf,
-			       cistpl_cftable_entry_t *dflt,
-			       void *priv_data)
+static int simple_config_check(struct pcmcia_device *p_dev, void *priv_data)
 {
 	static const int size_table[2] = { 8, 16 };
 	int *try = priv_data;
 
-	p_dev->io_lines = ((*try & 0x1) == 0) ?
-			16 : cf->io.flags & CISTPL_IO_LINES_MASK;
+	if (p_dev->resource[0]->start == 0)
+		return -ENODEV;
 
-	if ((cf->io.nwin > 0) && (cf->io.win[0].len == size_table[(*try >> 1)])
-	    && (cf->io.win[0].base != 0)) {
-		p_dev->resource[0]->start = cf->io.win[0].base;
-		if (!pcmcia_request_io(p_dev))
-			return 0;
-	}
-	return -EINVAL;
+	if ((*try & 0x1) == 0)
+		p_dev->io_lines = 16;
+
+	if (p_dev->resource[0]->end != size_table[(*try >> 1)])
+		return -ENODEV;
+
+	p_dev->resource[0]->end = 8;
+	p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
+	p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_8;
+
+	return pcmcia_request_io(p_dev);
 }
 
 static int simple_config_check_notpicky(struct pcmcia_device *p_dev,
-					cistpl_cftable_entry_t *cf,
-					cistpl_cftable_entry_t *dflt,
 					void *priv_data)
 {
 	static const unsigned int base[5] = { 0x3f8, 0x2f8, 0x3e8, 0x2e8, 0x0 };
 	int j;
 
-	if ((cf->io.nwin > 0) && ((cf->io.flags & CISTPL_IO_LINES_MASK) <= 3)) {
-		for (j = 0; j < 5; j++) {
-			p_dev->resource[0]->start = base[j];
-			p_dev->io_lines = base[j] ? 16 : 3;
-			if (!pcmcia_request_io(p_dev))
-				return 0;
-		}
+	if (p_dev->io_lines > 3)
+		return -ENODEV;
+
+	p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
+	p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_8;
+	p_dev->resource[0]->end = 8;
+
+	for (j = 0; j < 5; j++) {
+		p_dev->resource[0]->start = base[j];
+		p_dev->io_lines = base[j] ? 16 : 3;
+		if (!pcmcia_request_io(p_dev))
+			return 0;
 	}
 	return -ENODEV;
 }
@@ -468,12 +472,9 @@ static int simple_config(struct pcmcia_device *link)
 	struct serial_info *info = link->priv;
 	int i = -ENODEV, try;
 
-	link->resource[0]->flags |= IO_DATA_PATH_WIDTH_8;
-	link->resource[0]->end = 8;
-
 	/* First pass: look for a config entry that looks normal.
 	 * Two tries: without IO aliases, then with aliases */
-	link->config_flags |= CONF_AUTO_SET_VPP;
+	link->config_flags |= CONF_AUTO_SET_VPP | CONF_AUTO_SET_IO;
 	for (try = 0; try < 4; try++)
 		if (!pcmcia_loop_config(link, simple_config_check, &try))
 			goto found_port;
@@ -503,43 +504,44 @@ found_port:
 	return setup_serial(link, info, link->resource[0]->start, link->irq);
 }
 
-static int multi_config_check(struct pcmcia_device *p_dev,
-			      cistpl_cftable_entry_t *cf,
-			      cistpl_cftable_entry_t *dflt,
-			      void *priv_data)
+static int multi_config_check(struct pcmcia_device *p_dev, void *priv_data)
 {
-	int *base2 = priv_data;
+	int *multi = priv_data;
+
+	if (p_dev->resource[1]->end)
+		return -EINVAL;
 
 	/* The quad port cards have bad CIS's, so just look for a
 	   window larger than 8 ports and assume it will be right */
-	if ((cf->io.nwin == 1) && (cf->io.win[0].len > 8)) {
-		p_dev->resource[0]->start = cf->io.win[0].base;
-		p_dev->io_lines = cf->io.flags & CISTPL_IO_LINES_MASK;
-		if (!pcmcia_request_io(p_dev)) {
-			*base2 = p_dev->resource[0]->start + 8;
-			return 0;
-		}
-	}
-	return -ENODEV;
+	if (p_dev->resource[0]->end <= 8)
+		return -EINVAL;
+
+	p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
+	p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_8;
+	p_dev->resource[0]->end = *multi * 8;
+
+	if (pcmcia_request_io(p_dev))
+		return -ENODEV;
+	return 0;
 }
 
 static int multi_config_check_notpicky(struct pcmcia_device *p_dev,
-				       cistpl_cftable_entry_t *cf,
-				       cistpl_cftable_entry_t *dflt,
 				       void *priv_data)
 {
 	int *base2 = priv_data;
 
-	if (cf->io.nwin == 2) {
-		p_dev->resource[0]->start = cf->io.win[0].base;
-		p_dev->resource[1]->start = cf->io.win[1].base;
-		p_dev->io_lines = cf->io.flags & CISTPL_IO_LINES_MASK;
-		if (!pcmcia_request_io(p_dev)) {
-			*base2 = p_dev->resource[1]->start;
-			return 0;
-		}
-	}
-	return -ENODEV;
+	if (!p_dev->resource[0]->end || !p_dev->resource[1]->end)
+		return -ENODEV;
+
+	p_dev->resource[0]->end = p_dev->resource[1]->end = 8;
+	p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
+	p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_8;
+
+	if (pcmcia_request_io(p_dev))
+		return -ENODEV;
+
+	*base2 = p_dev->resource[0]->start + 8;
+	return 0;
 }
 
 static int multi_config(struct pcmcia_device *link)
@@ -547,12 +549,12 @@ static int multi_config(struct pcmcia_device *link)
 	struct serial_info *info = link->priv;
 	int i, base2 = 0;
 
+	link->config_flags |= CONF_AUTO_SET_IO;
 	/* First, look for a generic full-sized window */
-	link->resource[0]->flags |= IO_DATA_PATH_WIDTH_8;
-	link->resource[0]->end = info->multi * 8;
-	if (pcmcia_loop_config(link, multi_config_check, &base2)) {
+	if (!pcmcia_loop_config(link, multi_config_check, &info->multi))
+		base2 = link->resource[0]->start + 8;
+	else {
 		/* If that didn't work, look for two windows */
-		link->resource[0]->end = link->resource[1]->end = 8;
 		info->multi = 2;
 		if (pcmcia_loop_config(link, multi_config_check_notpicky,
 				       &base2)) {
@@ -587,7 +589,7 @@ static int multi_config(struct pcmcia_device *link)
 		    link->config_index == 3) {
 			err = setup_serial(link, info, base2,
 					link->irq);
-			base2 = link->resource[0]->start;;
+			base2 = link->resource[0]->start;
 		} else {
 			err = setup_serial(link, info, link->resource[0]->start,
 					link->irq);
@@ -611,18 +613,18 @@ static int multi_config(struct pcmcia_device *link)
 	return 0;
 }
 
-static int serial_check_for_multi(struct pcmcia_device *p_dev,
-				  cistpl_cftable_entry_t *cf,
-				  cistpl_cftable_entry_t *dflt,
-				  void *priv_data)
+static int serial_check_for_multi(struct pcmcia_device *p_dev,  void *priv_data)
 {
 	struct serial_info *info = p_dev->priv;
 
-	if ((cf->io.nwin == 1) && (cf->io.win[0].len % 8 == 0))
-		info->multi = cf->io.win[0].len >> 3;
+	if (!p_dev->resource[0]->end)
+		return -EINVAL;
+
+	if ((!p_dev->resource[1]->end) && (p_dev->resource[0]->end % 8 == 0))
+		info->multi = p_dev->resource[0]->end >> 3;
 
-	if ((cf->io.nwin == 2) && (cf->io.win[0].len == 8) &&
-		(cf->io.win[1].len == 8))
+	if ((p_dev->resource[1]->end) && (p_dev->resource[0]->end == 8)
+		&& (p_dev->resource[1]->end == 8))
 		info->multi = 2;
 
 	return 0; /* break */
diff --git a/drivers/staging/comedi/drivers/cb_das16_cs.c b/drivers/staging/comedi/drivers/cb_das16_cs.c
index 678fbf6..c43c689 100644
--- a/drivers/staging/comedi/drivers/cb_das16_cs.c
+++ b/drivers/staging/comedi/drivers/cb_das16_cs.c
@@ -710,36 +710,12 @@ static void das16cs_pcmcia_detach(struct pcmcia_device *link)
 
 
 static int das16cs_pcmcia_config_loop(struct pcmcia_device *p_dev,
-				cistpl_cftable_entry_t *cfg,
-				cistpl_cftable_entry_t *dflt,
 				void *priv_data)
 {
-	if (cfg->index == 0)
+	if (p_dev->config_index == 0)
 		return -EINVAL;
 
-	/* Do we need to allocate an interrupt? */
-	p_dev->config_flags |= CONF_ENABLE_IRQ;
-
-	/* IO window settings */
-	p_dev->resource[0]->end = p_dev->resource[1]->end = 0;
-	if ((cfg->io.nwin > 0) || (dflt->io.nwin > 0)) {
-		cistpl_io_t *io = (cfg->io.nwin) ? &cfg->io : &dflt->io;
-		p_dev->io_lines = io->flags & CISTPL_IO_LINES_MASK;
-		p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
-		p_dev->resource[0]->flags |=
-			pcmcia_io_cfg_data_width(io->flags);
-		p_dev->resource[0]->start = io->win[0].base;
-		p_dev->resource[0]->end = io->win[0].len;
-		if (io->nwin > 1) {
-			p_dev->resource[1]->flags = p_dev->resource[0]->flags;
-			p_dev->resource[1]->start = io->win[1].base;
-			p_dev->resource[1]->end = io->win[1].len;
-		}
-		/* This reserves IO space but doesn't actually enable it */
-		return pcmcia_request_io(p_dev);
-	}
-
-	return 0;
+	return pcmcia_request_io(p_dev);
 }
 
 static void das16cs_pcmcia_config(struct pcmcia_device *link)
@@ -748,6 +724,9 @@ static void das16cs_pcmcia_config(struct pcmcia_device *link)
 
 	dev_dbg(&link->dev, "das16cs_pcmcia_config\n");
 
+	/* Do we need to allocate an interrupt? */
+	link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_IO;
+
 	ret = pcmcia_loop_config(link, das16cs_pcmcia_config_loop, NULL);
 	if (ret) {
 		dev_warn(&link->dev, "no configuration found\n");
diff --git a/drivers/staging/comedi/drivers/das08_cs.c b/drivers/staging/comedi/drivers/das08_cs.c
index 12a96b7..d395909 100644
--- a/drivers/staging/comedi/drivers/das08_cs.c
+++ b/drivers/staging/comedi/drivers/das08_cs.c
@@ -192,35 +192,12 @@ static void das08_pcmcia_detach(struct pcmcia_device *link)
 
 
 static int das08_pcmcia_config_loop(struct pcmcia_device *p_dev,
-				cistpl_cftable_entry_t *cfg,
-				cistpl_cftable_entry_t *dflt,
 				void *priv_data)
 {
-	if (cfg->index == 0)
-		return -ENODEV;
-
-	/* Do we need to allocate an interrupt? */
-	p_dev->config_flags |= CONF_ENABLE_IRQ;
-
-	/* IO window settings */
-	p_dev->resource[0]->end = p_dev->resource[1]->end = 0;
-	if ((cfg->io.nwin > 0) || (dflt->io.nwin > 0)) {
-		cistpl_io_t *io = (cfg->io.nwin) ? &cfg->io : &dflt->io;
-		p_dev->io_lines = io->flags & CISTPL_IO_LINES_MASK;
-		p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
-		p_dev->resource[0]->flags |=
-			pcmcia_io_cfg_data_width(io->flags);
-		p_dev->resource[0]->start = io->win[0].base;
-		p_dev->resource[0]->end = io->win[0].len;
-		if (io->nwin > 1) {
-			p_dev->resource[1]->flags = p_dev->resource[0]->flags;
-			p_dev->resource[1]->start = io->win[1].base;
-			p_dev->resource[1]->end = io->win[1].len;
-		}
-		/* This reserves IO space but doesn't actually enable it */
-		return pcmcia_request_io(p_dev);
-	}
-	return 0;
+	if (p_dev->config_index == 0)
+		return -EINVAL;
+
+	return pcmcia_request_io(p_dev);
 }
 
 
@@ -238,6 +215,8 @@ static void das08_pcmcia_config(struct pcmcia_device *link)
 
 	dev_dbg(&link->dev, "das08_pcmcia_config\n");
 
+	link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_IO;
+
 	ret = pcmcia_loop_config(link, das08_pcmcia_config_loop, NULL);
 	if (ret) {
 		dev_warn(&link->dev, "no configuration found\n");
diff --git a/drivers/staging/comedi/drivers/ni_daq_700.c b/drivers/staging/comedi/drivers/ni_daq_700.c
index f22dc0f..7129b0c 100644
--- a/drivers/staging/comedi/drivers/ni_daq_700.c
+++ b/drivers/staging/comedi/drivers/ni_daq_700.c
@@ -530,35 +530,12 @@ static void dio700_cs_detach(struct pcmcia_device *link)
 ======================================================================*/
 
 static int dio700_pcmcia_config_loop(struct pcmcia_device *p_dev,
-				cistpl_cftable_entry_t *cfg,
-				cistpl_cftable_entry_t *dflt,
 				void *priv_data)
 {
-	if (cfg->index == 0)
-		return -ENODEV;
-
-	/* IO window settings */
-	p_dev->resource[0]->end = p_dev->resource[1]->end = 0;
-	if ((cfg->io.nwin > 0) || (dflt->io.nwin > 0)) {
-		cistpl_io_t *io = (cfg->io.nwin) ? &cfg->io : &dflt->io;
-		p_dev->io_lines = io->flags & CISTPL_IO_LINES_MASK;
-		p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
-		p_dev->resource[0]->flags |=
-			pcmcia_io_cfg_data_width(io->flags);
-		p_dev->resource[0]->start = io->win[0].base;
-		p_dev->resource[0]->end = io->win[0].len;
-		if (io->nwin > 1) {
-			p_dev->resource[1]->flags = p_dev->resource[0]->flags;
-			p_dev->resource[1]->start = io->win[1].base;
-			p_dev->resource[1]->end = io->win[1].len;
-		}
-		/* This reserves IO space but doesn't actually enable it */
-		if (pcmcia_request_io(p_dev) != 0)
-			return -ENODEV;
-	}
+	if (p_dev->config_index == 0)
+		return -EINVAL;
 
-	/* If we got this far, we're cool! */
-	return 0;
+	return pcmcia_request_io(p_dev);
 }
 
 static void dio700_config(struct pcmcia_device *link)
@@ -570,7 +547,8 @@ static void dio700_config(struct pcmcia_device *link)
 
 	dev_dbg(&link->dev, "dio700_config\n");
 
-	link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_AUDIO;
+	link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_AUDIO |
+		CONF_AUTO_SET_IO;
 
 	ret = pcmcia_loop_config(link, dio700_pcmcia_config_loop, NULL);
 	if (ret) {
diff --git a/drivers/staging/comedi/drivers/ni_daq_dio24.c b/drivers/staging/comedi/drivers/ni_daq_dio24.c
index 6dc2b06..4defdda 100644
--- a/drivers/staging/comedi/drivers/ni_daq_dio24.c
+++ b/drivers/staging/comedi/drivers/ni_daq_dio24.c
@@ -282,35 +282,12 @@ static void dio24_cs_detach(struct pcmcia_device *link)
 ======================================================================*/
 
 static int dio24_pcmcia_config_loop(struct pcmcia_device *p_dev,
-				cistpl_cftable_entry_t *cfg,
-				cistpl_cftable_entry_t *dflt,
 				void *priv_data)
 {
-	if (cfg->index == 0)
-		return -ENODEV;
-
-	/* IO window settings */
-	p_dev->resource[0]->end = p_dev->resource[1]->end = 0;
-	if ((cfg->io.nwin > 0) || (dflt->io.nwin > 0)) {
-		cistpl_io_t *io = (cfg->io.nwin) ? &cfg->io : &dflt->io;
-		p_dev->io_lines = io->flags & CISTPL_IO_LINES_MASK;
-		p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
-		p_dev->resource[0]->flags |=
-			pcmcia_io_cfg_data_width(io->flags);
-		p_dev->resource[0]->start = io->win[0].base;
-		p_dev->resource[0]->end = io->win[0].len;
-		if (io->nwin > 1) {
-			p_dev->resource[1]->flags = p_dev->resource[0]->flags;
-			p_dev->resource[1]->start = io->win[1].base;
-			p_dev->resource[1]->end = io->win[1].len;
-		}
-		/* This reserves IO space but doesn't actually enable it */
-		if (pcmcia_request_io(p_dev) != 0)
-			return -ENODEV;
-	}
+	if (p_dev->config_index == 0)
+		return -EINVAL;
 
-	/* If we got this far, we're cool! */
-	return 0;
+	return pcmcia_request_io(p_dev);
 }
 
 static void dio24_config(struct pcmcia_device *link)
@@ -321,7 +298,8 @@ static void dio24_config(struct pcmcia_device *link)
 
 	dev_dbg(&link->dev, "dio24_config\n");
 
-	link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_AUDIO;
+	link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_AUDIO |
+		CONF_AUTO_SET_IO;
 
 	ret = pcmcia_loop_config(link, dio24_pcmcia_config_loop, NULL);
 	if (ret) {
diff --git a/drivers/staging/comedi/drivers/ni_labpc_cs.c b/drivers/staging/comedi/drivers/ni_labpc_cs.c
index 6eacbd7..5123b31 100644
--- a/drivers/staging/comedi/drivers/ni_labpc_cs.c
+++ b/drivers/staging/comedi/drivers/ni_labpc_cs.c
@@ -261,35 +261,12 @@ static void labpc_cs_detach(struct pcmcia_device *link)
 ======================================================================*/
 
 static int labpc_pcmcia_config_loop(struct pcmcia_device *p_dev,
-				cistpl_cftable_entry_t *cfg,
-				cistpl_cftable_entry_t *dflt,
 				void *priv_data)
 {
-	if (cfg->index == 0)
-		return -ENODEV;
-
-	/* IO window settings */
-	p_dev->resource[0]->end = p_dev->resource[1]->end = 0;
-	if ((cfg->io.nwin > 0) || (dflt->io.nwin > 0)) {
-		cistpl_io_t *io = (cfg->io.nwin) ? &cfg->io : &dflt->io;
-		p_dev->io_lines = io->flags & CISTPL_IO_LINES_MASK;
-		p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
-		p_dev->resource[0]->flags |=
-			pcmcia_io_cfg_data_width(io->flags);
-		p_dev->resource[0]->start = io->win[0].base;
-		p_dev->resource[0]->end = io->win[0].len;
-		if (io->nwin > 1) {
-			p_dev->resource[1]->flags = p_dev->resource[0]->flags;
-			p_dev->resource[1]->start = io->win[1].base;
-			p_dev->resource[1]->end = io->win[1].len;
-		}
-		/* This reserves IO space but doesn't actually enable it */
-		if (pcmcia_request_io(p_dev) != 0)
-			return -ENODEV;
-	}
+	if (p_dev->config_index == 0)
+		return -EINVAL;
 
-	/* If we got this far, we're cool! */
-	return 0;
+	return pcmcia_request_io(p_dev);
 }
 
 
@@ -300,7 +277,7 @@ static void labpc_config(struct pcmcia_device *link)
 	dev_dbg(&link->dev, "labpc_config\n");
 
 	link->config_flags |= CONF_ENABLE_IRQ | CONF_ENABLE_PULSE_IRQ |
-		CONF_AUTO_AUDIO;
+		CONF_AUTO_AUDIO | CONF_AUTO_SET_IO;
 
 	ret = pcmcia_loop_config(link, labpc_pcmcia_config_loop, NULL);
 	if (ret) {
@@ -316,7 +293,6 @@ static void labpc_config(struct pcmcia_device *link)
 	   the I/O windows and the interrupt mapping, and putting the
 	   card and host interface into "Memory and IO" mode.
 	 */
-	p_dev->config_flags |= CONF_ENABLE_IRQ | CONF_ENABLE_PULSE_IRQ;
 	ret = pcmcia_enable_device(link);
 	if (ret)
 		goto failed;
diff --git a/drivers/staging/comedi/drivers/ni_mio_cs.c b/drivers/staging/comedi/drivers/ni_mio_cs.c
index da4e2a2..f1e31d3 100644
--- a/drivers/staging/comedi/drivers/ni_mio_cs.c
+++ b/drivers/staging/comedi/drivers/ni_mio_cs.c
@@ -262,10 +262,6 @@ static struct pcmcia_device *cur_dev = NULL;
 
 static int cs_attach(struct pcmcia_device *link)
 {
-	link->resource[0]->flags |= IO_DATA_PATH_WIDTH_16;
-	link->resource[0]->end = 16;
-	link->config_flags |= CONF_ENABLE_IRQ;
-
 	cur_dev = link;
 
 	mio_cs_config(link);
@@ -299,15 +295,12 @@ static int mio_cs_resume(struct pcmcia_device *link)
 }
 
 
-static int mio_pcmcia_config_loop(struct pcmcia_device *p_dev,
-				cistpl_cftable_entry_t *cfg,
-				cistpl_cftable_entry_t *dflt,
-				void *priv_data)
+static int mio_pcmcia_config_loop(struct pcmcia_device *p_dev, void *priv_data)
 {
 	int base, ret;
 
-	p_dev->resource[0]->end = cfg->io.win[0].len;
-	p_dev->io_lines = cfg->io.flags & CISTPL_IO_LINES_MASK;
+	p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
+	p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_16;
 
 	for (base = 0x000; base < 0x400; base += 0x20) {
 		p_dev->resource[0]->start = base;
@@ -324,6 +317,7 @@ static void mio_cs_config(struct pcmcia_device *link)
 	int ret;
 
 	DPRINTK("mio_cs_config(link=%p)\n", link);
+	link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_IO;
 
 	ret = pcmcia_loop_config(link, mio_pcmcia_config_loop, NULL);
 	if (ret) {
diff --git a/drivers/staging/comedi/drivers/quatech_daqp_cs.c b/drivers/staging/comedi/drivers/quatech_daqp_cs.c
index 03a72d7..afd283d 100644
--- a/drivers/staging/comedi/drivers/quatech_daqp_cs.c
+++ b/drivers/staging/comedi/drivers/quatech_daqp_cs.c
@@ -1068,35 +1068,11 @@ static void daqp_cs_detach(struct pcmcia_device *link)
 ======================================================================*/
 
 
-static int daqp_pcmcia_config_loop(struct pcmcia_device *p_dev,
-				cistpl_cftable_entry_t *cfg,
-				cistpl_cftable_entry_t *dflt,
-				void *priv_data)
+static int daqp_pcmcia_config_loop(struct pcmcia_device *p_dev, void *priv_data)
 {
-	if (cfg->index == 0)
-		return -ENODEV;
+	if (p_dev->config_index == 0)
+		return -EINVAL;
 
-	/* Do we need to allocate an interrupt? */
-	p_dev->config_flags |= CONF_ENABLE_IRQ;
-
-	/* IO window settings */
-	p_dev->resource[0]->end = p_dev->resource[1]->end = 0;
-	if ((cfg->io.nwin > 0) || (dflt->io.nwin > 0)) {
-		cistpl_io_t *io = (cfg->io.nwin) ? &cfg->io : &dflt->io;
-		p_dev->io_lines = io->flags & CISTPL_IO_LINES_MASK;
-		p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
-		p_dev->resource[0]->flags |=
-			pcmcia_io_cfg_data_width(io->flags);
-		p_dev->resource[0]->start = io->win[0].base;
-		p_dev->resource[0]->end = io->win[0].len;
-		if (io->nwin > 1) {
-			p_dev->resource[1]->flags = p_dev->resource[0]->flags;
-			p_dev->resource[1]->start = io->win[1].base;
-			p_dev->resource[1]->end = io->win[1].len;
-		}
-	}
-
-	/* This reserves IO space but doesn't actually enable it */
 	return pcmcia_request_io(p_dev);
 }
 
@@ -1106,6 +1082,8 @@ static void daqp_cs_config(struct pcmcia_device *link)
 
 	dev_dbg(&link->dev, "daqp_cs_config\n");
 
+	link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_IO;
+
 	ret = pcmcia_loop_config(link, daqp_pcmcia_config_loop, NULL);
 	if (ret) {
 		dev_warn(&link->dev, "no configuration found\n");
diff --git a/drivers/telephony/ixj_pcmcia.c b/drivers/telephony/ixj_pcmcia.c
index 670a76b..76edd39 100644
--- a/drivers/telephony/ixj_pcmcia.c
+++ b/drivers/telephony/ixj_pcmcia.c
@@ -31,8 +31,6 @@ static int ixj_probe(struct pcmcia_device *p_dev)
 {
 	dev_dbg(&p_dev->dev, "ixj_attach()\n");
 	/* Create new ixj device */
-	p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_8;
-	p_dev->resource[1]->flags |= IO_DATA_PATH_WIDTH_8;
 	p_dev->priv = kzalloc(sizeof(struct ixj_info_t), GFP_KERNEL);
 	if (!p_dev->priv) {
 		return -ENOMEM;
@@ -109,36 +107,28 @@ failed:
 	return;
 }
 
-static int ixj_config_check(struct pcmcia_device *p_dev,
-			    cistpl_cftable_entry_t *cfg,
-			    cistpl_cftable_entry_t *dflt,
-			    void *priv_data)
+static int ixj_config_check(struct pcmcia_device *p_dev, void *priv_data)
 {
-	if ((cfg->io.nwin > 0) || (dflt->io.nwin > 0)) {
-		cistpl_io_t *io = (cfg->io.nwin) ? &cfg->io : &dflt->io;
-		p_dev->resource[0]->start = io->win[0].base;
-		p_dev->resource[0]->end = io->win[0].len;
-		p_dev->io_lines = 3;
-		if (io->nwin == 2) {
-			p_dev->resource[1]->start = io->win[1].base;
-			p_dev->resource[1]->end = io->win[1].len;
-		}
-		if (!pcmcia_request_io(p_dev))
-			return 0;
-	}
-	return -ENODEV;
+	p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
+	p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_8;
+	p_dev->resource[1]->flags &= ~IO_DATA_PATH_WIDTH;
+	p_dev->resource[1]->flags |= IO_DATA_PATH_WIDTH_8;
+	p_dev->io_lines = 3;
+
+	return pcmcia_request_io(p_dev);
 }
 
 static int ixj_config(struct pcmcia_device * link)
 {
 	IXJ *j;
 	ixj_info_t *info;
-	cistpl_cftable_entry_t dflt = { 0 };
 
 	info = link->priv;
 	dev_dbg(&link->dev, "ixj_config\n");
 
-	if (pcmcia_loop_config(link, ixj_config_check, &dflt))
+	link->config_flags = CONF_AUTO_SET_IO;
+
+	if (pcmcia_loop_config(link, ixj_config_check, NULL))
 		goto failed;
 
 	if (pcmcia_enable_device(link))
diff --git a/drivers/usb/host/sl811_cs.c b/drivers/usb/host/sl811_cs.c
index d960629..81d7eea 100644
--- a/drivers/usb/host/sl811_cs.c
+++ b/drivers/usb/host/sl811_cs.c
@@ -131,28 +131,12 @@ static void sl811_cs_release(struct pcmcia_device * link)
 	platform_device_unregister(&platform_dev);
 }
 
-static int sl811_cs_config_check(struct pcmcia_device *p_dev,
-				 cistpl_cftable_entry_t *cfg,
-				 cistpl_cftable_entry_t *dflt,
-				 void *priv_data)
+static int sl811_cs_config_check(struct pcmcia_device *p_dev, void *priv_data)
 {
-	if (cfg->index == 0)
-		return -ENODEV;
+	if (p_dev->config_index == 0)
+		return -EINVAL;
 
-	/* IO window settings */
-	p_dev->resource[0]->end = p_dev->resource[1]->end = 0;
-	if ((cfg->io.nwin > 0) || (dflt->io.nwin > 0)) {
-		cistpl_io_t *io = (cfg->io.nwin) ? &cfg->io : &dflt->io;
-		p_dev->io_lines = io->flags & CISTPL_IO_LINES_MASK;
-
-		p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_8;
-		p_dev->resource[0]->start = io->win[0].base;
-		p_dev->resource[0]->end = io->win[0].len;
-
-		return pcmcia_request_io(p_dev);
-	}
-	pcmcia_disable_device(p_dev);
-	return -ENODEV;
+	return pcmcia_request_io(p_dev);
 }
 
 
@@ -164,7 +148,7 @@ static int sl811_cs_config(struct pcmcia_device *link)
 	dev_dbg(&link->dev, "sl811_cs_config\n");
 
 	link->config_flags |= CONF_ENABLE_IRQ |	CONF_AUTO_SET_VPP |
-		CONF_AUTO_CHECK_VCC;
+		CONF_AUTO_CHECK_VCC | CONF_AUTO_SET_IO;
 
 	if (pcmcia_loop_config(link, sl811_cs_config_check, NULL))
 		goto failed;
diff --git a/include/pcmcia/ds.h b/include/pcmcia/ds.h
index 0577e5f..0b8c8d4 100644
--- a/include/pcmcia/ds.h
+++ b/include/pcmcia/ds.h
@@ -93,6 +93,7 @@ struct pcmcia_device {
 	/* device setup */
 	unsigned int		irq;
 	struct resource		*resource[PCMCIA_NUM_RESOURCES];
+	resource_size_t		card_addr;	/* for the 1st IOMEM resource */
 	unsigned int		vpp;
 
 	unsigned int		config_flags;	/* CONF_ENABLE_ flags below */
@@ -175,8 +176,6 @@ int pcmcia_parse_tuple(tuple_t *tuple, cisparse_t *parse);
 /* loop CIS entries for valid configuration */
 int pcmcia_loop_config(struct pcmcia_device *p_dev,
 		       int	(*conf_check)	(struct pcmcia_device *p_dev,
-						 cistpl_cftable_entry_t *cf,
-						 cistpl_cftable_entry_t *dflt,
 						 void *priv_data),
 		       void *priv_data);
 
@@ -225,16 +224,6 @@ void pcmcia_disable_device(struct pcmcia_device *p_dev);
 #define IO_DATA_PATH_WIDTH_16	0x08
 #define IO_DATA_PATH_WIDTH_AUTO	0x10
 
-/* convert flag found in cfgtable to data path width parameter */
-static inline int pcmcia_io_cfg_data_width(unsigned int flags)
-{
-	if (!(flags & CISTPL_IO_8BIT))
-		return IO_DATA_PATH_WIDTH_16;
-	if (!(flags & CISTPL_IO_16BIT))
-		return IO_DATA_PATH_WIDTH_8;
-	return IO_DATA_PATH_WIDTH_AUTO;
-}
-
 /* IO memory */
 #define WIN_MEMORY_TYPE_CM	0x00 /* default */
 #define WIN_MEMORY_TYPE_AM	0x20 /* MAP_ATTRIB */
@@ -264,16 +253,17 @@ static inline int pcmcia_io_cfg_data_width(unsigned int flags)
 #define PRESENT_IOSIZE		0x200
 
 /* flags to be passed to pcmcia_enable_device() */
-#define CONF_ENABLE_IRQ         0x01
-#define CONF_ENABLE_SPKR        0x02
-#define CONF_ENABLE_PULSE_IRQ   0x04
-#define CONF_ENABLE_ESR         0x08
+#define CONF_ENABLE_IRQ         0x0001
+#define CONF_ENABLE_SPKR        0x0002
+#define CONF_ENABLE_PULSE_IRQ   0x0004
+#define CONF_ENABLE_ESR         0x0008
 
 /* flags used by pcmcia_loop_config() autoconfiguration */
-#define CONF_AUTO_CHECK_VCC	0x10 /* check for matching Vcc? */
-#define CONF_AUTO_SET_VPP	0x20 /* set Vpp? */
-#define CONF_AUTO_AUDIO		0x40 /* enable audio line? */
-
+#define CONF_AUTO_CHECK_VCC	0x0100 /* check for matching Vcc? */
+#define CONF_AUTO_SET_VPP	0x0200 /* set Vpp? */
+#define CONF_AUTO_AUDIO		0x0400 /* enable audio line? */
+#define CONF_AUTO_SET_IO	0x0800 /* set ->resource[0,1] */
+#define CONF_AUTO_SET_IOMEM	0x1000 /* set ->resource[2] */
 
 #endif /* __KERNEL__ */
 
-- 
1.7.0.4


^ permalink raw reply related

* Hands-Free Profile 1.6 + Wide Band Speech
From: Ingvar PALSSON @ 2010-10-21 13:55 UTC (permalink / raw)
  To: linux-bluetooth@vger.kernel.org

Hi,

ST-Ericsson plans to implement support for Hands-Free Profile 1.6 including Wide Band Speech, and we would like to contribute it to BlueZ.
We are not aware of any ongoing work within BlueZ on HFP 1.6, but would like to cooperate in order to have a design/implementation that can be accepted.

As a very first, and very rough, design proposal for the BlueZ parts in kernel can be mentioned:
- A new (SCO) socket option is used (set by HF control layer in user space prior to connect) in order to indicate that a transparent eSCO link is required.
- PCM/I2S or similar is used to transfer the 16kHz audio to the BT Controller. We have no intention to use HCI for 16 kHz eSCO. I.e. the mSBC codec must be integrated in the Controller (there is however nothing in the design that prevents HCI from being used for SCO data at a later stage if someone wishes to implement it).

Any feedback is very much appreciated!

Kind regards,
/Ingvar




^ permalink raw reply

* Re: [PATCH 0/1] Bluetooth: fix crash in L2CAP
From: Greg KH @ 2010-10-21 13:35 UTC (permalink / raw)
  To: Gustavo F. Padovan; +Cc: linux-bluetooth, stable, linux-kernel
In-Reply-To: <1287638393-8573-1-git-send-email-padovan@profusion.mobi>

On Thu, Oct 21, 2010 at 03:19:52AM -0200, Gustavo F. Padovan wrote:
> Hi Greg,
> 
> The following patch is good for 2.6.36.1. It arrived too late in linux-bluetooth
> and we didn't had time to put it into 2.6.36. It fixes a serious crash into
> the L2CAP layer. The issue isn't in 2.6.35 and below.

It needs to get into Linus's tree before I can accept it into the
-stable trees.  Please get it there and then send stable@kernel.org the
git commit id and I will add it.

Instructions on how to do this are in the file,
Documentation/stable_kernel_rules.txt.  Please read that for how to
properly do this.

thanks,

greg k-h

^ permalink raw reply

* Re: [PATCH v4] Bluetooth: btwilink driver
From: Pavan Savoy @ 2010-10-21 13:26 UTC (permalink / raw)
  To: padovan, marcel; +Cc: linux-bluetooth, linux-kernel, Pavan Savoy
In-Reply-To: <1287525975-17187-1-git-send-email-pavan_savoy@ti.com>

Gustavo, Marcel,

On Tue, Oct 19, 2010 at 6:06 PM,  <pavan_savoy@ti.com> wrote:
> From: Pavan Savoy <pavan_savoy@ti.com>
>
> v4 comments
>
> module init now returns what platform_driver_register returns.
> type casting of void* private data has been removed

If there are no more critical comments, can it go in?
Please review.

Thanks,
Pavan

> v3 comments
>
> Lizardo,
> I have taken care of most of the comments you had.
> Have re-wrote some of the code commenting you've mentioned.
> Thanks for the comments,
>
> Marcel, Gustavo, & list,
> Please review this version of patch.
>
> The other few like -EPERM for platform driver registration is to keep
> it similar to other drivers, type casting is maintained just to feel safe
> and have style similar to other drivers.
> BT_WILINK in Kconfig is similar to BT_MRVL.
> I hope those aren't too critical.
>
> -- patch description --
>
> This is the bluetooth protocol driver for the TI WiLink7 chipsets.
> Texas Instrument's WiLink chipsets combine wireless technologies
> like BT, FM, GPS and WLAN onto a single chip.
>
> This Bluetooth driver works on top of the TI_ST shared transport
> line discipline driver which also allows other drivers like
> FM V4L2 and GPS character driver to make use of the same UART interface.
>
> Kconfig and Makefile modifications to enable the Bluetooth
> driver for Texas Instrument's WiLink 7 chipset.
>
> Signed-off-by: Pavan Savoy <pavan_savoy@ti.com>
> ---
> =C2=A0drivers/bluetooth/Kconfig =C2=A0 =C2=A0| =C2=A0 10 +
> =C2=A0drivers/bluetooth/Makefile =C2=A0 | =C2=A0 =C2=A01 +
> =C2=A0drivers/bluetooth/btwilink.c | =C2=A0411 ++++++++++++++++++++++++++=
++++++++++++++++
> =C2=A03 files changed, 422 insertions(+), 0 deletions(-)
> =C2=A0create mode 100644 drivers/bluetooth/btwilink.c
>
> diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig
> index 02deef4..8e0de9a 100644
> --- a/drivers/bluetooth/Kconfig
> +++ b/drivers/bluetooth/Kconfig
> @@ -219,4 +219,14 @@ config BT_ATH3K
> =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0Say Y here to compile support for "Athe=
ros firmware download driver"
> =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0into the kernel or say M to compile it =
as module (ath3k).
>
> +config BT_WILINK
> + =C2=A0 =C2=A0 =C2=A0 tristate "Texas Instruments WiLink7 driver"
> + =C2=A0 =C2=A0 =C2=A0 depends on TI_ST
> + =C2=A0 =C2=A0 =C2=A0 help
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 This enables the Bluetooth driver for Texas=
 Instrument's BT/FM/GPS
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 combo devices. This makes use of shared tra=
nsport line discipline
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 core driver to communicate with the BT core=
 of the combo chip.
> +
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 Say Y here to compile support for Texas Ins=
trument's WiLink7 driver
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 into the kernel or say M to compile it as m=
odule.
> =C2=A0endmenu
> diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile
> index 71bdf13..f4460f4 100644
> --- a/drivers/bluetooth/Makefile
> +++ b/drivers/bluetooth/Makefile
> @@ -18,6 +18,7 @@ obj-$(CONFIG_BT_HCIBTSDIO) =C2=A0 =C2=A0+=3D btsdio.o
> =C2=A0obj-$(CONFIG_BT_ATH3K) =C2=A0 =C2=A0 =C2=A0 =C2=A0 +=3D ath3k.o
> =C2=A0obj-$(CONFIG_BT_MRVL) =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0+=3D btmrvl=
.o
> =C2=A0obj-$(CONFIG_BT_MRVL_SDIO) =C2=A0 =C2=A0 +=3D btmrvl_sdio.o
> +obj-$(CONFIG_BT_WILINK) =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0=
 =C2=A0+=3D btwilink.o
>
> =C2=A0btmrvl-y =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =
=C2=A0 =C2=A0 =C2=A0 :=3D btmrvl_main.o
> =C2=A0btmrvl-$(CONFIG_DEBUG_FS) =C2=A0 =C2=A0 =C2=A0+=3D btmrvl_debugfs.o
> diff --git a/drivers/bluetooth/btwilink.c b/drivers/bluetooth/btwilink.c
> new file mode 100644
> index 0000000..218efd6
> --- /dev/null
> +++ b/drivers/bluetooth/btwilink.c
> @@ -0,0 +1,411 @@
> +/*
> + * =C2=A0Texas Instrument's Bluetooth Driver For Shared Transport.
> + *
> + * =C2=A0Bluetooth Driver acts as interface between HCI core and
> + * =C2=A0TI Shared Transport Layer.
> + *
> + * =C2=A0Copyright (C) 2009-2010 Texas Instruments
> + * =C2=A0Author: Raja Mani <raja_mani@ti.com>
> + * =C2=A0 =C2=A0 Pavan Savoy <pavan_savoy@ti.com>
> + *
> + * =C2=A0This program is free software; you can redistribute it and/or m=
odify
> + * =C2=A0it under the terms of the GNU General Public License version 2 =
as
> + * =C2=A0published by the Free Software Foundation.
> + *
> + * =C2=A0This program is distributed in the hope that it will be useful,
> + * =C2=A0but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * =C2=A0MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. =C2=A0See =
the
> + * =C2=A0GNU General Public License for more details.
> + *
> + * =C2=A0You should have received a copy of the GNU General Public Licen=
se
> + * =C2=A0along with this program; if not, write to the Free Software
> + * =C2=A0Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA =C2=A0=
02111-1307 =C2=A0USA
> + *
> + */
> +
> +#include <linux/platform_device.h>
> +#include <net/bluetooth/bluetooth.h>
> +#include <net/bluetooth/hci_core.h>
> +
> +#include <linux/ti_wilink_st.h>
> +
> +/* Bluetooth Driver Version */
> +#define VERSION =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 "1.0"
> +
> +/* Number of seconds to wait for registration completion
> + * when ST returns PENDING status.
> + */
> +#define BT_REGISTER_TIMEOUT =C2=A0 6000 =C2=A0 =C2=A0 /* 6 sec */
> +
> +/**
> + * struct ti_st - driver operation structure
> + * @hdev: hci device pointer which binds to bt driver
> + * @reg_status: ST registration callback status
> + * @st_write: write function provided by the ST driver
> + * =C2=A0 =C2=A0 to be used by the driver during send_frame.
> + * @wait_reg_completion - completion sync between ti_st_open
> + * =C2=A0 =C2=A0 and ti_st_registration_completion_cb.
> + */
> +struct ti_st {
> + =C2=A0 =C2=A0 =C2=A0 struct hci_dev *hdev;
> + =C2=A0 =C2=A0 =C2=A0 char reg_status;
> + =C2=A0 =C2=A0 =C2=A0 long (*st_write) (struct sk_buff *);
> + =C2=A0 =C2=A0 =C2=A0 struct completion wait_reg_completion;
> +};
> +
> +static int reset;
> +
> +/* Increments HCI counters based on pocket ID (cmd,acl,sco) */
> +static inline void ti_st_tx_complete(struct ti_st *hst, int pkt_type)
> +{
> + =C2=A0 =C2=A0 =C2=A0 struct hci_dev *hdev;
> + =C2=A0 =C2=A0 =C2=A0 hdev =3D hst->hdev;
> +
> + =C2=A0 =C2=A0 =C2=A0 /* Update HCI stat counters */
> + =C2=A0 =C2=A0 =C2=A0 switch (pkt_type) {
> + =C2=A0 =C2=A0 =C2=A0 case HCI_COMMAND_PKT:
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 hdev->stat.cmd_tx++;
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 break;
> +
> + =C2=A0 =C2=A0 =C2=A0 case HCI_ACLDATA_PKT:
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 hdev->stat.acl_tx++;
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 break;
> +
> + =C2=A0 =C2=A0 =C2=A0 case HCI_SCODATA_PKT:
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 hdev->stat.sco_tx++;
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 break;
> + =C2=A0 =C2=A0 =C2=A0 }
> +}
> +
> +/* ------- Interfaces to Shared Transport ------ */
> +
> +/* Called by ST layer to indicate protocol registration completion
> + * status.ti_st_open() function will wait for signal from this
> + * API when st_register() function returns ST_PENDING.
> + */
> +static void st_registration_completion_cb(void *priv_data, char data)
> +{
> + =C2=A0 =C2=A0 =C2=A0 struct ti_st *lhst =3D priv_data;
> +
> + =C2=A0 =C2=A0 =C2=A0 /* Save registration status for use in ti_st_open(=
) */
> + =C2=A0 =C2=A0 =C2=A0 lhst->reg_status =3D data;
> + =C2=A0 =C2=A0 =C2=A0 /* complete the wait in ti_st_open() */
> + =C2=A0 =C2=A0 =C2=A0 complete(&lhst->wait_reg_completion);
> +}
> +
> +/* Called by Shared Transport layer when receive data is
> + * available */
> +static long st_receive(void *priv_data, struct sk_buff *skb)
> +{
> + =C2=A0 =C2=A0 =C2=A0 int err;
> + =C2=A0 =C2=A0 =C2=A0 struct ti_st *lhst =3D priv_data;
> +
> + =C2=A0 =C2=A0 =C2=A0 if (!skb)
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 return -EFAULT;
> +
> + =C2=A0 =C2=A0 =C2=A0 if (!lhst) {
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 kfree_skb(skb);
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 return -EFAULT;
> + =C2=A0 =C2=A0 =C2=A0 }
> +
> + =C2=A0 =C2=A0 =C2=A0 skb->dev =3D (struct net_device *)lhst->hdev;
> +
> + =C2=A0 =C2=A0 =C2=A0 /* Forward skb to HCI core layer */
> + =C2=A0 =C2=A0 =C2=A0 err =3D hci_recv_frame(skb);
> + =C2=A0 =C2=A0 =C2=A0 if (err) {
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 kfree_skb(skb);
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 BT_ERR("Unable to push=
 skb to HCI core(%d)", err);
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 return err;
> + =C2=A0 =C2=A0 =C2=A0 }
> +
> + =C2=A0 =C2=A0 =C2=A0 lhst->hdev->stat.byte_rx +=3D skb->len;
> +
> + =C2=A0 =C2=A0 =C2=A0 return 0;
> +}
> +
> +/* ------- Interfaces to HCI layer ------ */
> +/* protocol structure registered with shared transport */
> +static struct st_proto_s ti_st_proto =3D {
> + =C2=A0 =C2=A0 =C2=A0 .type =3D ST_BT,
> + =C2=A0 =C2=A0 =C2=A0 .recv =3D st_receive,
> + =C2=A0 =C2=A0 =C2=A0 .reg_complete_cb =3D st_registration_completion_cb=
,
> + =C2=A0 =C2=A0 =C2=A0 .priv_data =3D NULL,
> +};
> +
> +/* Called from HCI core to initialize the device */
> +static int ti_st_open(struct hci_dev *hdev)
> +{
> + =C2=A0 =C2=A0 =C2=A0 unsigned long timeleft;
> + =C2=A0 =C2=A0 =C2=A0 struct ti_st *hst;
> + =C2=A0 =C2=A0 =C2=A0 int err;
> +
> + =C2=A0 =C2=A0 =C2=A0 BT_DBG("%s %p", hdev->name, hdev);
> +
> + =C2=A0 =C2=A0 =C2=A0 /* provide contexts for callbacks from ST */
> + =C2=A0 =C2=A0 =C2=A0 hst =3D hdev->driver_data;
> + =C2=A0 =C2=A0 =C2=A0 ti_st_proto.priv_data =3D hst;
> +
> + =C2=A0 =C2=A0 =C2=A0 err =3D st_register(&ti_st_proto);
> + =C2=A0 =C2=A0 =C2=A0 if (err =3D=3D -EINPROGRESS) {
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 /* Prepare wait-for-co=
mpletion handler data structures.
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0* Needed to sync=
hronize this and
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0* st_registratio=
n_completion_cb() functions.
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0*/
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 init_completion(&hst->=
wait_reg_completion);
> +
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 /* Reset ST registrati=
on callback status flag , this value
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0* will be update=
d in ti_st_registration_completion_cb()
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0* function whene=
ver it called from ST driver.
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0*/
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 hst->reg_status =3D -E=
INPROGRESS;
> +
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 /* ST is busy with eit=
her protocol registration or firmware
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0* download. Wait=
 until the registration callback is called
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0*/
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 BT_DBG(" waiting for r=
egistration completion signal from ST");
> +
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 timeleft =3D wait_for_=
completion_timeout
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =
=C2=A0 (&hst->wait_reg_completion,
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =
=C2=A0 =C2=A0msecs_to_jiffies(BT_REGISTER_TIMEOUT));
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 if (!timeleft) {
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =
=C2=A0 BT_ERR("Timeout(%d sec),didn't get reg "
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 "completion =
signal from ST",
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 BT_REGISTER_=
TIMEOUT / 1000);
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =
=C2=A0 return -ETIMEDOUT;
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 }
> +
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 /* Is ST registration =
callback called with ERROR status? */
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 if (hst->reg_status !=
=3D 0) {
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =
=C2=A0 BT_ERR("ST registration completed with invalid "
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 "status %d",=
 hst->reg_status);
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =
=C2=A0 return -EAGAIN;
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 }
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 err =3D 0;
> + =C2=A0 =C2=A0 =C2=A0 } else if (err =3D=3D -EPERM) {
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 BT_ERR("st_register fa=
iled %d", err);
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 return err;
> + =C2=A0 =C2=A0 =C2=A0 }
> +
> + =C2=A0 =C2=A0 =C2=A0 hst->st_write =3D ti_st_proto.write;
> + =C2=A0 =C2=A0 =C2=A0 if (!hst->st_write) {
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 BT_ERR("undefined ST w=
rite function");
> +
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 /* Undo registration w=
ith ST */
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 err =3D st_unregister(=
ST_BT);
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 if (err)
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =
=C2=A0 BT_ERR("st_unregister() failed with error %d", err);
> +
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 hst->st_write =3D NULL=
;
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 return err;
> + =C2=A0 =C2=A0 =C2=A0 }
> +
> + =C2=A0 =C2=A0 =C2=A0 /* Registration with ST layer is successful,
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0* hardware is ready to accept commands from =
HCI core.
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0*/
> + =C2=A0 =C2=A0 =C2=A0 set_bit(HCI_RUNNING, &hdev->flags);
> +
> + =C2=A0 =C2=A0 =C2=A0 return err;
> +}
> +
> +/* Close device */
> +static int ti_st_close(struct hci_dev *hdev)
> +{
> + =C2=A0 =C2=A0 =C2=A0 int err;
> + =C2=A0 =C2=A0 =C2=A0 struct ti_st *hst =3D hdev->driver_data;
> +
> + =C2=A0 =C2=A0 =C2=A0 /* continue to unregister from transport */
> + =C2=A0 =C2=A0 =C2=A0 err =3D st_unregister(ST_BT);
> + =C2=A0 =C2=A0 =C2=A0 if (err)
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 BT_ERR("st_unregister(=
) failed with error %d", err);
> +
> + =C2=A0 =C2=A0 =C2=A0 hst->st_write =3D NULL;
> +
> + =C2=A0 =C2=A0 =C2=A0 return err;
> +}
> +
> +static int ti_st_send_frame(struct sk_buff *skb)
> +{
> + =C2=A0 =C2=A0 =C2=A0 struct hci_dev *hdev;
> + =C2=A0 =C2=A0 =C2=A0 struct ti_st *hst;
> + =C2=A0 =C2=A0 =C2=A0 long len;
> +
> + =C2=A0 =C2=A0 =C2=A0 if (!skb)
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 return -ENOMEM;
> +
> + =C2=A0 =C2=A0 =C2=A0 hdev =3D (struct hci_dev *)skb->dev;
> + =C2=A0 =C2=A0 =C2=A0 if (!hdev)
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 return -ENODEV;
> +
> + =C2=A0 =C2=A0 =C2=A0 if (!test_bit(HCI_RUNNING, &hdev->flags))
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 return -EBUSY;
> +
> + =C2=A0 =C2=A0 =C2=A0 hst =3D hdev->driver_data;
> +
> + =C2=A0 =C2=A0 =C2=A0 /* Prepend skb with frame type */
> + =C2=A0 =C2=A0 =C2=A0 memcpy(skb_push(skb, 1), &bt_cb(skb)->pkt_type, 1)=
;
> +
> + =C2=A0 =C2=A0 =C2=A0 BT_DBG(" %s: type %d len %d", hdev->name, bt_cb(sk=
b)->pkt_type,
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =
=C2=A0 skb->len);
> +
> + =C2=A0 =C2=A0 =C2=A0 /* Insert skb to shared transport layer's transmit=
 queue.
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0* Freeing skb memory is taken care in shared=
 transport layer,
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0* so don't free skb memory here.
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0*/
> + =C2=A0 =C2=A0 =C2=A0 if (!hst->st_write) {
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 kfree_skb(skb);
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 BT_ERR(" Could not wri=
te to ST (st_write is NULL)");
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 return -EAGAIN;
> + =C2=A0 =C2=A0 =C2=A0 }
> +
> + =C2=A0 =C2=A0 =C2=A0 len =3D hst->st_write(skb);
> + =C2=A0 =C2=A0 =C2=A0 if (len < 0) {
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 kfree_skb(skb);
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 BT_ERR(" ST write fail=
ed (%ld)", len);
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 return -EAGAIN;
> + =C2=A0 =C2=A0 =C2=A0 }
> +
> + =C2=A0 =C2=A0 =C2=A0 /* ST accepted our skb. So, Go ahead and do rest *=
/
> + =C2=A0 =C2=A0 =C2=A0 hdev->stat.byte_tx +=3D len;
> + =C2=A0 =C2=A0 =C2=A0 ti_st_tx_complete(hst, bt_cb(skb)->pkt_type);
> +
> + =C2=A0 =C2=A0 =C2=A0 return 0;
> +}
> +
> +static void ti_st_destruct(struct hci_dev *hdev)
> +{
> + =C2=A0 =C2=A0 =C2=A0 if (!hdev)
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 return;
> +
> + =C2=A0 =C2=A0 =C2=A0 BT_DBG("%s", hdev->name);
> +
> + =C2=A0 =C2=A0 =C2=A0 /* free ti_st memory */
> + =C2=A0 =C2=A0 =C2=A0 kfree(hdev->driver_data);
> +
> + =C2=A0 =C2=A0 =C2=A0 return;
> +}
> +
> +/* Creates new HCI device */
> +static int ti_st_register_dev(struct ti_st *hst)
> +{
> + =C2=A0 =C2=A0 =C2=A0 int err;
> + =C2=A0 =C2=A0 =C2=A0 struct hci_dev *hdev;
> +
> + =C2=A0 =C2=A0 =C2=A0 /* Initialize and register HCI device */
> + =C2=A0 =C2=A0 =C2=A0 hdev =3D hci_alloc_dev();
> + =C2=A0 =C2=A0 =C2=A0 if (!hdev)
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 return -ENOMEM;
> +
> + =C2=A0 =C2=A0 =C2=A0 BT_DBG("hdev %p", hdev);
> +
> + =C2=A0 =C2=A0 =C2=A0 hst->hdev =3D hdev;
> + =C2=A0 =C2=A0 =C2=A0 hdev->bus =3D HCI_UART;
> + =C2=A0 =C2=A0 =C2=A0 hdev->driver_data =3D hst;
> + =C2=A0 =C2=A0 =C2=A0 hdev->open =3D ti_st_open;
> + =C2=A0 =C2=A0 =C2=A0 hdev->close =3D ti_st_close;
> + =C2=A0 =C2=A0 =C2=A0 hdev->flush =3D NULL;
> + =C2=A0 =C2=A0 =C2=A0 hdev->send =3D ti_st_send_frame;
> + =C2=A0 =C2=A0 =C2=A0 hdev->destruct =3D ti_st_destruct;
> + =C2=A0 =C2=A0 =C2=A0 hdev->owner =3D THIS_MODULE;
> +
> + =C2=A0 =C2=A0 =C2=A0 if (reset)
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 set_bit(HCI_QUIRK_NO_R=
ESET, &hdev->quirks);
> +
> + =C2=A0 =C2=A0 =C2=A0 err =3D hci_register_dev(hdev);
> + =C2=A0 =C2=A0 =C2=A0 if (err < 0) {
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 BT_ERR("Can't register=
 HCI device error %d", err);
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 hci_free_dev(hdev);
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 return err;
> + =C2=A0 =C2=A0 =C2=A0 }
> +
> + =C2=A0 =C2=A0 =C2=A0 BT_DBG(" HCI device registered (hdev %p)", hdev);
> + =C2=A0 =C2=A0 =C2=A0 return 0;
> +}
> +
> +
> +static int bt_ti_probe(struct platform_device *pdev)
> +{
> + =C2=A0 =C2=A0 =C2=A0 int err;
> + =C2=A0 =C2=A0 =C2=A0 static struct ti_st *hst;
> +
> + =C2=A0 =C2=A0 =C2=A0 BT_DBG(" Bluetooth Driver Version %s", VERSION);
> +
> + =C2=A0 =C2=A0 =C2=A0 hst =3D kzalloc(sizeof(struct ti_st), GFP_KERNEL);
> + =C2=A0 =C2=A0 =C2=A0 if (!hst)
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 return -ENOMEM;
> +
> + =C2=A0 =C2=A0 =C2=A0 /* Expose "hciX" device to user space */
> + =C2=A0 =C2=A0 =C2=A0 err =3D ti_st_register_dev(hst);
> + =C2=A0 =C2=A0 =C2=A0 if (err) {
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 kfree(hst);
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 return err;
> + =C2=A0 =C2=A0 =C2=A0 }
> +
> + =C2=A0 =C2=A0 =C2=A0 dev_set_drvdata(&pdev->dev, hst);
> + =C2=A0 =C2=A0 =C2=A0 return err;
> +}
> +
> +static int bt_ti_remove(struct platform_device *pdev)
> +{
> + =C2=A0 =C2=A0 =C2=A0 struct ti_st *hst;
> + =C2=A0 =C2=A0 =C2=A0 struct hci_dev *hdev;
> +
> + =C2=A0 =C2=A0 =C2=A0 hst =3D dev_get_drvdata(&pdev->dev);
> +
> + =C2=A0 =C2=A0 =C2=A0 if (!hst)
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 return -EFAULT;
> +
> + =C2=A0 =C2=A0 =C2=A0 /* Deallocate local resource's memory =C2=A0*/
> + =C2=A0 =C2=A0 =C2=A0 hdev =3D hst->hdev;
> +
> + =C2=A0 =C2=A0 =C2=A0 if (!hdev) {
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 BT_ERR("Invalid hdev m=
emory");
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 kfree(hst);
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 return -EFAULT;
> + =C2=A0 =C2=A0 =C2=A0 }
> +
> + =C2=A0 =C2=A0 =C2=A0 ti_st_close(hdev);
> + =C2=A0 =C2=A0 =C2=A0 hci_unregister_dev(hdev);
> + =C2=A0 =C2=A0 =C2=A0 /* Free HCI device memory */
> + =C2=A0 =C2=A0 =C2=A0 hci_free_dev(hdev);
> +
> + =C2=A0 =C2=A0 =C2=A0 return 0;
> +}
> +
> +static struct platform_driver btwilink_driver =3D {
> + =C2=A0 =C2=A0 =C2=A0 .probe =3D bt_ti_probe,
> + =C2=A0 =C2=A0 =C2=A0 .remove =3D bt_ti_remove,
> + =C2=A0 =C2=A0 =C2=A0 .driver =3D {
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 .name =3D "btwilink",
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 .owner =3D THIS_MODULE=
,
> + =C2=A0 =C2=A0 =C2=A0 },
> +};
> +
> +/* ------- Module Init/Exit interfaces ------ */
> +static int __init bt_drv_init(void)
> +{
> + =C2=A0 =C2=A0 =C2=A0 long ret;
> +
> + =C2=A0 =C2=A0 =C2=A0 ret =3D platform_driver_register(&btwilink_driver)=
;
> + =C2=A0 =C2=A0 =C2=A0 if (ret !=3D 0) {
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 BT_ERR("btwilink platf=
orm driver registration failed");
> + =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 return ret;
> + =C2=A0 =C2=A0 =C2=A0 }
> + =C2=A0 =C2=A0 =C2=A0 return 0;
> +}
> +
> +static void __exit bt_drv_exit(void)
> +{
> + =C2=A0 =C2=A0 =C2=A0 platform_driver_unregister(&btwilink_driver);
> +}
> +
> +module_init(bt_drv_init);
> +module_exit(bt_drv_exit);
> +
> +/* ------ Module Info ------ */
> +
> +module_param(reset, bool, 0644);
> +MODULE_PARM_DESC(reset, "Send HCI reset command on initialization");
> +MODULE_AUTHOR("Raja Mani <raja_mani@ti.com>");
> +MODULE_DESCRIPTION("Bluetooth Driver for TI Shared Transport" VERSION);
> +MODULE_VERSION(VERSION);
> +MODULE_LICENSE("GPL");
> --
> 1.6.5
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-bluetooth=
" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at =C2=A0http://vger.kernel.org/majordomo-info.html
>

^ permalink raw reply

* [PATCH] Fix history listing queries in phonebook-tracker
From: Radoslaw Jablonski @ 2010-10-21 12:26 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Radoslaw Jablonski

Now we are able to display contact's name in vcard-listing when
contact has entry in phonebook available.
---
 plugins/phonebook-tracker.c |  140 ++++++++++++++++++++++++++++++++++++-------
 1 files changed, 118 insertions(+), 22 deletions(-)

diff --git a/plugins/phonebook-tracker.c b/plugins/phonebook-tracker.c
index f0e00b8..1c579d1 100644
--- a/plugins/phonebook-tracker.c
+++ b/plugins/phonebook-tracker.c
@@ -172,13 +172,34 @@
 	"nco:nameHonorificPrefix(?c) nco:nameHonorificSuffix(?c) "	\
 	"nco:phoneNumber(?h) "						\
 	"WHERE { "							\
+	"{"								\
+		"?c a nco:Contact . "					\
+		"?c nco:hasPhoneNumber ?h . "				\
 		"?call a nmo:Call ; "					\
 		"nmo:from ?c ; "					\
 		"nmo:isSent false ; "					\
 		"nmo:isAnswered false ."				\
-		"?c a nco:Contact . "					\
-	"OPTIONAL { ?c nco:hasPhoneNumber ?h . } "			\
-	"} ORDER BY DESC(nmo:receivedDate(?call))"
+	"}UNION{"							\
+		"?x a nco:Contact . "					\
+		"?x nco:hasPhoneNumber ?h . "				\
+		"?call a nmo:Call ; "					\
+		"nmo:from ?x ; "					\
+		"nmo:isSent false ; "					\
+		"nmo:isAnswered false ."				\
+		"?c a nco:PersonContact . "				\
+		"?c nco:hasPhoneNumber ?h . "				\
+	"} UNION { "							\
+		"?x a nco:Contact . "					\
+		"?x nco:hasPhoneNumber ?h . "				\
+		"?call a nmo:Call ; "					\
+		"nmo:from ?x ; "					\
+		"nmo:isSent false ; "					\
+		"nmo:isAnswered false ."				\
+		"?c a nco:PersonContact . "				\
+		"?c nco:hasAffiliation ?a . "				\
+		"?a nco:hasPhoneNumber ?h . "				\
+	"} "								\
+	"} GROUP BY ?call ORDER BY DESC(nmo:receivedDate(?call))"
 
 #define INCOMING_CALLS_QUERY						\
 	"SELECT nco:phoneNumber(?h) nco:fullname(?c) "			\
@@ -244,13 +265,34 @@
 	"nco:nameHonorificPrefix(?c) nco:nameHonorificSuffix(?c) "	\
 	"nco:phoneNumber(?h) "						\
 	"WHERE { "							\
+	"{"								\
+		"?c a nco:Contact . "					\
+		"?c nco:hasPhoneNumber ?h . "				\
 		"?call a nmo:Call ; "					\
 		"nmo:from ?c ; "					\
 		"nmo:isSent false ; "					\
 		"nmo:isAnswered true ."					\
-		"?c a nco:Contact . "					\
-	"OPTIONAL { ?c nco:hasPhoneNumber ?h . } "			\
-	"} ORDER BY DESC(nmo:receivedDate(?call))"
+	"} UNION { "							\
+		"?x a nco:Contact . "					\
+		"?x nco:hasPhoneNumber ?h ."				\
+		"?call a nmo:Call ; "					\
+		"nmo:from ?x ; "					\
+		"nmo:isSent false ; "					\
+		"nmo:isAnswered true ."					\
+		"?c a nco:PersonContact . "				\
+		"?c nco:hasPhoneNumber ?h ."				\
+	"}UNION { "							\
+		"?x a nco:Contact . "					\
+		"?x nco:hasPhoneNumber ?h ."				\
+		"?call a nmo:Call ; "					\
+		"nmo:from ?x ; "					\
+		"nmo:isSent false ; "					\
+		"nmo:isAnswered true ."					\
+		"?c a nco:PersonContact . "				\
+		"?c nco:hasAffiliation ?a . "				\
+		"?a nco:hasPhoneNumber ?h . "				\
+	"}"								\
+	"} GROUP BY ?call ORDER BY DESC(nmo:receivedDate(?call))"
 
 #define OUTGOING_CALLS_QUERY						\
 	"SELECT nco:phoneNumber(?h) nco:fullname(?c) "			\
@@ -313,12 +355,31 @@
 	"nco:nameHonorificPrefix(?c) nco:nameHonorificSuffix(?c) "	\
 	"nco:phoneNumber(?h) "						\
 	"WHERE { "							\
+	"{"								\
+		"?c a nco:Contact . "					\
+		"?c nco:hasPhoneNumber ?h . "				\
 		"?call a nmo:Call ; "					\
 		"nmo:to ?c ; "						\
 		"nmo:isSent true . "					\
-		"?c a nco:Contact . "					\
-	"OPTIONAL { ?c nco:hasPhoneNumber ?h . } "			\
-	"} ORDER BY DESC(nmo:sentDate(?call))"
+	"} UNION {"							\
+		"?x a nco:Contact . "					\
+		"?x nco:hasPhoneNumber ?h . "				\
+		"?call a nmo:Call ; "					\
+		"nmo:to ?x ; "						\
+		"nmo:isSent true . "					\
+		"?c a nco:PersonContact . "				\
+		"?c nco:hasPhoneNumber ?h . "				\
+	"} UNION {"							\
+		"?x a nco:Contact . "					\
+		"?x nco:hasPhoneNumber ?h . "				\
+		"?call a nmo:Call ; "					\
+		"nmo:to ?x ; "						\
+		"nmo:isSent true . "					\
+		"?c a nco:PersonContact . "				\
+		"?c nco:hasAffiliation ?a . "				\
+		"?a nco:hasPhoneNumber ?h . "				\
+	"}"								\
+	"} GROUP BY ?call ORDER BY DESC(nmo:sentDate(?call))"
 
 #define COMBINED_CALLS_QUERY						\
 	"SELECT nco:phoneNumber(?h) nco:fullname(?c) "			\
@@ -420,19 +481,54 @@
 	"nco:nameAdditional(?c) nco:nameHonorificPrefix(?c) "		\
 	"nco:nameHonorificSuffix(?c) nco:phoneNumber(?h) "		\
 	"WHERE { "							\
-	"{ "								\
-		"?call a nmo:Call ; "					\
-		"nmo:to ?c ; "						\
-		"nmo:isSent true . "					\
-		"?c a nco:Contact . "					\
-		"OPTIONAL { ?c nco:hasPhoneNumber ?h . } "		\
-	"} UNION { "							\
-		"?call a nmo:Call ; "					\
-		"nmo:from ?c ; "					\
-		"nmo:isSent false . "					\
-		"?c a nco:Contact . "					\
-		"OPTIONAL { ?c nco:hasPhoneNumber ?h . } "		\
-	"} } ORDER BY DESC(nmo:receivedDate(?call))"
+	"	{ "							\
+			"?c a nco:Contact . "				\
+			"?c nco:hasPhoneNumber ?h . "			\
+			"?call a nmo:Call ; "				\
+			"nmo:to ?c ; "					\
+			"nmo:isSent true . "				\
+		"} UNION {"						\
+			"?x a nco:Contact . "				\
+			"?x nco:hasPhoneNumber ?h . "			\
+			"?call a nmo:Call ; "				\
+			"nmo:to ?x ; "					\
+			"nmo:isSent true . "				\
+			"?c a nco:PersonContact . "			\
+			"?c nco:hasPhoneNumber ?h . "			\
+		"} UNION {"						\
+			"?x a nco:Contact . "				\
+			"?x nco:hasPhoneNumber ?h . "			\
+			"?call a nmo:Call ; "				\
+			"nmo:to ?x ; "					\
+			"nmo:isSent true . "				\
+			"?c a nco:PersonContact . "			\
+			"?c nco:hasAffiliation ?a . "			\
+			"?a nco:hasPhoneNumber ?h . "			\
+		"}UNION {"						\
+			"?c a nco:Contact . "				\
+			"?c nco:hasPhoneNumber ?h . "			\
+			"?call a nmo:Call ; "				\
+			"nmo:from ?c ; "				\
+			"nmo:isSent false . "				\
+		"} UNION {"						\
+			"?x a nco:Contact . "				\
+			"?x nco:hasPhoneNumber ?h . "			\
+			"?call a nmo:Call ; "				\
+			"nmo:from ?x ; "				\
+			"nmo:isSent false . "				\
+			"?c a nco:PersonContact . "			\
+			"?c nco:hasPhoneNumber ?h . "			\
+		"} UNION {"						\
+			"?x a nco:Contact . "				\
+			"?x nco:hasPhoneNumber ?h . "			\
+			"?call a nmo:Call ; "				\
+			"nmo:from ?x ; "				\
+			"nmo:isSent false . "				\
+			"?c a nco:PersonContact . "			\
+			"?c nco:hasAffiliation ?a . "			\
+			"?a nco:hasPhoneNumber ?h . "			\
+		"}"							\
+	"} GROUP BY ?call ORDER BY DESC(nmo:receivedDate(?call))"
 
 
 #define CONTACTS_QUERY_FROM_URI						\
-- 
1.7.0.4


^ permalink raw reply related

* Re: [PATCH 3/4] Sim Access Profile Server
From: Suraj Sumangala @ 2010-10-21  8:41 UTC (permalink / raw)
  To: Waldemar.Rymarkiewicz@tieto.com
  Cc: Suraj Sumangala, linux-bluetooth@vger.kernel.org,
	marcel@holtmann.org
In-Reply-To: <99B09243E1A5DA4898CDD8B700111448097D287CDD@EXMB04.eu.tieto.com>

Hi,

On 10/21/2010 2:08 PM, Waldemar.Rymarkiewicz@tieto.com wrote:
> Hi Suraj,
>
>>
>> Looks like this patch does not apply successfully on the
>> latest Bluez tree after applying the first two. Can you verify that?
>>
>
> I've just git pull the latest upstream, git am for all the patches and don't have the problem with applying them.
>
> /Waldek

Alright, may be it has something to do with my mail client.

Regards
Suraj

^ permalink raw reply

* RE: [PATCH 3/4] Sim Access Profile Server
From: Waldemar.Rymarkiewicz @ 2010-10-21  8:38 UTC (permalink / raw)
  To: suraj; +Cc: linux-bluetooth, marcel, Suraj.Sumangala
In-Reply-To: <4CBFE96A.9090805@Atheros.com>

Hi Suraj,

>
>Looks like this patch does not apply successfully on the=20
>latest Bluez tree after applying the first two. Can you verify that?
>

I've just git pull the latest upstream, git am for all the patches and don'=
t have the problem with applying them.

/Waldek=

^ permalink raw reply

* RE: AVRCP 1.4 : Future on Target Role
From: Waldemar.Rymarkiewicz @ 2010-10-21  8:30 UTC (permalink / raw)
  To: dstockwell, ag.shivendra, linux-bluetooth
In-Reply-To: <442C56D12F0C4CE0B6625FF181846FED@freqoneremote>

Hi David, 

>Regarding an AVRCP device, I have not found any on the market. 

There are already devices with AVRCP 1.4 on the market. See https://www.bluetooth.org/tpg/listings.cfm  for listing (use advanced searching).

Reagrads 
/Waldek



^ permalink raw reply

* Re: pull request: bluetooth-2.6 2010-10-18
From: David Miller @ 2010-10-21  7:54 UTC (permalink / raw)
  To: padovan; +Cc: linville, marcel, linux-wireless, linux-bluetooth
In-Reply-To: <20101018190208.GA3080@vigoh>

From: "Gustavo F. Padovan" <padovan@profusion.mobi>
Date: Mon, 18 Oct 2010 17:02:09 -0200

> * John W. Linville <linville@tuxdriver.com> [2010-10-18 16:08:31 -0400]:
> 
>> On Mon, Oct 18, 2010 at 03:55:40AM -0200, Gustavo F. Padovan wrote:
>> > Hi John,
>> > 
>> > Not sure if we still have time for a last minute fix, but here goes a
>> > fix for a NULL dereference in L2CAP layer for 2.6.36-rc8. Please also let
>> > me know if this is the right way to handle such last minute fix. Maybe you
>> > should want this directly through Dave. 
>> > 
>> > Thanks.
>> 
>> The fix seems fine to me.  Given the short schedule, it might be best
>> to skip the middleman and have Dave pull directly from you?
>> 
>> Dave, be forewarned that Gustavo has based his tree off 2.6.36-rc8 --
>> dunno if you are ready to pull that into net-2.6 or not.
> 
> If you guys think that rebase against Linus' tree could be a problem, I
> can start to base bluetooth-2.6 against net-2.6 for the next pull
> requests.

I've now pulled this.

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox