diff options
Diffstat (limited to 'drivers/bluetooth')
-rw-r--r-- | drivers/bluetooth/btusb.c | 38 |
1 files changed, 38 insertions, 0 deletions
diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index 82ea7b230b17..03b83aa91277 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -1794,6 +1794,7 @@ static int btusb_setup_csr(struct hci_dev *hdev) struct hci_rp_read_local_version *rp; struct sk_buff *skb; bool is_fake = false; + int ret; BT_DBG("%s", hdev->name); @@ -1882,6 +1883,43 @@ static int btusb_setup_csr(struct hci_dev *hdev) */ clear_bit(HCI_QUIRK_RESET_ON_CLOSE, &hdev->quirks); clear_bit(HCI_QUIRK_SIMULTANEOUS_DISCOVERY, &hdev->quirks); + + /* + * Special workaround for clones with a Barrot 8041a02 chip, + * these clones are really messed-up: + * 1. Their bulk rx endpoint will never report any data unless + * the device was suspended at least once (yes really). + * 2. They will not wakeup when autosuspended and receiving data + * on their bulk rx endpoint from e.g. a keyboard or mouse + * (IOW remote-wakeup support is broken for the bulk endpoint). + * + * To fix 1. enable runtime-suspend, force-suspend the + * hci and then wake-it up by disabling runtime-suspend. + * + * To fix 2. clear the hci's can_wake flag, this way the hci + * will still be autosuspended when it is not open. + */ + if (bcdDevice == 0x8891 && + le16_to_cpu(rp->lmp_subver) == 0x1012 && + le16_to_cpu(rp->hci_rev) == 0x0810 && + le16_to_cpu(rp->hci_ver) == BLUETOOTH_VER_4_0) { + bt_dev_warn(hdev, "CSR: detected a fake CSR dongle using a Barrot 8041a02 chip, this chip is very buggy and may have issues\n"); + + pm_runtime_allow(&data->udev->dev); + + ret = pm_runtime_suspend(&data->udev->dev); + if (ret >= 0) + msleep(200); + else + bt_dev_err(hdev, "Failed to suspend the device for Barrot 8041a02 receive-issue workaround\n"); + + pm_runtime_forbid(&data->udev->dev); + + device_set_wakeup_capable(&data->udev->dev, false); + /* Re-enable autosuspend if this was requested */ + if (enable_autosuspend) + usb_enable_autosuspend(data->udev); + } } kfree_skb(skb); |