aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/bluetooth/btintel.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/bluetooth/btintel.c')
-rw-r--r--drivers/bluetooth/btintel.c369
1 files changed, 369 insertions, 0 deletions
diff --git a/drivers/bluetooth/btintel.c b/drivers/bluetooth/btintel.c
index 048423fd83bf..1f13e617bf56 100644
--- a/drivers/bluetooth/btintel.c
+++ b/drivers/bluetooth/btintel.c
@@ -22,6 +22,8 @@
*/
#include <linux/module.h>
+#include <linux/firmware.h>
+#include <linux/regmap.h>
#include <net/bluetooth/bluetooth.h>
#include <net/bluetooth/hci_core.h>
@@ -89,6 +91,75 @@ int btintel_set_bdaddr(struct hci_dev *hdev, const bdaddr_t *bdaddr)
}
EXPORT_SYMBOL_GPL(btintel_set_bdaddr);
+int btintel_set_diag(struct hci_dev *hdev, bool enable)
+{
+ struct sk_buff *skb;
+ u8 param[3];
+ int err;
+
+ if (enable) {
+ param[0] = 0x03;
+ param[1] = 0x03;
+ param[2] = 0x03;
+ } else {
+ param[0] = 0x00;
+ param[1] = 0x00;
+ param[2] = 0x00;
+ }
+
+ skb = __hci_cmd_sync(hdev, 0xfc43, 3, param, HCI_INIT_TIMEOUT);
+ if (IS_ERR(skb)) {
+ err = PTR_ERR(skb);
+ if (err == -ENODATA)
+ goto done;
+ BT_ERR("%s: Changing Intel diagnostic mode failed (%d)",
+ hdev->name, err);
+ return err;
+ }
+ kfree_skb(skb);
+
+done:
+ btintel_set_event_mask(hdev, enable);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(btintel_set_diag);
+
+int btintel_set_diag_mfg(struct hci_dev *hdev, bool enable)
+{
+ struct sk_buff *skb;
+ u8 param[2];
+ int err;
+
+ param[0] = 0x01;
+ param[1] = 0x00;
+
+ skb = __hci_cmd_sync(hdev, 0xfc11, 2, param, HCI_INIT_TIMEOUT);
+ if (IS_ERR(skb)) {
+ err = PTR_ERR(skb);
+ BT_ERR("%s: Entering Intel manufacturer mode failed (%d)",
+ hdev->name, err);
+ return PTR_ERR(skb);
+ }
+ kfree_skb(skb);
+
+ err = btintel_set_diag(hdev, enable);
+
+ param[0] = 0x00;
+ param[1] = 0x00;
+
+ skb = __hci_cmd_sync(hdev, 0xfc11, 2, param, HCI_INIT_TIMEOUT);
+ if (IS_ERR(skb)) {
+ err = PTR_ERR(skb);
+ BT_ERR("%s: Leaving Intel manufacturer mode failed (%d)",
+ hdev->name, err);
+ return PTR_ERR(skb);
+ }
+ kfree_skb(skb);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(btintel_set_diag_mfg);
+
void btintel_hw_error(struct hci_dev *hdev, u8 code)
{
struct sk_buff *skb;
@@ -169,6 +240,304 @@ int btintel_secure_send(struct hci_dev *hdev, u8 fragment_type, u32 plen,
}
EXPORT_SYMBOL_GPL(btintel_secure_send);
+int btintel_load_ddc_config(struct hci_dev *hdev, const char *ddc_name)
+{
+ const struct firmware *fw;
+ struct sk_buff *skb;
+ const u8 *fw_ptr;
+ int err;
+
+ err = request_firmware_direct(&fw, ddc_name, &hdev->dev);
+ if (err < 0) {
+ bt_dev_err(hdev, "Failed to load Intel DDC file %s (%d)",
+ ddc_name, err);
+ return err;
+ }
+
+ bt_dev_info(hdev, "Found Intel DDC parameters: %s", ddc_name);
+
+ fw_ptr = fw->data;
+
+ /* DDC file contains one or more DDC structure which has
+ * Length (1 byte), DDC ID (2 bytes), and DDC value (Length - 2).
+ */
+ while (fw->size > fw_ptr - fw->data) {
+ u8 cmd_plen = fw_ptr[0] + sizeof(u8);
+
+ skb = __hci_cmd_sync(hdev, 0xfc8b, cmd_plen, fw_ptr,
+ HCI_INIT_TIMEOUT);
+ if (IS_ERR(skb)) {
+ bt_dev_err(hdev, "Failed to send Intel_Write_DDC (%ld)",
+ PTR_ERR(skb));
+ release_firmware(fw);
+ return PTR_ERR(skb);
+ }
+
+ fw_ptr += cmd_plen;
+ kfree_skb(skb);
+ }
+
+ release_firmware(fw);
+
+ bt_dev_info(hdev, "Applying Intel DDC parameters completed");
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(btintel_load_ddc_config);
+
+int btintel_set_event_mask(struct hci_dev *hdev, bool debug)
+{
+ u8 mask[8] = { 0x87, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+ struct sk_buff *skb;
+ int err;
+
+ if (debug)
+ mask[1] |= 0x62;
+
+ skb = __hci_cmd_sync(hdev, 0xfc52, 8, mask, HCI_INIT_TIMEOUT);
+ if (IS_ERR(skb)) {
+ err = PTR_ERR(skb);
+ BT_ERR("%s: Setting Intel event mask failed (%d)",
+ hdev->name, err);
+ return err;
+ }
+ kfree_skb(skb);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(btintel_set_event_mask);
+
+int btintel_set_event_mask_mfg(struct hci_dev *hdev, bool debug)
+{
+ struct sk_buff *skb;
+ u8 param[2];
+ int err;
+
+ param[0] = 0x01;
+ param[1] = 0x00;
+
+ skb = __hci_cmd_sync(hdev, 0xfc11, 2, param, HCI_INIT_TIMEOUT);
+ if (IS_ERR(skb)) {
+ err = PTR_ERR(skb);
+ BT_ERR("%s: Entering Intel manufacturer mode failed (%d)",
+ hdev->name, err);
+ return PTR_ERR(skb);
+ }
+ kfree_skb(skb);
+
+ err = btintel_set_event_mask(hdev, debug);
+
+ param[0] = 0x00;
+ param[1] = 0x00;
+
+ skb = __hci_cmd_sync(hdev, 0xfc11, 2, param, HCI_INIT_TIMEOUT);
+ if (IS_ERR(skb)) {
+ err = PTR_ERR(skb);
+ BT_ERR("%s: Leaving Intel manufacturer mode failed (%d)",
+ hdev->name, err);
+ return PTR_ERR(skb);
+ }
+ kfree_skb(skb);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(btintel_set_event_mask_mfg);
+
+/* ------- REGMAP IBT SUPPORT ------- */
+
+#define IBT_REG_MODE_8BIT 0x00
+#define IBT_REG_MODE_16BIT 0x01
+#define IBT_REG_MODE_32BIT 0x02
+
+struct regmap_ibt_context {
+ struct hci_dev *hdev;
+ __u16 op_write;
+ __u16 op_read;
+};
+
+struct ibt_cp_reg_access {
+ __le32 addr;
+ __u8 mode;
+ __u8 len;
+ __u8 data[0];
+} __packed;
+
+struct ibt_rp_reg_access {
+ __u8 status;
+ __le32 addr;
+ __u8 data[0];
+} __packed;
+
+static int regmap_ibt_read(void *context, const void *addr, size_t reg_size,
+ void *val, size_t val_size)
+{
+ struct regmap_ibt_context *ctx = context;
+ struct ibt_cp_reg_access cp;
+ struct ibt_rp_reg_access *rp;
+ struct sk_buff *skb;
+ int err = 0;
+
+ if (reg_size != sizeof(__le32))
+ return -EINVAL;
+
+ switch (val_size) {
+ case 1:
+ cp.mode = IBT_REG_MODE_8BIT;
+ break;
+ case 2:
+ cp.mode = IBT_REG_MODE_16BIT;
+ break;
+ case 4:
+ cp.mode = IBT_REG_MODE_32BIT;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* regmap provides a little-endian formatted addr */
+ cp.addr = *(__le32 *)addr;
+ cp.len = val_size;
+
+ bt_dev_dbg(ctx->hdev, "Register (0x%x) read", le32_to_cpu(cp.addr));
+
+ skb = hci_cmd_sync(ctx->hdev, ctx->op_read, sizeof(cp), &cp,
+ HCI_CMD_TIMEOUT);
+ if (IS_ERR(skb)) {
+ err = PTR_ERR(skb);
+ bt_dev_err(ctx->hdev, "regmap: Register (0x%x) read error (%d)",
+ le32_to_cpu(cp.addr), err);
+ return err;
+ }
+
+ if (skb->len != sizeof(*rp) + val_size) {
+ bt_dev_err(ctx->hdev, "regmap: Register (0x%x) read error, bad len",
+ le32_to_cpu(cp.addr));
+ err = -EINVAL;
+ goto done;
+ }
+
+ rp = (struct ibt_rp_reg_access *)skb->data;
+
+ if (rp->addr != cp.addr) {
+ bt_dev_err(ctx->hdev, "regmap: Register (0x%x) read error, bad addr",
+ le32_to_cpu(rp->addr));
+ err = -EINVAL;
+ goto done;
+ }
+
+ memcpy(val, rp->data, val_size);
+
+done:
+ kfree_skb(skb);
+ return err;
+}
+
+static int regmap_ibt_gather_write(void *context,
+ const void *addr, size_t reg_size,
+ const void *val, size_t val_size)
+{
+ struct regmap_ibt_context *ctx = context;
+ struct ibt_cp_reg_access *cp;
+ struct sk_buff *skb;
+ int plen = sizeof(*cp) + val_size;
+ u8 mode;
+ int err = 0;
+
+ if (reg_size != sizeof(__le32))
+ return -EINVAL;
+
+ switch (val_size) {
+ case 1:
+ mode = IBT_REG_MODE_8BIT;
+ break;
+ case 2:
+ mode = IBT_REG_MODE_16BIT;
+ break;
+ case 4:
+ mode = IBT_REG_MODE_32BIT;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ cp = kmalloc(plen, GFP_KERNEL);
+ if (!cp)
+ return -ENOMEM;
+
+ /* regmap provides a little-endian formatted addr/value */
+ cp->addr = *(__le32 *)addr;
+ cp->mode = mode;
+ cp->len = val_size;
+ memcpy(&cp->data, val, val_size);
+
+ bt_dev_dbg(ctx->hdev, "Register (0x%x) write", le32_to_cpu(cp->addr));
+
+ skb = hci_cmd_sync(ctx->hdev, ctx->op_write, plen, cp, HCI_CMD_TIMEOUT);
+ if (IS_ERR(skb)) {
+ err = PTR_ERR(skb);
+ bt_dev_err(ctx->hdev, "regmap: Register (0x%x) write error (%d)",
+ le32_to_cpu(cp->addr), err);
+ goto done;
+ }
+ kfree_skb(skb);
+
+done:
+ kfree(cp);
+ return err;
+}
+
+static int regmap_ibt_write(void *context, const void *data, size_t count)
+{
+ /* data contains register+value, since we only support 32bit addr,
+ * minimum data size is 4 bytes.
+ */
+ if (WARN_ONCE(count < 4, "Invalid register access"))
+ return -EINVAL;
+
+ return regmap_ibt_gather_write(context, data, 4, data + 4, count - 4);
+}
+
+static void regmap_ibt_free_context(void *context)
+{
+ kfree(context);
+}
+
+static struct regmap_bus regmap_ibt = {
+ .read = regmap_ibt_read,
+ .write = regmap_ibt_write,
+ .gather_write = regmap_ibt_gather_write,
+ .free_context = regmap_ibt_free_context,
+ .reg_format_endian_default = REGMAP_ENDIAN_LITTLE,
+ .val_format_endian_default = REGMAP_ENDIAN_LITTLE,
+};
+
+/* Config is the same for all register regions */
+static const struct regmap_config regmap_ibt_cfg = {
+ .name = "btintel_regmap",
+ .reg_bits = 32,
+ .val_bits = 32,
+};
+
+struct regmap *btintel_regmap_init(struct hci_dev *hdev, u16 opcode_read,
+ u16 opcode_write)
+{
+ struct regmap_ibt_context *ctx;
+
+ bt_dev_info(hdev, "regmap: Init R%x-W%x region", opcode_read,
+ opcode_write);
+
+ ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return ERR_PTR(-ENOMEM);
+
+ ctx->op_read = opcode_read;
+ ctx->op_write = opcode_write;
+ ctx->hdev = hdev;
+
+ return regmap_init(&hdev->dev, &regmap_ibt, ctx, &regmap_ibt_cfg);
+}
+EXPORT_SYMBOL_GPL(btintel_regmap_init);
+
MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>");
MODULE_DESCRIPTION("Bluetooth support for Intel devices ver " VERSION);
MODULE_VERSION(VERSION);