mirror of
https://github.com/FEX-Emu/linux.git
synced 2024-12-27 11:55:53 +00:00
Bluetooth: btintel: Add iBT register access over HCI support
Add regmap ibt to support Intel Bluetooth silicon register access over HCI. Intel BT/FM combo chip allows to read/write some registers (e.g. FM registers) via its HCI interface. Read/Write operations are performed via a HCI transaction composed of a HCI command (host->controller) followed by a HCI command complete event (controller->host). Read/Write Command opcodes can be specified to the regmap init function. We define data formats which are intel/vendor specific. Register Read/Write HCI command payload (Host): Field: | REG ADDR | MODE | DATA_LEN | DATA... | size: | 32b | 8b | 8b | 8b* | Register Read HCI command complete event payload (Controller): Field: | CMD STATUS | REG ADDR | DATA... | size: | 8b | 32b | 8b* | Register Write HCI command complete event payload (Controller): Field: | CMD_STATUS | size: | 8b | Since this payload is HCI encapsulated, Little Endian byte order is used. Write/Read Example: If we write 0x0000002a at address 0x00008c04, with opcode_write 0xfc5d, The resulting transaction is (btmon trace): < HCI Command (0x3f|0x005d) plen 10 [hci0] 04 8c 00 00 02 04 2a 00 00 00 > HCI Event (0x0e) plen 4 Unknown (0x3f|0x005d) ncmd 1 00 Then, if we read the same register with opcode_read 0xfc5e: < HCI Command (0x3f|0x005e) plen 6 [hci0] 04 8c 00 00 02 04 > HCI Event (0x0e) plen 12 [hci0] Unknown (0x3f|0x005e) ncmd 1 00 04 8c 00 00 2a 00 00 00 Signed-off-by: Loic Poulain <loic.poulain@intel.com> Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
This commit is contained in:
parent
aa6555622c
commit
d06f107bcd
@ -4,6 +4,7 @@ menu "Bluetooth device drivers"
|
|||||||
|
|
||||||
config BT_INTEL
|
config BT_INTEL
|
||||||
tristate
|
tristate
|
||||||
|
select REGMAP
|
||||||
|
|
||||||
config BT_BCM
|
config BT_BCM
|
||||||
tristate
|
tristate
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
|
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
#include <linux/firmware.h>
|
#include <linux/firmware.h>
|
||||||
|
#include <linux/regmap.h>
|
||||||
|
|
||||||
#include <net/bluetooth/bluetooth.h>
|
#include <net/bluetooth/bluetooth.h>
|
||||||
#include <net/bluetooth/hci_core.h>
|
#include <net/bluetooth/hci_core.h>
|
||||||
@ -215,6 +216,201 @@ int btintel_load_ddc_config(struct hci_dev *hdev, const char *ddc_name)
|
|||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(btintel_load_ddc_config);
|
EXPORT_SYMBOL_GPL(btintel_load_ddc_config);
|
||||||
|
|
||||||
|
/* ------- 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, ®map_ibt, ctx, ®map_ibt_cfg);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(btintel_regmap_init);
|
||||||
|
|
||||||
MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>");
|
MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>");
|
||||||
MODULE_DESCRIPTION("Bluetooth support for Intel devices ver " VERSION);
|
MODULE_DESCRIPTION("Bluetooth support for Intel devices ver " VERSION);
|
||||||
MODULE_VERSION(VERSION);
|
MODULE_VERSION(VERSION);
|
||||||
|
@ -80,6 +80,9 @@ int btintel_secure_send(struct hci_dev *hdev, u8 fragment_type, u32 plen,
|
|||||||
const void *param);
|
const void *param);
|
||||||
int btintel_load_ddc_config(struct hci_dev *hdev, const char *ddc_name);
|
int btintel_load_ddc_config(struct hci_dev *hdev, const char *ddc_name);
|
||||||
|
|
||||||
|
struct regmap *btintel_regmap_init(struct hci_dev *hdev, u16 opcode_read,
|
||||||
|
u16 opcode_write);
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
static inline int btintel_check_bdaddr(struct hci_dev *hdev)
|
static inline int btintel_check_bdaddr(struct hci_dev *hdev)
|
||||||
@ -113,4 +116,10 @@ static inline int btintel_load_ddc_config(struct hci_dev *hdev,
|
|||||||
return -EOPNOTSUPP;
|
return -EOPNOTSUPP;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline struct regmap *btintel_regmap_init(struct hci_dev *hdev,
|
||||||
|
u16 opcode_read,
|
||||||
|
u16 opcode_write)
|
||||||
|
{
|
||||||
|
return ERR_PTR(-EINVAL);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
Loading…
Reference in New Issue
Block a user