diff --git a/drivers/bluetooth/hci_usb.c b/drivers/bluetooth/hci_usb.c index 406af57..3742ab7 100644 --- a/drivers/bluetooth/hci_usb.c +++ b/drivers/bluetooth/hci_usb.c @@ -56,6 +56,8 @@ #undef BT_DBG #define BT_DBG(D...) #endif +/* The Workque Function */ +static void set_alternate_config(struct work_struct *work); #ifndef CONFIG_BT_HCIUSB_ZERO_PACKET #undef URB_ZERO_PACKET @@ -840,6 +842,7 @@ static void hci_usb_destruct(struct hci_dev *hdev) static void hci_usb_notify(struct hci_dev *hdev, unsigned int evt) { BT_DBG("%s evt %d", hdev->name, evt); + schedule_work(&hdev->setting_work); } static int hci_usb_probe(struct usb_interface *intf, const struct usb_device_id *id) @@ -1007,6 +1010,7 @@ static int hci_usb_probe(struct usb_interface *intf, const struct usb_device_id hdev->owner = THIS_MODULE; + INIT_WORK(&hdev->setting_work,set_alternate_config); if (reset || id->driver_info & HCI_RESET) set_bit(HCI_QUIRK_RESET_ON_INIT, &hdev->quirks); @@ -1142,6 +1146,128 @@ static int hci_usb_resume(struct usb_interface *intf) return 0; } + +/*Set the alternate setting based on the number of SCO channels and the + voice setting.This function is invoked when there is a change in Number of + SCO channels or when the voice encoding changes.*/ +static void set_alternate_config(struct work_struct *work) +{ + + struct hci_dev *hdev = container_of(work, struct hci_dev, setting_work); + struct hci_usb *hUSB = (struct hci_usb *) hdev->driver_data; + +/* if bit=0,its 8bit encoding else its 16bit */ +__u16 bit = 0x20; +struct usb_interface *isocIface; + int isocIfnum=1, isocAlt=0; + struct usb_host_endpoint *ep; + struct usb_host_interface *uif; + struct _urb *_urb,*_tmp; + struct _urb_queue *q = &hUSB->pending_q[isoc]; + unsigned long flags; + atomic_t temp;/*Holds the number of URBs we need to skip(which are submitted)*/ + struct list_head inprocess;/*This list holds the already submitted URBs */ + + /*Change the alternate setting only if the number of SCO channels are more than 1 */ + if(hdev->conn_hash.sco_num > 0){ + /* The alternate setting selection is based on the following table */ + /* No. of SCO channels Bit-Encoding Alternate Setting Max. Packet Size */ + /* 1 8bit 1 9 */ + /* 1 16bit 2 17 */ + /* 2 8bit 2 17 */ + /* 2 16bit 4 33 */ + /* 3 8bit 3 25 */ + /* 3 16bit 5 49 */ + switch(hdev->conn_hash.sco_num) + { + case 1: + if(hdev->voice_setting && bit) + isocAlt=2; + else + isocAlt=1; + break; + case 2: + if(hdev->voice_setting && bit) + isocAlt=4; + else + isocAlt=2; + break; + case 3: + if(hdev->voice_setting && bit) + isocAlt=5; + else + isocAlt=3; + break; + } + + /*Stop Current TX */ + clear_bit(HCI_USB_TX_WAKEUP, &hUSB->state); + INIT_LIST_HEAD(&inprocess); + temp = hUSB->pending_tx[isoc]; + /* We cannot purge URBs which have been submitted. inprocess is a */ + /* temporary list which holds the currently submitted URBs. */ + /* This list is later merged with the emptyed pending queue. */ + + while ((_urb = _urb_dequeue(q))) { + /*Dequeue all the submitted URBs and put them in the temporary list*/ + if(!atomic_dec_and_test(&temp)){ + _urb->queue = q; + list_add(&_urb->list, &inprocess); + } + else{ + /*Unlink all the rest of URBs and put them into the completed queue.*/ + _urb_unlink(_urb); + _urb_queue_tail(__completed_q(hUSB,HCI_SCODATA_PKT), _urb); + } + } + /*merge the inprocess queue with the pending queue*/ + spin_lock_irqsave(&q->lock, flags); + list_for_each_entry_safe(_urb, _tmp, &inprocess, list) { + list_move_tail(&_urb->list, &q->head); + } + spin_unlock_irqrestore(&q->lock, flags); + isocIface = usb_ifnum_to_if(hUSB->udev, isocIfnum); + /* Set the setting and the in/out endpoints */ + if (isocIface) { + int e; + struct usb_host_endpoint *out = NULL; + struct usb_host_endpoint *in = NULL; + uif = &isocIface->altsetting[isocAlt]; + for (e = 0; e < uif->desc.bNumEndpoints; e++) { + ep = &uif->endpoint[e]; + switch (ep->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) { + case USB_ENDPOINT_XFER_ISOC: + if (ep->desc.bEndpointAddress & USB_DIR_IN) + in = ep; + else + out = ep; + break; + } + } + + if (!in || !out) + BT_DBG("Isoc endpoints not found"); + else { + BT_DBG("isoc ifnum %d alts %d", isocIfnum, isocAlt); + if (usb_set_interface(hUSB->udev,isocIfnum, isocAlt)) { + BT_ERR("Can't set isoc interface settings"); + hUSB->isoc_iface = isocIface; + usb_driver_release_interface(&hci_usb_driver, hUSB->isoc_iface); + hUSB->isoc_iface = NULL; + } else { + hUSB->isoc_iface = isocIface; + hUSB->isoc_in_ep = in; + hUSB->isoc_out_ep = out; + } + } + } + set_bit(HCI_USB_TX_WAKEUP, &hUSB->state); + } + +} + + + static struct usb_driver hci_usb_driver = { .name = "hci_usb", .probe = hci_usb_probe, diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index 8a57b7d..f060288 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -110,6 +110,7 @@ struct hci_dev { struct sk_buff_head raw_q; struct sk_buff_head cmd_q; + struct work_struct setting_work; struct sk_buff *sent_cmd; struct semaphore req_lock;