Bluetooth: Add suspend/resume support to btusb driver
[pandora-kernel.git] / drivers / bluetooth / btusb.c
index 6a01068..0cd4a55 100644 (file)
@@ -102,14 +102,19 @@ static struct usb_device_id blacklist_table[] = {
        { USB_DEVICE(0x0a5c, 0x2101), .driver_info = BTUSB_RESET | BTUSB_WRONG_SCO_MTU },
 
        /* Broadcom BCM2046 */
+       { USB_DEVICE(0x0a5c, 0x2146), .driver_info = BTUSB_RESET },
        { USB_DEVICE(0x0a5c, 0x2151), .driver_info = BTUSB_RESET },
 
+       /* Apple MacBook Pro with Broadcom chip */
+       { USB_DEVICE(0x05ac, 0x820f), .driver_info = BTUSB_RESET },
+
        /* IBM/Lenovo ThinkPad with Broadcom chip */
        { USB_DEVICE(0x0a5c, 0x201e), .driver_info = BTUSB_RESET | BTUSB_WRONG_SCO_MTU },
        { USB_DEVICE(0x0a5c, 0x2110), .driver_info = BTUSB_RESET | BTUSB_WRONG_SCO_MTU },
 
        /* Targus ACB10US */
        { USB_DEVICE(0x0a5c, 0x2100), .driver_info = BTUSB_RESET },
+       { USB_DEVICE(0x0a5c, 0x2154), .driver_info = BTUSB_RESET },
 
        /* ANYCOM Bluetooth USB-200 and USB-250 */
        { USB_DEVICE(0x0a5c, 0x2111), .driver_info = BTUSB_RESET },
@@ -147,6 +152,9 @@ static struct usb_device_id blacklist_table[] = {
        { USB_DEVICE(0x050d, 0x0012), .driver_info = BTUSB_RESET | BTUSB_WRONG_SCO_MTU },
        { USB_DEVICE(0x050d, 0x0013), .driver_info = BTUSB_RESET | BTUSB_WRONG_SCO_MTU },
 
+       /* Belkin F8T016 device */
+       { USB_DEVICE(0x050d, 0x016a), .driver_info = BTUSB_RESET },
+
        /* Digianswer devices */
        { USB_DEVICE(0x08fd, 0x0001), .driver_info = BTUSB_DIGIANSWER },
        { USB_DEVICE(0x08fd, 0x0002), .driver_info = BTUSB_IGNORE },
@@ -169,6 +177,7 @@ static struct usb_device_id blacklist_table[] = {
 struct btusb_data {
        struct hci_dev       *hdev;
        struct usb_device    *udev;
+       struct usb_interface *intf;
        struct usb_interface *isoc;
 
        spinlock_t lock;
@@ -189,6 +198,7 @@ struct btusb_data {
        struct usb_endpoint_descriptor *isoc_rx_ep;
 
        int isoc_altsetting;
+       int suspend_count;
 };
 
 static void btusb_intr_complete(struct urb *urb)
@@ -227,7 +237,7 @@ static void btusb_intr_complete(struct urb *urb)
        }
 }
 
-static int btusb_submit_intr_urb(struct hci_dev *hdev)
+static int btusb_submit_intr_urb(struct hci_dev *hdev, gfp_t mem_flags)
 {
        struct btusb_data *data = hdev->driver_data;
        struct urb *urb;
@@ -240,13 +250,13 @@ static int btusb_submit_intr_urb(struct hci_dev *hdev)
        if (!data->intr_ep)
                return -ENODEV;
 
-       urb = usb_alloc_urb(0, GFP_ATOMIC);
+       urb = usb_alloc_urb(0, mem_flags);
        if (!urb)
                return -ENOMEM;
 
        size = le16_to_cpu(data->intr_ep->wMaxPacketSize);
 
-       buf = kmalloc(size, GFP_ATOMIC);
+       buf = kmalloc(size, mem_flags);
        if (!buf) {
                usb_free_urb(urb);
                return -ENOMEM;
@@ -262,12 +272,11 @@ static int btusb_submit_intr_urb(struct hci_dev *hdev)
 
        usb_anchor_urb(urb, &data->intr_anchor);
 
-       err = usb_submit_urb(urb, GFP_ATOMIC);
+       err = usb_submit_urb(urb, mem_flags);
        if (err < 0) {
                BT_ERR("%s urb %p submission failed (%d)",
                                                hdev->name, urb, -err);
                usb_unanchor_urb(urb);
-               kfree(buf);
        }
 
        usb_free_urb(urb);
@@ -311,7 +320,7 @@ static void btusb_bulk_complete(struct urb *urb)
        }
 }
 
-static int btusb_submit_bulk_urb(struct hci_dev *hdev)
+static int btusb_submit_bulk_urb(struct hci_dev *hdev, gfp_t mem_flags)
 {
        struct btusb_data *data = hdev->driver_data;
        struct urb *urb;
@@ -324,13 +333,13 @@ static int btusb_submit_bulk_urb(struct hci_dev *hdev)
        if (!data->bulk_rx_ep)
                return -ENODEV;
 
-       urb = usb_alloc_urb(0, GFP_KERNEL);
+       urb = usb_alloc_urb(0, mem_flags);
        if (!urb)
                return -ENOMEM;
 
        size = le16_to_cpu(data->bulk_rx_ep->wMaxPacketSize);
 
-       buf = kmalloc(size, GFP_KERNEL);
+       buf = kmalloc(size, mem_flags);
        if (!buf) {
                usb_free_urb(urb);
                return -ENOMEM;
@@ -345,12 +354,11 @@ static int btusb_submit_bulk_urb(struct hci_dev *hdev)
 
        usb_anchor_urb(urb, &data->bulk_anchor);
 
-       err = usb_submit_urb(urb, GFP_KERNEL);
+       err = usb_submit_urb(urb, mem_flags);
        if (err < 0) {
                BT_ERR("%s urb %p submission failed (%d)",
                                                hdev->name, urb, -err);
                usb_unanchor_urb(urb);
-               kfree(buf);
        }
 
        usb_free_urb(urb);
@@ -423,7 +431,7 @@ static void inline __fill_isoc_descriptor(struct urb *urb, int len, int mtu)
        urb->number_of_packets = i;
 }
 
-static int btusb_submit_isoc_urb(struct hci_dev *hdev)
+static int btusb_submit_isoc_urb(struct hci_dev *hdev, gfp_t mem_flags)
 {
        struct btusb_data *data = hdev->driver_data;
        struct urb *urb;
@@ -436,14 +444,14 @@ static int btusb_submit_isoc_urb(struct hci_dev *hdev)
        if (!data->isoc_rx_ep)
                return -ENODEV;
 
-       urb = usb_alloc_urb(BTUSB_MAX_ISOC_FRAMES, GFP_KERNEL);
+       urb = usb_alloc_urb(BTUSB_MAX_ISOC_FRAMES, mem_flags);
        if (!urb)
                return -ENOMEM;
 
        size = le16_to_cpu(data->isoc_rx_ep->wMaxPacketSize) *
                                                BTUSB_MAX_ISOC_FRAMES;
 
-       buf = kmalloc(size, GFP_KERNEL);
+       buf = kmalloc(size, mem_flags);
        if (!buf) {
                usb_free_urb(urb);
                return -ENOMEM;
@@ -466,12 +474,11 @@ static int btusb_submit_isoc_urb(struct hci_dev *hdev)
 
        usb_anchor_urb(urb, &data->isoc_anchor);
 
-       err = usb_submit_urb(urb, GFP_KERNEL);
+       err = usb_submit_urb(urb, mem_flags);
        if (err < 0) {
                BT_ERR("%s urb %p submission failed (%d)",
                                                hdev->name, urb, -err);
                usb_unanchor_urb(urb);
-               kfree(buf);
        }
 
        usb_free_urb(urb);
@@ -514,9 +521,9 @@ static int btusb_open(struct hci_dev *hdev)
        if (test_and_set_bit(BTUSB_INTR_RUNNING, &data->flags))
                return 0;
 
-       err = btusb_submit_intr_urb(hdev);
+       err = btusb_submit_intr_urb(hdev, GFP_KERNEL);
        if (err < 0) {
-               clear_bit(BTUSB_INTR_RUNNING, &hdev->flags);
+               clear_bit(BTUSB_INTR_RUNNING, &data->flags);
                clear_bit(HCI_RUNNING, &hdev->flags);
        }
 
@@ -532,8 +539,10 @@ static int btusb_close(struct hci_dev *hdev)
        if (!test_and_clear_bit(HCI_RUNNING, &hdev->flags))
                return 0;
 
+       cancel_work_sync(&data->work);
+
        clear_bit(BTUSB_ISOC_RUNNING, &data->flags);
-       usb_kill_anchored_urbs(&data->intr_anchor);
+       usb_kill_anchored_urbs(&data->isoc_anchor);
 
        clear_bit(BTUSB_BULK_RUNNING, &data->flags);
        usb_kill_anchored_urbs(&data->bulk_anchor);
@@ -672,8 +681,19 @@ static void btusb_notify(struct hci_dev *hdev, unsigned int evt)
 
        BT_DBG("%s evt %d", hdev->name, evt);
 
-       if (evt == HCI_NOTIFY_CONN_ADD || evt == HCI_NOTIFY_CONN_DEL)
-               schedule_work(&data->work);
+       if (hdev->conn_hash.acl_num > 0) {
+               if (!test_and_set_bit(BTUSB_BULK_RUNNING, &data->flags)) {
+                       if (btusb_submit_bulk_urb(hdev, GFP_ATOMIC) < 0)
+                               clear_bit(BTUSB_BULK_RUNNING, &data->flags);
+                       else
+                               btusb_submit_bulk_urb(hdev, GFP_ATOMIC);
+               }
+       } else {
+               clear_bit(BTUSB_BULK_RUNNING, &data->flags);
+               usb_unlink_anchored_urbs(&data->bulk_anchor);
+       }
+
+       schedule_work(&data->work);
 }
 
 static int inline __set_isoc_interface(struct hci_dev *hdev, int altsetting)
@@ -724,18 +744,6 @@ static void btusb_work(struct work_struct *work)
        struct btusb_data *data = container_of(work, struct btusb_data, work);
        struct hci_dev *hdev = data->hdev;
 
-       if (hdev->conn_hash.acl_num > 0) {
-               if (!test_and_set_bit(BTUSB_BULK_RUNNING, &data->flags)) {
-                       if (btusb_submit_bulk_urb(hdev) < 0)
-                               clear_bit(BTUSB_BULK_RUNNING, &data->flags);
-                       else
-                               btusb_submit_bulk_urb(hdev);
-               }
-       } else {
-               clear_bit(BTUSB_BULK_RUNNING, &data->flags);
-               usb_kill_anchored_urbs(&data->bulk_anchor);
-       }
-
        if (hdev->conn_hash.sco_num > 0) {
                if (data->isoc_altsetting != 2) {
                        clear_bit(BTUSB_ISOC_RUNNING, &data->flags);
@@ -746,10 +754,10 @@ static void btusb_work(struct work_struct *work)
                }
 
                if (!test_and_set_bit(BTUSB_ISOC_RUNNING, &data->flags)) {
-                       if (btusb_submit_isoc_urb(hdev) < 0)
+                       if (btusb_submit_isoc_urb(hdev, GFP_KERNEL) < 0)
                                clear_bit(BTUSB_ISOC_RUNNING, &data->flags);
                        else
-                               btusb_submit_isoc_urb(hdev);
+                               btusb_submit_isoc_urb(hdev, GFP_KERNEL);
                }
        } else {
                clear_bit(BTUSB_ISOC_RUNNING, &data->flags);
@@ -821,6 +829,7 @@ static int btusb_probe(struct usb_interface *intf,
        }
 
        data->udev = interface_to_usbdev(intf);
+       data->intf = intf;
 
        spin_lock_init(&data->lock);
 
@@ -889,7 +898,7 @@ static int btusb_probe(struct usb_interface *intf,
 
        if (data->isoc) {
                err = usb_driver_claim_interface(&btusb_driver,
-                                                       data->isoc, NULL);
+                                                       data->isoc, data);
                if (err < 0) {
                        hci_free_dev(hdev);
                        kfree(data);
@@ -921,20 +930,90 @@ static void btusb_disconnect(struct usb_interface *intf)
 
        hdev = data->hdev;
 
-       if (data->isoc)
-               usb_driver_release_interface(&btusb_driver, data->isoc);
+       __hci_dev_hold(hdev);
 
-       usb_set_intfdata(intf, NULL);
+       usb_set_intfdata(data->intf, NULL);
+
+       if (data->isoc)
+               usb_set_intfdata(data->isoc, NULL);
 
        hci_unregister_dev(hdev);
 
+       if (intf == data->isoc)
+               usb_driver_release_interface(&btusb_driver, data->intf);
+       else if (data->isoc)
+               usb_driver_release_interface(&btusb_driver, data->isoc);
+
+       __hci_dev_put(hdev);
+
        hci_free_dev(hdev);
 }
 
+static int btusb_suspend(struct usb_interface *intf, pm_message_t message)
+{
+       struct btusb_data *data = usb_get_intfdata(intf);
+
+       BT_DBG("intf %p", intf);
+
+       if (data->suspend_count++)
+               return 0;
+
+       cancel_work_sync(&data->work);
+
+       usb_kill_anchored_urbs(&data->tx_anchor);
+
+       usb_kill_anchored_urbs(&data->isoc_anchor);
+       usb_kill_anchored_urbs(&data->bulk_anchor);
+       usb_kill_anchored_urbs(&data->intr_anchor);
+
+       return 0;
+}
+
+static int btusb_resume(struct usb_interface *intf)
+{
+       struct btusb_data *data = usb_get_intfdata(intf);
+       struct hci_dev *hdev = data->hdev;
+       int err;
+
+       BT_DBG("intf %p", intf);
+
+       if (--data->suspend_count)
+               return 0;
+
+       if (!test_bit(HCI_RUNNING, &hdev->flags))
+               return 0;
+
+       if (test_bit(BTUSB_INTR_RUNNING, &data->flags)) {
+               err = btusb_submit_intr_urb(hdev, GFP_NOIO);
+               if (err < 0) {
+                       clear_bit(BTUSB_INTR_RUNNING, &data->flags);
+                       return err;
+               }
+       }
+
+       if (test_bit(BTUSB_BULK_RUNNING, &data->flags)) {
+               if (btusb_submit_bulk_urb(hdev, GFP_NOIO) < 0)
+                       clear_bit(BTUSB_BULK_RUNNING, &data->flags);
+               else
+                       btusb_submit_bulk_urb(hdev, GFP_NOIO);
+       }
+
+       if (test_bit(BTUSB_ISOC_RUNNING, &data->flags)) {
+               if (btusb_submit_isoc_urb(hdev, GFP_NOIO) < 0)
+                       clear_bit(BTUSB_ISOC_RUNNING, &data->flags);
+               else
+                       btusb_submit_isoc_urb(hdev, GFP_NOIO);
+       }
+
+       return 0;
+}
+
 static struct usb_driver btusb_driver = {
        .name           = "btusb",
        .probe          = btusb_probe,
        .disconnect     = btusb_disconnect,
+       .suspend        = btusb_suspend,
+       .resume         = btusb_resume,
        .id_table       = btusb_table,
 };