[PATCH 8/9] Bluetooth: Add support for the ST-Ericsson CG2900.

From: Par-Gunnar Hjalmdahl
Date: Fri Oct 22 2010 - 06:40:39 EST


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@xxxxxxxxxxxxxx>
---
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@xxxxxxxxxxxxxx)
+ * Henrik Possung (henrik.possung@xxxxxxxxxxxxxx)
+ * Josef Kindberg (josef.kindberg@xxxxxxxxxxxxxx)
+ * Dariusz Szymszak (dariusz.xd.szymczak@xxxxxxxxxxxxxx)
+ * Kjell Andersson (kjell.k.andersson@xxxxxxxxxxxxxx)
+ *
+ * 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
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/