diff options
author | Marcel Ziswiler <marcel.ziswiler@toradex.com> | 2012-11-12 15:28:39 +0100 |
---|---|---|
committer | Marcel Ziswiler <marcel.ziswiler@toradex.com> | 2012-11-12 15:28:39 +0100 |
commit | f987e832a9e79d2ce8009a5ea9c7b677624b3b30 (patch) | |
tree | 0dd09a5e6b4c60ee0a9916907dfc2cda83f3e496 /drivers/usb | |
parent | f737b7f46a72c099cf8ac88baff02fbf61b1a47c (diff) | |
parent | fc993d9bc48f772133d8cd156c67c296477db070 (diff) |
Merge branch 'l4t/l4t-r16-r2' into colibri
Conflicts:
arch/arm/mach-tegra/tegra3_usb_phy.c
arch/arm/mach-tegra/usb_phy.c
drivers/usb/gadget/tegra_udc.c
drivers/usb/otg/Makefile
drivers/video/tegra/fb.c
sound/soc/tegra/tegra_pcm.c
Diffstat (limited to 'drivers/usb')
-rw-r--r-- | drivers/usb/class/cdc-acm.c | 2 | ||||
-rw-r--r-- | drivers/usb/gadget/Kconfig | 2 | ||||
-rw-r--r-- | drivers/usb/gadget/Makefile | 1 | ||||
-rw-r--r-- | drivers/usb/gadget/android.c | 68 | ||||
-rw-r--r-- | drivers/usb/gadget/f_accessory.c | 404 | ||||
-rw-r--r-- | drivers/usb/gadget/f_audio_source.c | 827 | ||||
-rw-r--r-- | drivers/usb/gadget/tegra_udc.c | 178 | ||||
-rw-r--r-- | drivers/usb/gadget/tegra_udc.h | 21 | ||||
-rw-r--r-- | drivers/usb/host/ehci-q.c | 2 | ||||
-rw-r--r-- | drivers/usb/host/ehci-tegra.c | 18 | ||||
-rw-r--r-- | drivers/usb/host/ehci.h | 22 | ||||
-rw-r--r-- | drivers/usb/otg/Makefile | 1 | ||||
-rw-r--r-- | drivers/usb/otg/tegra-otg.c | 60 | ||||
-rw-r--r-- | drivers/usb/serial/baseband_usb_chr.c | 98 |
14 files changed, 1585 insertions, 119 deletions
diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index 8fb105e03658..cd16e13f61c9 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -416,7 +416,7 @@ static void acm_read_bulk_callback(struct urb *urb) } usb_mark_last_busy(acm->dev); - if (urb->status) { + if (urb->status && !urb->actual_length) { dev_dbg(&acm->data->dev, "%s - non-zero urb status: %d\n", __func__, urb->status); return; diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 8e051d72d6aa..e7c6ca49ab8e 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -143,7 +143,7 @@ config USB_FSL_USB2 tristate "Freescale Highspeed USB DR Peripheral Controller" depends on FSL_SOC || ARCH_MXC || ARCH_TEGRA select USB_GADGET_DUALSPEED - select USB_FSL_MPH_DR_OF if OF + select USB_FSL_MPH_DR_OF if OF && FSL_SOC help Some of Freescale PowerPC processors have a High Speed Dual-Role(DR) USB controller, which supports device mode. diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index c8c7c687010d..13f24548cee1 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -21,6 +21,7 @@ obj-$(CONFIG_USB_ATMEL_USBA) += atmel_usba_udc.o obj-$(CONFIG_USB_FSL_USB2) += fsl_usb2_udc.o fsl_usb2_udc-y := fsl_udc_core.o fsl_usb2_udc-$(CONFIG_ARCH_MXC) += fsl_mxc_udc.o +CFLAGS_tegra_udc.o = -Werror obj-$(CONFIG_USB_TEGRA) += tegra_udc.o obj-$(CONFIG_USB_M66592) += m66592-udc.o obj-$(CONFIG_USB_R8A66597) += r8a66597-udc.o diff --git a/drivers/usb/gadget/android.c b/drivers/usb/gadget/android.c index dbc22802b43c..9f79c267890e 100644 --- a/drivers/usb/gadget/android.c +++ b/drivers/usb/gadget/android.c @@ -45,6 +45,7 @@ #include "epautoconf.c" #include "composite.c" +#include "f_audio_source.c" #include "f_mass_storage.c" #include "u_serial.c" #include "f_acm.c" @@ -649,6 +650,67 @@ static struct android_usb_function accessory_function = { .ctrlrequest = accessory_function_ctrlrequest, }; +static int audio_source_function_init(struct android_usb_function *f, + struct usb_composite_dev *cdev) +{ + struct audio_source_config *config; + + config = kzalloc(sizeof(struct audio_source_config), GFP_KERNEL); + if (!config) + return -ENOMEM; + config->card = -1; + config->device = -1; + f->config = config; + return 0; +} + +static void audio_source_function_cleanup(struct android_usb_function *f) +{ + kfree(f->config); +} + +static int audio_source_function_bind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + struct audio_source_config *config = f->config; + + return audio_source_bind_config(c, config); +} + +static void audio_source_function_unbind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + struct audio_source_config *config = f->config; + + config->card = -1; + config->device = -1; +} + +static ssize_t audio_source_pcm_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct audio_source_config *config = f->config; + + /* print PCM card and device numbers */ + return sprintf(buf, "%d %d\n", config->card, config->device); +} + +static DEVICE_ATTR(pcm, S_IRUGO | S_IWUSR, audio_source_pcm_show, NULL); + +static struct device_attribute *audio_source_function_attributes[] = { + &dev_attr_pcm, + NULL +}; + +static struct android_usb_function audio_source_function = { + .name = "audio_source", + .init = audio_source_function_init, + .cleanup = audio_source_function_cleanup, + .bind_config = audio_source_function_bind_config, + .unbind_config = audio_source_function_unbind_config, + .attributes = audio_source_function_attributes, +}; static struct android_usb_function *supported_functions[] = { &adb_function, @@ -658,6 +720,7 @@ static struct android_usb_function *supported_functions[] = { &rndis_function, &mass_storage_function, &accessory_function, + &audio_source_function, NULL }; @@ -1113,6 +1176,11 @@ static void android_disconnect(struct usb_gadget *gadget) unsigned long flags; composite_disconnect(gadget); + /* accessory HID support can be active while the + accessory function is not actually enabled, + so we need to inform it when we are disconnected. + */ + acc_disconnect(); spin_lock_irqsave(&cdev->lock, flags); dev->connected = 0; diff --git a/drivers/usb/gadget/f_accessory.c b/drivers/usb/gadget/f_accessory.c index ae65faaf3d77..d248ef98b532 100644 --- a/drivers/usb/gadget/f_accessory.c +++ b/drivers/usb/gadget/f_accessory.c @@ -33,6 +33,8 @@ #include <linux/device.h> #include <linux/miscdevice.h> +#include <linux/hid.h> +#include <linux/hiddev.h> #include <linux/usb.h> #include <linux/usb/ch9.h> #include <linux/usb/f_accessory.h> @@ -40,7 +42,7 @@ #define BULK_BUFFER_SIZE 16384 #define ACC_STRING_SIZE 256 -#define PROTOCOL_VERSION 1 +#define PROTOCOL_VERSION 2 /* String IDs */ #define INTERFACE_STRING_INDEX 0 @@ -49,6 +51,20 @@ #define TX_REQ_MAX 4 #define RX_REQ_MAX 2 +struct acc_hid_dev { + struct list_head list; + struct hid_device *hid; + struct acc_dev *dev; + /* accessory defined ID */ + int id; + /* HID report descriptor */ + u8 *report_desc; + /* length of HID report descriptor */ + int report_desc_len; + /* number of bytes of report_desc we have received so far */ + int report_desc_offset; +}; + struct acc_dev { struct usb_function function; struct usb_composite_dev *cdev; @@ -78,6 +94,8 @@ struct acc_dev { /* set to 1 if we have a pending start request */ int start_requested; + int audio_mode; + /* synchronize access to our device file */ atomic_t open_excl; @@ -87,7 +105,21 @@ struct acc_dev { wait_queue_head_t write_wq; struct usb_request *rx_req[RX_REQ_MAX]; int rx_done; - struct delayed_work work; + + /* delayed work for handling ACCESSORY_START */ + struct delayed_work start_work; + + /* worker for registering and unregistering hid devices */ + struct work_struct hid_work; + + /* list of active HID devices */ + struct list_head hid_list; + + /* list of new HID devices to register */ + struct list_head new_hid_list; + + /* list of dead HID devices to unregister */ + struct list_head dead_hid_list; }; static struct usb_interface_descriptor acc_interface_desc = { @@ -296,7 +328,161 @@ static void acc_complete_set_string(struct usb_ep *ep, struct usb_request *req) } } -static int __init create_bulk_endpoints(struct acc_dev *dev, +static void acc_complete_set_hid_report_desc(struct usb_ep *ep, + struct usb_request *req) +{ + struct acc_hid_dev *hid = req->context; + struct acc_dev *dev = hid->dev; + int length = req->actual; + + if (req->status != 0) { + pr_err("acc_complete_set_hid_report_desc, err %d\n", + req->status); + return; + } + + memcpy(hid->report_desc + hid->report_desc_offset, req->buf, length); + hid->report_desc_offset += length; + if (hid->report_desc_offset == hid->report_desc_len) { + /* After we have received the entire report descriptor + * we schedule work to initialize the HID device + */ + schedule_work(&dev->hid_work); + } +} + +static void acc_complete_send_hid_event(struct usb_ep *ep, + struct usb_request *req) +{ + struct acc_hid_dev *hid = req->context; + int length = req->actual; + + if (req->status != 0) { + pr_err("acc_complete_send_hid_event, err %d\n", req->status); + return; + } + + hid_report_raw_event(hid->hid, HID_INPUT_REPORT, req->buf, length, 1); +} + +static int acc_hid_parse(struct hid_device *hid) +{ + struct acc_hid_dev *hdev = hid->driver_data; + + hid_parse_report(hid, hdev->report_desc, hdev->report_desc_len); + return 0; +} + +static int acc_hid_start(struct hid_device *hid) +{ + return 0; +} + +static void acc_hid_stop(struct hid_device *hid) +{ +} + +static int acc_hid_open(struct hid_device *hid) +{ + return 0; +} + +static void acc_hid_close(struct hid_device *hid) +{ +} + +static struct hid_ll_driver acc_hid_ll_driver = { + .parse = acc_hid_parse, + .start = acc_hid_start, + .stop = acc_hid_stop, + .open = acc_hid_open, + .close = acc_hid_close, +}; + +static struct acc_hid_dev *acc_hid_new(struct acc_dev *dev, + int id, int desc_len) +{ + struct acc_hid_dev *hdev; + + hdev = kzalloc(sizeof(*hdev), GFP_ATOMIC); + if (!hdev) + return NULL; + hdev->report_desc = kzalloc(desc_len, GFP_ATOMIC); + if (!hdev->report_desc) { + kfree(hdev); + return NULL; + } + hdev->dev = dev; + hdev->id = id; + hdev->report_desc_len = desc_len; + + return hdev; +} + +static struct acc_hid_dev *acc_hid_get(struct list_head *list, int id) +{ + struct acc_hid_dev *hid; + + list_for_each_entry(hid, list, list) { + if (hid->id == id) + return hid; + } + return NULL; +} + +static int acc_register_hid(struct acc_dev *dev, int id, int desc_length) +{ + struct acc_hid_dev *hid; + unsigned long flags; + + /* report descriptor length must be > 0 */ + if (desc_length <= 0) + return -EINVAL; + + spin_lock_irqsave(&dev->lock, flags); + /* replace HID if one already exists with this ID */ + hid = acc_hid_get(&dev->hid_list, id); + if (!hid) + hid = acc_hid_get(&dev->new_hid_list, id); + if (hid) + list_move(&hid->list, &dev->dead_hid_list); + + hid = acc_hid_new(dev, id, desc_length); + if (!hid) { + spin_unlock_irqrestore(&dev->lock, flags); + return -ENOMEM; + } + + list_add(&hid->list, &dev->new_hid_list); + spin_unlock_irqrestore(&dev->lock, flags); + + /* schedule work to register the HID device */ + schedule_work(&dev->hid_work); + return 0; +} + +static int acc_unregister_hid(struct acc_dev *dev, int id) +{ + struct acc_hid_dev *hid; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + hid = acc_hid_get(&dev->hid_list, id); + if (!hid) + hid = acc_hid_get(&dev->new_hid_list, id); + if (!hid) { + spin_unlock_irqrestore(&dev->lock, flags); + return -EINVAL; + } + + list_move(&hid->list, &dev->dead_hid_list); + spin_unlock_irqrestore(&dev->lock, flags); + + schedule_work(&dev->hid_work); + return 0; +} + +static int create_bulk_endpoints(struct acc_dev *dev, struct usb_endpoint_descriptor *in_desc, struct usb_endpoint_descriptor *out_desc) { @@ -353,7 +539,7 @@ static int __init create_bulk_endpoints(struct acc_dev *dev, return 0; fail: - printk(KERN_ERR "acc_bind() could not allocate requests\n"); + pr_err("acc_bind() could not allocate requests\n"); while ((req = req_get(dev, &dev->tx_idle))) acc_request_free(req, dev->ep_in); for (i = 0; i < RX_REQ_MAX; i++) @@ -510,6 +696,8 @@ static long acc_ioctl(struct file *fp, unsigned code, unsigned long value) break; case ACCESSORY_IS_START_REQUESTED: return dev->start_requested; + case ACCESSORY_GET_AUDIO_MODE: + return dev->audio_mode; } if (!src) return -EINVAL; @@ -540,7 +728,7 @@ static int acc_release(struct inode *ip, struct file *fp) return 0; } -/* file operations for /dev/acc_usb */ +/* file operations for /dev/usb_accessory */ static const struct file_operations acc_fops = { .owner = THIS_MODULE, .read = acc_read, @@ -550,23 +738,47 @@ static const struct file_operations acc_fops = { .release = acc_release, }; +static int acc_hid_probe(struct hid_device *hdev, + const struct hid_device_id *id) +{ + int ret; + + ret = hid_parse(hdev); + if (ret) + return ret; + return hid_hw_start(hdev, HID_CONNECT_DEFAULT); +} + static struct miscdevice acc_device = { .minor = MISC_DYNAMIC_MINOR, .name = "usb_accessory", .fops = &acc_fops, }; +static const struct hid_device_id acc_hid_table[] = { + { HID_USB_DEVICE(HID_ANY_ID, HID_ANY_ID) }, + { } +}; + +static struct hid_driver acc_hid_driver = { + .name = "USB accessory", + .id_table = acc_hid_table, + .probe = acc_hid_probe, +}; static int acc_ctrlrequest(struct usb_composite_dev *cdev, const struct usb_ctrlrequest *ctrl) { struct acc_dev *dev = _acc_dev; int value = -EOPNOTSUPP; + struct acc_hid_dev *hid; + int offset; u8 b_requestType = ctrl->bRequestType; u8 b_request = ctrl->bRequest; u16 w_index = le16_to_cpu(ctrl->wIndex); u16 w_value = le16_to_cpu(ctrl->wValue); u16 w_length = le16_to_cpu(ctrl->wLength); + unsigned long flags; /* printk(KERN_INFO "acc_ctrlrequest " @@ -579,20 +791,56 @@ static int acc_ctrlrequest(struct usb_composite_dev *cdev, if (b_request == ACCESSORY_START) { dev->start_requested = 1; schedule_delayed_work( - &dev->work, msecs_to_jiffies(10)); + &dev->start_work, msecs_to_jiffies(10)); value = 0; } else if (b_request == ACCESSORY_SEND_STRING) { dev->string_index = w_index; cdev->gadget->ep0->driver_data = dev; cdev->req->complete = acc_complete_set_string; value = w_length; + } else if (b_request == ACCESSORY_SET_AUDIO_MODE && + w_index == 0 && w_length == 0) { + dev->audio_mode = w_value; + value = 0; + } else if (b_request == ACCESSORY_REGISTER_HID) { + value = acc_register_hid(dev, w_value, w_index); + } else if (b_request == ACCESSORY_UNREGISTER_HID) { + value = acc_unregister_hid(dev, w_value); + } else if (b_request == ACCESSORY_SET_HID_REPORT_DESC) { + spin_lock_irqsave(&dev->lock, flags); + hid = acc_hid_get(&dev->new_hid_list, w_value); + spin_unlock_irqrestore(&dev->lock, flags); + if (!hid) { + value = -EINVAL; + goto err; + } + offset = w_index; + if (offset != hid->report_desc_offset + || offset + w_length > hid->report_desc_len) { + value = -EINVAL; + goto err; + } + cdev->req->context = hid; + cdev->req->complete = acc_complete_set_hid_report_desc; + value = w_length; + } else if (b_request == ACCESSORY_SEND_HID_EVENT) { + spin_lock_irqsave(&dev->lock, flags); + hid = acc_hid_get(&dev->hid_list, w_value); + spin_unlock_irqrestore(&dev->lock, flags); + if (!hid) { + value = -EINVAL; + goto err; + } + cdev->req->context = hid; + cdev->req->complete = acc_complete_send_hid_event; + value = w_length; } } else if (b_requestType == (USB_DIR_IN | USB_TYPE_VENDOR)) { if (b_request == ACCESSORY_GET_PROTOCOL) { *((u16 *)cdev->req->buf) = PROTOCOL_VERSION; value = sizeof(u16); - /* clear any strings left over from a previous session */ + /* clear strings left over from a previous session */ memset(dev->manufacturer, 0, sizeof(dev->manufacturer)); memset(dev->model, 0, sizeof(dev->model)); memset(dev->description, 0, sizeof(dev->description)); @@ -600,6 +848,7 @@ static int acc_ctrlrequest(struct usb_composite_dev *cdev, memset(dev->uri, 0, sizeof(dev->uri)); memset(dev->serial, 0, sizeof(dev->serial)); dev->start_requested = 0; + dev->audio_mode = 0; } } @@ -612,6 +861,7 @@ static int acc_ctrlrequest(struct usb_composite_dev *cdev, __func__); } +err: if (value == -EOPNOTSUPP) VDBG(cdev, "unknown class-specific control req " @@ -631,6 +881,10 @@ acc_function_bind(struct usb_configuration *c, struct usb_function *f) DBG(cdev, "acc_function_bind dev: %p\n", dev); + ret = hid_register_driver(&acc_hid_driver); + if (ret) + return ret; + dev->start_requested = 0; /* allocate interface ID(s) */ @@ -660,6 +914,36 @@ acc_function_bind(struct usb_configuration *c, struct usb_function *f) } static void +kill_all_hid_devices(struct acc_dev *dev) +{ + struct acc_hid_dev *hid; + struct list_head *entry, *temp; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + list_for_each_safe(entry, temp, &dev->hid_list) { + hid = list_entry(entry, struct acc_hid_dev, list); + list_del(&hid->list); + list_add(&hid->list, &dev->dead_hid_list); + } + list_for_each_safe(entry, temp, &dev->new_hid_list) { + hid = list_entry(entry, struct acc_hid_dev, list); + list_del(&hid->list); + list_add(&hid->list, &dev->dead_hid_list); + } + spin_unlock_irqrestore(&dev->lock, flags); + + schedule_work(&dev->hid_work); +} + +static void +acc_hid_unbind(struct acc_dev *dev) +{ + hid_unregister_driver(&acc_hid_driver); + kill_all_hid_devices(dev); +} + +static void acc_function_unbind(struct usb_configuration *c, struct usb_function *f) { struct acc_dev *dev = func_to_dev(f); @@ -670,14 +954,104 @@ acc_function_unbind(struct usb_configuration *c, struct usb_function *f) acc_request_free(req, dev->ep_in); for (i = 0; i < RX_REQ_MAX; i++) acc_request_free(dev->rx_req[i], dev->ep_out); + + acc_hid_unbind(dev); } -static void acc_work(struct work_struct *data) +static void acc_start_work(struct work_struct *data) { char *envp[2] = { "ACCESSORY=START", NULL }; kobject_uevent_env(&acc_device.this_device->kobj, KOBJ_CHANGE, envp); } +static int acc_hid_init(struct acc_hid_dev *hdev) +{ + struct hid_device *hid; + int ret; + + hid = hid_allocate_device(); + if (IS_ERR(hid)) + return PTR_ERR(hid); + + hid->ll_driver = &acc_hid_ll_driver; + hid->dev.parent = acc_device.this_device; + + hid->bus = BUS_USB; + hid->vendor = HID_ANY_ID; + hid->product = HID_ANY_ID; + hid->driver_data = hdev; + ret = hid_add_device(hid); + if (ret) { + pr_err("can't add hid device: %d\n", ret); + hid_destroy_device(hid); + return ret; + } + + hdev->hid = hid; + return 0; +} + +static void acc_hid_delete(struct acc_hid_dev *hid) +{ + kfree(hid->report_desc); + kfree(hid); +} + +static void acc_hid_work(struct work_struct *data) +{ + struct acc_dev *dev = _acc_dev; + struct list_head *entry, *temp; + struct acc_hid_dev *hid; + struct list_head new_list, dead_list; + unsigned long flags; + + INIT_LIST_HEAD(&new_list); + + spin_lock_irqsave(&dev->lock, flags); + + /* copy hids that are ready for initialization to new_list */ + list_for_each_safe(entry, temp, &dev->new_hid_list) { + hid = list_entry(entry, struct acc_hid_dev, list); + if (hid->report_desc_offset == hid->report_desc_len) + list_move(&hid->list, &new_list); + } + + if (list_empty(&dev->dead_hid_list)) { + INIT_LIST_HEAD(&dead_list); + } else { + /* move all of dev->dead_hid_list to dead_list */ + dead_list.prev = dev->dead_hid_list.prev; + dead_list.next = dev->dead_hid_list.next; + dead_list.next->prev = &dead_list; + dead_list.prev->next = &dead_list; + INIT_LIST_HEAD(&dev->dead_hid_list); + } + + spin_unlock_irqrestore(&dev->lock, flags); + + /* register new HID devices */ + list_for_each_safe(entry, temp, &new_list) { + hid = list_entry(entry, struct acc_hid_dev, list); + if (acc_hid_init(hid)) { + pr_err("can't add HID device %p\n", hid); + acc_hid_delete(hid); + } else { + spin_lock_irqsave(&dev->lock, flags); + list_move(&hid->list, &dev->hid_list); + spin_unlock_irqrestore(&dev->lock, flags); + } + } + + /* remove dead HID devices */ + list_for_each_safe(entry, temp, &dead_list) { + hid = list_entry(entry, struct acc_hid_dev, list); + list_del(&hid->list); + if (hid->hid) + hid_destroy_device(hid->hid); + acc_hid_delete(hid); + } +} + static int acc_function_set_alt(struct usb_function *f, unsigned intf, unsigned alt) { @@ -763,7 +1137,11 @@ static int acc_setup(void) init_waitqueue_head(&dev->write_wq); atomic_set(&dev->open_excl, 0); INIT_LIST_HEAD(&dev->tx_idle); - INIT_DELAYED_WORK(&dev->work, acc_work); + INIT_LIST_HEAD(&dev->hid_list); + INIT_LIST_HEAD(&dev->new_hid_list); + INIT_LIST_HEAD(&dev->dead_hid_list); + INIT_DELAYED_WORK(&dev->start_work, acc_start_work); + INIT_WORK(&dev->hid_work, acc_hid_work); /* _acc_dev must be set before calling usb_gadget_register_driver */ _acc_dev = dev; @@ -776,10 +1154,16 @@ static int acc_setup(void) err: kfree(dev); - printk(KERN_ERR "USB accessory gadget driver failed to initialize\n"); + pr_err("USB accessory gadget driver failed to initialize\n"); return ret; } +static void acc_disconnect(void) +{ + /* unregister all HID devices if USB is disconnected */ + kill_all_hid_devices(_acc_dev); +} + static void acc_cleanup(void) { misc_deregister(&acc_device); diff --git a/drivers/usb/gadget/f_audio_source.c b/drivers/usb/gadget/f_audio_source.c new file mode 100644 index 000000000000..3ba7d7569b12 --- /dev/null +++ b/drivers/usb/gadget/f_audio_source.c @@ -0,0 +1,827 @@ +/* + * Gadget Function Driver for USB audio source device + * + * Copyright (C) 2012 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/device.h> +#include <linux/usb/audio.h> +#include <linux/wait.h> +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/pcm.h> + +#define SAMPLE_RATE 44100 +/* Each frame is two 16 bit integers (one per channel) */ +#define BYTES_PER_FRAME 4 +#define FRAMES_PER_MSEC (SAMPLE_RATE / 1000) + +#define IN_EP_MAX_PACKET_SIZE 256 + +/* Number of requests to allocate */ +#define IN_EP_REQ_COUNT 4 + +#define AUDIO_AC_INTERFACE 0 +#define AUDIO_AS_INTERFACE 1 +#define AUDIO_NUM_INTERFACES 2 + +/* B.3.1 Standard AC Interface Descriptor */ +static struct usb_interface_descriptor ac_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, +}; + +DECLARE_UAC_AC_HEADER_DESCRIPTOR(2); + +#define UAC_DT_AC_HEADER_LENGTH UAC_DT_AC_HEADER_SIZE(AUDIO_NUM_INTERFACES) +/* 1 input terminal, 1 output terminal and 1 feature unit */ +#define UAC_DT_TOTAL_LENGTH (UAC_DT_AC_HEADER_LENGTH \ + + UAC_DT_INPUT_TERMINAL_SIZE + UAC_DT_OUTPUT_TERMINAL_SIZE \ + + UAC_DT_FEATURE_UNIT_SIZE(0)) +/* B.3.2 Class-Specific AC Interface Descriptor */ +static struct uac1_ac_header_descriptor_2 ac_header_desc = { + .bLength = UAC_DT_AC_HEADER_LENGTH, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_HEADER, + .bcdADC = __constant_cpu_to_le16(0x0100), + .wTotalLength = __constant_cpu_to_le16(UAC_DT_TOTAL_LENGTH), + .bInCollection = AUDIO_NUM_INTERFACES, + .baInterfaceNr = { + [0] = AUDIO_AC_INTERFACE, + [1] = AUDIO_AS_INTERFACE, + } +}; + +#define INPUT_TERMINAL_ID 1 +static struct uac_input_terminal_descriptor input_terminal_desc = { + .bLength = UAC_DT_INPUT_TERMINAL_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_INPUT_TERMINAL, + .bTerminalID = INPUT_TERMINAL_ID, + .wTerminalType = UAC_INPUT_TERMINAL_MICROPHONE, + .bAssocTerminal = 0, + .wChannelConfig = 0x3, +}; + +DECLARE_UAC_FEATURE_UNIT_DESCRIPTOR(0); + +#define FEATURE_UNIT_ID 2 +static struct uac_feature_unit_descriptor_0 feature_unit_desc = { + .bLength = UAC_DT_FEATURE_UNIT_SIZE(0), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_FEATURE_UNIT, + .bUnitID = FEATURE_UNIT_ID, + .bSourceID = INPUT_TERMINAL_ID, + .bControlSize = 2, +}; + +#define OUTPUT_TERMINAL_ID 3 +static struct uac1_output_terminal_descriptor output_terminal_desc = { + .bLength = UAC_DT_OUTPUT_TERMINAL_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_OUTPUT_TERMINAL, + .bTerminalID = OUTPUT_TERMINAL_ID, + .wTerminalType = UAC_TERMINAL_STREAMING, + .bAssocTerminal = FEATURE_UNIT_ID, + .bSourceID = FEATURE_UNIT_ID, +}; + +/* B.4.1 Standard AS Interface Descriptor */ +static struct usb_interface_descriptor as_interface_alt_0_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING, +}; + +static struct usb_interface_descriptor as_interface_alt_1_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bAlternateSetting = 1, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING, +}; + +/* B.4.2 Class-Specific AS Interface Descriptor */ +static struct uac1_as_header_descriptor as_header_desc = { + .bLength = UAC_DT_AS_HEADER_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_AS_GENERAL, + .bTerminalLink = INPUT_TERMINAL_ID, + .bDelay = 1, + .wFormatTag = UAC_FORMAT_TYPE_I_PCM, +}; + +DECLARE_UAC_FORMAT_TYPE_I_DISCRETE_DESC(1); + +static struct uac_format_type_i_discrete_descriptor_1 as_type_i_desc = { + .bLength = UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(1), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_FORMAT_TYPE, + .bFormatType = UAC_FORMAT_TYPE_I, + .bSubframeSize = 2, + .bBitResolution = 16, + .bSamFreqType = 1, +}; + +/* Standard ISO IN Endpoint Descriptor for highspeed */ +static struct usb_endpoint_descriptor hs_as_in_ep_desc = { + .bLength = USB_DT_ENDPOINT_AUDIO_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_SYNC_SYNC + | USB_ENDPOINT_XFER_ISOC, + .wMaxPacketSize = __constant_cpu_to_le16(IN_EP_MAX_PACKET_SIZE), + .bInterval = 4, /* poll 1 per millisecond */ +}; + +/* Standard ISO IN Endpoint Descriptor for highspeed */ +static struct usb_endpoint_descriptor fs_as_in_ep_desc = { + .bLength = USB_DT_ENDPOINT_AUDIO_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_SYNC_SYNC + | USB_ENDPOINT_XFER_ISOC, + .wMaxPacketSize = __constant_cpu_to_le16(IN_EP_MAX_PACKET_SIZE), + .bInterval = 1, /* poll 1 per millisecond */ +}; + +/* Class-specific AS ISO OUT Endpoint Descriptor */ +static struct uac_iso_endpoint_descriptor as_iso_in_desc = { + .bLength = UAC_ISO_ENDPOINT_DESC_SIZE, + .bDescriptorType = USB_DT_CS_ENDPOINT, + .bDescriptorSubtype = UAC_EP_GENERAL, + .bmAttributes = 1, + .bLockDelayUnits = 1, + .wLockDelay = __constant_cpu_to_le16(1), +}; + +static struct usb_descriptor_header *hs_audio_desc[] = { + (struct usb_descriptor_header *)&ac_interface_desc, + (struct usb_descriptor_header *)&ac_header_desc, + + (struct usb_descriptor_header *)&input_terminal_desc, + (struct usb_descriptor_header *)&output_terminal_desc, + (struct usb_descriptor_header *)&feature_unit_desc, + + (struct usb_descriptor_header *)&as_interface_alt_0_desc, + (struct usb_descriptor_header *)&as_interface_alt_1_desc, + (struct usb_descriptor_header *)&as_header_desc, + + (struct usb_descriptor_header *)&as_type_i_desc, + + (struct usb_descriptor_header *)&hs_as_in_ep_desc, + (struct usb_descriptor_header *)&as_iso_in_desc, + NULL, +}; + +static struct usb_descriptor_header *fs_audio_desc[] = { + (struct usb_descriptor_header *)&ac_interface_desc, + (struct usb_descriptor_header *)&ac_header_desc, + + (struct usb_descriptor_header *)&input_terminal_desc, + (struct usb_descriptor_header *)&output_terminal_desc, + (struct usb_descriptor_header *)&feature_unit_desc, + + (struct usb_descriptor_header *)&as_interface_alt_0_desc, + (struct usb_descriptor_header *)&as_interface_alt_1_desc, + (struct usb_descriptor_header *)&as_header_desc, + + (struct usb_descriptor_header *)&as_type_i_desc, + + (struct usb_descriptor_header *)&fs_as_in_ep_desc, + (struct usb_descriptor_header *)&as_iso_in_desc, + NULL, +}; + +static struct snd_pcm_hardware audio_hw_info = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER, + + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 2, + .channels_max = 2, + .rate_min = SAMPLE_RATE, + .rate_max = SAMPLE_RATE, + + .buffer_bytes_max = 1024 * 1024, + .period_bytes_min = 64, + .period_bytes_max = 512 * 1024, + .periods_min = 2, + .periods_max = 1024, +}; + +/*-------------------------------------------------------------------------*/ + +struct audio_source_config { + int card; + int device; +}; + +struct audio_dev { + struct usb_function func; + struct snd_card *card; + struct snd_pcm *pcm; + struct snd_pcm_substream *substream; + + struct list_head idle_reqs; + struct usb_ep *in_ep; + struct usb_endpoint_descriptor *in_desc; + + spinlock_t lock; + + /* beginning, end and current position in our buffer */ + void *buffer_start; + void *buffer_end; + void *buffer_pos; + + /* byte size of a "period" */ + unsigned int period; + /* bytes sent since last call to snd_pcm_period_elapsed */ + unsigned int period_offset; + /* time we started playing */ + ktime_t start_time; + /* number of frames sent since start_time */ + s64 frames_sent; +}; + +static inline struct audio_dev *func_to_audio(struct usb_function *f) +{ + return container_of(f, struct audio_dev, func); +} + +/*-------------------------------------------------------------------------*/ + +static struct usb_request *audio_request_new(struct usb_ep *ep, int buffer_size) +{ + struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!req) + return NULL; + + req->buf = kmalloc(buffer_size, GFP_KERNEL); + if (!req->buf) { + usb_ep_free_request(ep, req); + return NULL; + } + req->length = buffer_size; + return req; +} + +static void audio_request_free(struct usb_request *req, struct usb_ep *ep) +{ + if (req) { + kfree(req->buf); + usb_ep_free_request(ep, req); + } +} + +static void audio_req_put(struct audio_dev *audio, struct usb_request *req) +{ + unsigned long flags; + + spin_lock_irqsave(&audio->lock, flags); + list_add_tail(&req->list, &audio->idle_reqs); + spin_unlock_irqrestore(&audio->lock, flags); +} + +static struct usb_request *audio_req_get(struct audio_dev *audio) +{ + unsigned long flags; + struct usb_request *req; + + spin_lock_irqsave(&audio->lock, flags); + if (list_empty(&audio->idle_reqs)) { + req = 0; + } else { + req = list_first_entry(&audio->idle_reqs, struct usb_request, + list); + list_del(&req->list); + } + spin_unlock_irqrestore(&audio->lock, flags); + return req; +} + +/* send the appropriate number of packets to match our bitrate */ +static void audio_send(struct audio_dev *audio) +{ + struct snd_pcm_runtime *runtime; + struct usb_request *req; + int length, length1, length2, ret; + s64 msecs; + s64 frames; + ktime_t now; + + /* audio->substream will be null if we have been closed */ + if (!audio->substream) + return; + /* audio->buffer_pos will be null if we have been stopped */ + if (!audio->buffer_pos) + return; + + runtime = audio->substream->runtime; + + /* compute number of frames to send */ + now = ktime_get(); + msecs = ktime_to_ns(now) - ktime_to_ns(audio->start_time); + do_div(msecs, 1000000); + frames = msecs * SAMPLE_RATE; + do_div(frames, 1000); + + /* Readjust our frames_sent if we fall too far behind. + * If we get too far behind it is better to drop some frames than + * to keep sending data too fast in an attempt to catch up. + */ + if (frames - audio->frames_sent > 10 * FRAMES_PER_MSEC) + audio->frames_sent = frames - FRAMES_PER_MSEC; + + frames -= audio->frames_sent; + + /* We need to send something to keep the pipeline going */ + if (frames <= 0) + frames = FRAMES_PER_MSEC; + + while (frames > 0) { + req = audio_req_get(audio); + if (!req) + break; + + length = frames_to_bytes(runtime, frames); + if (length > IN_EP_MAX_PACKET_SIZE) + length = IN_EP_MAX_PACKET_SIZE; + + if (audio->buffer_pos + length > audio->buffer_end) + length1 = audio->buffer_end - audio->buffer_pos; + else + length1 = length; + memcpy(req->buf, audio->buffer_pos, length1); + if (length1 < length) { + /* Wrap around and copy remaining length + * at beginning of buffer. + */ + length2 = length - length1; + memcpy(req->buf + length1, audio->buffer_start, + length2); + audio->buffer_pos = audio->buffer_start + length2; + } else { + audio->buffer_pos += length1; + if (audio->buffer_pos >= audio->buffer_end) + audio->buffer_pos = audio->buffer_start; + } + + req->length = length; + ret = usb_ep_queue(audio->in_ep, req, GFP_ATOMIC); + if (ret < 0) { + pr_err("usb_ep_queue failed ret: %d\n", ret); + audio_req_put(audio, req); + break; + } + + frames -= bytes_to_frames(runtime, length); + audio->frames_sent += bytes_to_frames(runtime, length); + } +} + +static void audio_control_complete(struct usb_ep *ep, struct usb_request *req) +{ + /* nothing to do here */ +} + +static void audio_data_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct audio_dev *audio = req->context; + + pr_debug("audio_data_complete req->status %d req->actual %d\n", + req->status, req->actual); + + audio_req_put(audio, req); + + if (!audio->buffer_start) + return; + + audio->period_offset += req->actual; + if (audio->period_offset >= audio->period) { + snd_pcm_period_elapsed(audio->substream); + audio->period_offset = 0; + } + audio_send(audio); +} + +static int audio_set_endpoint_req(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + int value = -EOPNOTSUPP; + u16 ep = le16_to_cpu(ctrl->wIndex); + u16 len = le16_to_cpu(ctrl->wLength); + u16 w_value = le16_to_cpu(ctrl->wValue); + + pr_debug("bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n", + ctrl->bRequest, w_value, len, ep); + + switch (ctrl->bRequest) { + case UAC_SET_CUR: + case UAC_SET_MIN: + case UAC_SET_MAX: + case UAC_SET_RES: + value = len; + break; + default: + break; + } + + return value; +} + +static int audio_get_endpoint_req(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + struct usb_composite_dev *cdev = f->config->cdev; + int value = -EOPNOTSUPP; + u8 ep = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF); + u16 len = le16_to_cpu(ctrl->wLength); + u16 w_value = le16_to_cpu(ctrl->wValue); + u8 *buf = cdev->req->buf; + + pr_debug("bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n", + ctrl->bRequest, w_value, len, ep); + + if (w_value == UAC_EP_CS_ATTR_SAMPLE_RATE << 8) { + switch (ctrl->bRequest) { + case UAC_GET_CUR: + case UAC_GET_MIN: + case UAC_GET_MAX: + case UAC_GET_RES: + /* return our sample rate */ + buf[0] = (u8)SAMPLE_RATE; + buf[1] = (u8)(SAMPLE_RATE >> 8); + buf[2] = (u8)(SAMPLE_RATE >> 16); + value = 3; + break; + default: + break; + } + } + + return value; +} + +static int +audio_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) +{ + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = cdev->req; + int value = -EOPNOTSUPP; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + + /* composite driver infrastructure handles everything; interface + * activation uses set_alt(). + */ + switch (ctrl->bRequestType) { + case USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT: + value = audio_set_endpoint_req(f, ctrl); + break; + + case USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT: + value = audio_get_endpoint_req(f, ctrl); + break; + } + + /* respond with data transfer or status phase? */ + if (value >= 0) { + pr_debug("audio req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + req->zero = 0; + req->length = value; + req->complete = audio_control_complete; + value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (value < 0) + pr_err("audio response on err %d\n", value); + } + + /* device either stalls (value < 0) or reports success */ + return value; +} + +static int audio_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct audio_dev *audio = func_to_audio(f); + struct usb_composite_dev *cdev = f->config->cdev; + + pr_debug("audio_set_alt intf %d, alt %d\n", intf, alt); + config_ep_by_speed(cdev->gadget, f, audio->in_ep); + usb_ep_enable(audio->in_ep); + return 0; +} + +static void audio_disable(struct usb_function *f) +{ + struct audio_dev *audio = func_to_audio(f); + + pr_debug("audio_disable\n"); + usb_ep_disable(audio->in_ep); +} + +/*-------------------------------------------------------------------------*/ + +static void audio_build_desc(struct audio_dev *audio) +{ + u8 *sam_freq; + int rate; + + /* Set channel numbers */ + input_terminal_desc.bNrChannels = 2; + as_type_i_desc.bNrChannels = 2; + + /* Set sample rates */ + rate = SAMPLE_RATE; + sam_freq = as_type_i_desc.tSamFreq[0]; + memcpy(sam_freq, &rate, 3); +} + +/* audio function driver setup/binding */ +static int +audio_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct audio_dev *audio = func_to_audio(f); + int status; + struct usb_ep *ep; + struct usb_request *req; + int i; + + audio_build_desc(audio); + + /* allocate instance-specific interface IDs, and patch descriptors */ + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + ac_interface_desc.bInterfaceNumber = status; + + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + as_interface_alt_0_desc.bInterfaceNumber = status; + as_interface_alt_1_desc.bInterfaceNumber = status; + + status = -ENODEV; + + /* allocate our endpoint */ + ep = usb_ep_autoconfig(cdev->gadget, &fs_as_in_ep_desc); + if (!ep) + goto fail; + audio->in_ep = ep; + ep->driver_data = audio; /* claim */ + + if (gadget_is_dualspeed(c->cdev->gadget)) + hs_as_in_ep_desc.bEndpointAddress = + fs_as_in_ep_desc.bEndpointAddress; + + f->descriptors = fs_audio_desc; + f->hs_descriptors = hs_audio_desc; + + for (i = 0, status = 0; i < IN_EP_REQ_COUNT && status == 0; i++) { + req = audio_request_new(ep, IN_EP_MAX_PACKET_SIZE); + if (req) { + req->context = audio; + req->complete = audio_data_complete; + audio_req_put(audio, req); + } else + status = -ENOMEM; + } + +fail: + return status; +} + +static void +audio_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct audio_dev *audio = func_to_audio(f); + struct usb_request *req; + + while ((req = audio_req_get(audio))) + audio_request_free(req, audio->in_ep); + + snd_card_free_when_closed(audio->card); + kfree(audio); +} + +static void audio_pcm_playback_start(struct audio_dev *audio) +{ + audio->start_time = ktime_get(); + audio->frames_sent = 0; + audio_send(audio); +} + +static void audio_pcm_playback_stop(struct audio_dev *audio) +{ + unsigned long flags; + + spin_lock_irqsave(&audio->lock, flags); + audio->buffer_start = 0; + audio->buffer_end = 0; + audio->buffer_pos = 0; + spin_unlock_irqrestore(&audio->lock, flags); +} + +static int audio_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_dev *audio = substream->private_data; + + runtime->private_data = audio; + runtime->hw = audio_hw_info; + snd_pcm_limit_hw_rates(runtime); + runtime->hw.channels_max = 2; + + audio->substream = substream; + return 0; +} + +static int audio_pcm_close(struct snd_pcm_substream *substream) +{ + struct audio_dev *audio = substream->private_data; + unsigned long flags; + + spin_lock_irqsave(&audio->lock, flags); + audio->substream = NULL; + spin_unlock_irqrestore(&audio->lock, flags); + + return 0; +} + +static int audio_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + unsigned int channels = params_channels(params); + unsigned int rate = params_rate(params); + + if (rate != SAMPLE_RATE) + return -EINVAL; + if (channels != 2) + return -EINVAL; + + return snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(params)); +} + +static int audio_pcm_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int audio_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_dev *audio = runtime->private_data; + + audio->period = snd_pcm_lib_period_bytes(substream); + audio->period_offset = 0; + audio->buffer_start = runtime->dma_area; + audio->buffer_end = audio->buffer_start + + snd_pcm_lib_buffer_bytes(substream); + audio->buffer_pos = audio->buffer_start; + + return 0; +} + +static snd_pcm_uframes_t audio_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_dev *audio = runtime->private_data; + ssize_t bytes = audio->buffer_pos - audio->buffer_start; + + /* return offset of next frame to fill in our buffer */ + return bytes_to_frames(runtime, bytes); +} + +static int audio_pcm_playback_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct audio_dev *audio = substream->runtime->private_data; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + audio_pcm_playback_start(audio); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + audio_pcm_playback_stop(audio); + break; + + default: + ret = -EINVAL; + } + + return ret; +} + +static struct snd_pcm_ops audio_playback_ops = { + .open = audio_pcm_open, + .close = audio_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = audio_pcm_hw_params, + .hw_free = audio_pcm_hw_free, + .prepare = audio_pcm_prepare, + .trigger = audio_pcm_playback_trigger, + .pointer = audio_pcm_pointer, +}; + +int audio_source_bind_config(struct usb_configuration *c, + struct audio_source_config *config) +{ + struct audio_dev *audio; + struct snd_card *card; + struct snd_pcm *pcm; + int err; + + config->card = -1; + config->device = -1; + + audio = kzalloc(sizeof *audio, GFP_KERNEL); + if (!audio) + return -ENOMEM; + + audio->func.name = "audio_source"; + + spin_lock_init(&audio->lock); + + audio->func.bind = audio_bind; + audio->func.unbind = audio_unbind; + audio->func.set_alt = audio_set_alt; + audio->func.setup = audio_setup; + audio->func.disable = audio_disable; + audio->in_desc = &fs_as_in_ep_desc; + + INIT_LIST_HEAD(&audio->idle_reqs); + + err = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, + THIS_MODULE, 0, &card); + if (err) + goto snd_card_fail; + + snd_card_set_dev(card, &c->cdev->gadget->dev); + + err = snd_pcm_new(card, "USB audio source", 0, 1, 0, &pcm); + if (err) + goto pcm_fail; + pcm->private_data = audio; + pcm->info_flags = 0; + audio->pcm = pcm; + + strlcpy(pcm->name, "USB gadget audio", sizeof(pcm->name)); + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &audio_playback_ops); + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + NULL, 0, 64 * 1024); + + strlcpy(card->driver, "audio_source", sizeof(card->driver)); + strlcpy(card->shortname, card->driver, sizeof(card->shortname)); + strlcpy(card->longname, "USB accessory audio source", + sizeof(card->longname)); + + err = snd_card_register(card); + if (err) + goto register_fail; + + err = usb_add_function(c, &audio->func); + if (err) + goto add_fail; + + config->card = pcm->card->number; + config->device = pcm->device; + audio->card = card; + return 0; + +add_fail: +register_fail: +pcm_fail: + snd_card_free(audio->card); +snd_card_fail: + kfree(audio); + return err; +} diff --git a/drivers/usb/gadget/tegra_udc.c b/drivers/usb/gadget/tegra_udc.c index aad99a6cd66e..ddf9c602dc89 100644 --- a/drivers/usb/gadget/tegra_udc.c +++ b/drivers/usb/gadget/tegra_udc.c @@ -129,8 +129,9 @@ static void done(struct tegra_ep *ep, struct tegra_req *req, int status) { struct tegra_udc *udc = NULL; unsigned char stopped = ep->stopped; - struct ep_td_struct *curr_td, *next_td; + struct ep_td_struct *curr_td, *next_td = 0; int j; + int count; BUG_ON(!(in_irq() || irqs_disabled())); udc = (struct tegra_udc *)ep->udc; /* Removed the req from tegra_ep->queue */ @@ -143,12 +144,19 @@ static void done(struct tegra_ep *ep, struct tegra_req *req, int status) status = req->req.status; /* Free dtd for the request */ - next_td = req->head; - for (j = 0; j < req->dtd_count; j++) { + count = 0; + if (ep->last_td) { + next_td = ep->last_td; + count = ep->last_dtd_count; + } + ep->last_td = req->head; + ep->last_dtd_count = req->dtd_count; + + for (j = 0; j < count; j++) { curr_td = next_td; - if (j != req->dtd_count - 1) + if (j != count - 1) { next_td = curr_td->next_td_virt; - + } dma_pool_free(udc->td_pool, curr_td, curr_td->td_dma); } @@ -568,6 +576,8 @@ static int tegra_ep_enable(struct usb_ep *_ep, ep->ep.maxpacket = max; ep->desc = desc; ep->stopped = 0; + ep->last_td = 0; + ep->last_dtd_count = 0; /* Controller related setup * Init EPx Queue Head (Ep Capabilites field in QH @@ -610,6 +620,8 @@ static int tegra_ep_disable(struct usb_ep *_ep) unsigned long flags = 0; u32 epctrl; int ep_num; + struct ep_td_struct *curr_td, *next_td; + int j; ep = container_of(_ep, struct tegra_ep, ep); if (!_ep || !ep->desc) { @@ -638,6 +650,18 @@ static int tegra_ep_disable(struct usb_ep *_ep) ep->desc = NULL; ep->stopped = 1; + if (ep->last_td) { + next_td = ep->last_td; + for (j = 0; j < ep->last_dtd_count; j++) { + curr_td = next_td; + dma_pool_free(udc->td_pool, curr_td, curr_td->td_dma); + if (j != ep->last_dtd_count - 1) { + next_td = curr_td->next_td_virt; + } + } + } + ep->last_td =0; + ep->last_dtd_count = 0; spin_unlock_irqrestore(&udc->lock, flags); VDBG("disabled %s OK", _ep->name); @@ -1216,6 +1240,76 @@ static int tegra_set_selfpowered(struct usb_gadget *gadget, int is_on) return 0; } +static int tegra_usb_set_charging_current(struct tegra_udc *udc) +{ + int max_ua; + + if (NULL == udc->vbus_reg) + return 0; + + switch (udc->connect_type) { + case CONNECT_TYPE_NONE: + pr_debug("detected USB charging is disabled"); + max_ua = 0; + break; + case CONNECT_TYPE_SDP: + pr_debug("detected SDP port"); + max_ua = USB_CHARGING_SDP_CURRENT_LIMIT_UA; + break; + case CONNECT_TYPE_DCP: + pr_debug("detected DCP port(wall charger)"); + max_ua = USB_CHARGING_DCP_CURRENT_LIMIT_UA; + break; + case CONNECT_TYPE_CDP: + pr_debug("detected CDP port(1A USB port)"); + max_ua = USB_CHARGING_CDP_CURRENT_LIMIT_UA; + break; + case CONNECT_TYPE_NON_STANDARD_CHARGER: + pr_debug("detected non-standard charging port"); + max_ua = USB_CHARGING_NON_STANDARD_CHARGER_CURRENT_LIMIT_UA; + break; + default: + pr_debug("detected USB charging type is unknown"); + max_ua = 0; + } + + return regulator_set_current_limit(udc->vbus_reg, 0, max_ua); +} + +static void tegra_detect_charging_type_is_cdp_or_dcp(struct tegra_udc *udc) +{ + u32 portsc; + u32 temp; + unsigned long flags; + + /* use spinlock to prevent kernel preemption here */ + spin_lock_irqsave(&udc->lock, flags); + + /* set controller to run which cause D+ pull high */ + temp = udc_readl(udc, USB_CMD_REG_OFFSET); + temp |= USB_CMD_RUN_STOP; + udc_writel(udc, temp, USB_CMD_REG_OFFSET); + + udelay(10); + + /* use D+ and D- status to check it is CDP or DCP */ + portsc = udc_readl(udc, PORTSCX_REG_OFFSET) & PORTSCX_LINE_STATUS_BITS; + if (portsc == (PORTSCX_LINE_STATUS_DP_BIT | PORTSCX_LINE_STATUS_DM_BIT)) + udc->connect_type = CONNECT_TYPE_DCP; + else if (portsc == PORTSCX_LINE_STATUS_DP_BIT) + udc->connect_type = CONNECT_TYPE_CDP; + else + /* + * If it take more 100mS between D+ pull high and read Line + * Status, host might initiate the RESET, then we see both + * line status as 0 (SE0). This really should not happen as we + * disabled the kernel preemption before reaching here. + */ + BUG(); + + spin_unlock_irqrestore(&udc->lock, flags); +} + /** * Notify controller that VBUS is powered, called by whatever * detects VBUS sessions @@ -1238,13 +1332,10 @@ static int tegra_vbus_session(struct usb_gadget *gadget, int is_active) dr_controller_reset(udc); udc->vbus_active = 0; udc->usb_state = USB_STATE_DEFAULT; + udc->connect_type = CONNECT_TYPE_NONE; spin_unlock_irqrestore(&udc->lock,flags); tegra_usb_phy_power_off(udc->phy); - if (udc->vbus_reg) { - /* set the current limit to 0mA */ - regulator_set_current_limit( - udc->vbus_reg, 0, 0); - } + tegra_usb_set_charging_current(udc); } else if (!udc->vbus_active && is_active) { tegra_usb_phy_power_on(udc->phy); /* setup the controller in the device mode */ @@ -1256,18 +1347,23 @@ static int tegra_vbus_session(struct usb_gadget *gadget, int is_active) udc->ep0_state = WAIT_FOR_SETUP; udc->ep0_dir = 0; udc->vbus_active = 1; + if (tegra_usb_phy_charger_detected(udc->phy)) { + tegra_detect_charging_type_is_cdp_or_dcp(udc); + } else { + udc->connect_type = CONNECT_TYPE_SDP; + /* + * Schedule work to wait for 1000 msec and check for + * a non-standard charger if setup packet is not + * received. + */ + schedule_delayed_work(&udc->work, msecs_to_jiffies( + USB_CHARGER_DETECTION_WAIT_TIME_MS)); + } /* start the controller */ dr_controller_run(udc); - if (udc->vbus_reg) { - /* set the current limit to 100mA */ - regulator_set_current_limit( - udc->vbus_reg, 0, 100); - } - /* Schedule work to wait for 1000 msec and check for - * charger if setup packet is not received */ - schedule_delayed_work(&udc->work, - USB_CHARGER_DETECTION_WAIT_TIME_MS); + tegra_usb_set_charging_current(udc); } + return 0; } @@ -2095,26 +2191,18 @@ static void tegra_udc_irq_work(struct work_struct *irq_work) DBG("%s(%d) END\n", __func__, __LINE__); } -/** - * If VBUS is detected and setup packet is not received in 100ms then - * work thread starts and checks for the USB charger detection. +/* + * When VBUS is detected we already know it is DCP/SDP/CDP devices if it is a + * standard device; If we did not receive EP0 setup packet, we can assuming it + * is a charger capable of 1.8A charging. */ static void tegra_udc_charger_detect_work(struct work_struct *work) { struct tegra_udc *udc = container_of(work, struct tegra_udc, work.work); DBG("%s(%d) BEGIN\n", __func__, __LINE__); - /* check for the platform charger detection */ - if (tegra_usb_phy_charger_detected(udc->phy)) { - printk(KERN_INFO "USB compliant charger detected\n"); - /* check udc regulator is available for drawing vbus current*/ - if (udc->vbus_reg) { - /* set the current limit in uA */ - regulator_set_current_limit( - udc->vbus_reg, 0, - USB_CHARGING_CURRENT_LIMIT_MA*1000); - } - } + udc->connect_type = CONNECT_TYPE_NON_STANDARD_CHARGER; + tegra_usb_set_charging_current(udc); DBG("%s(%d) END\n", __func__, __LINE__); } @@ -2159,10 +2247,8 @@ static irqreturn_t tegra_udc_irq(int irq, void *_udc) } /* Disable ISR for OTG host mode */ - if (udc->stopped) { - spin_unlock_irqrestore(&udc->lock, flags); - return status; - } + if (udc->stopped) + goto done; /* Fence read for coherency of AHB master intiated writes */ readb(IO_ADDRESS(IO_PPCS_PHYS + USB1_PREFETCH_ID)); @@ -2170,6 +2256,9 @@ static irqreturn_t tegra_udc_irq(int irq, void *_udc) irq_src = udc_readl(udc, USB_STS_REG_OFFSET) & udc_readl(udc, USB_INTR_REG_OFFSET); + if (irq_src == 0) + goto done; + /* Clear notification bits */ udc_writel(udc, irq_src, USB_STS_REG_OFFSET); @@ -2226,6 +2315,7 @@ static irqreturn_t tegra_udc_irq(int irq, void *_udc) if (irq_src & (USB_STS_ERR | USB_STS_SYS_ERR)) VDBG("Error IRQ %x", irq_src); +done: spin_unlock_irqrestore(&udc->lock, flags); return status; } @@ -2274,11 +2364,13 @@ static int tegra_udc_start(struct usb_gadget_driver *driver, /* Enable DR IRQ reg and Set usbcmd reg Run bit */ - dr_controller_run(udc); - udc->usb_state = USB_STATE_ATTACHED; - udc->ep0_state = WAIT_FOR_SETUP; - udc->ep0_dir = 0; - udc->vbus_active = vbus_enabled(udc); + if (vbus_enabled(udc)) { + dr_controller_run(udc); + udc->usb_state = USB_STATE_ATTACHED; + udc->ep0_state = WAIT_FOR_SETUP; + udc->ep0_dir = 0; + udc->vbus_active = vbus_enabled(udc); + } printk(KERN_INFO "%s: bind to driver %s\n", udc->gadget.name, driver->driver.name); @@ -2632,7 +2724,7 @@ static int __init tegra_udc_probe(struct platform_device *pdev) } #else /* Power down the phy if cable is not connected */ - if (!vbus_enabled()) + if (!vbus_enabled(udc)) tegra_usb_phy_power_off(udc->phy); #endif diff --git a/drivers/usb/gadget/tegra_udc.h b/drivers/usb/gadget/tegra_udc.h index e94543fd98e3..094e3eb0e458 100644 --- a/drivers/usb/gadget/tegra_udc.h +++ b/drivers/usb/gadget/tegra_udc.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011 NVIDIA Corporation + * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved. * * Description: * High-speed USB device controller driver. @@ -38,7 +38,11 @@ #define USB_MAX_CTRL_PAYLOAD 64 /* Charger current limit=1800mA, as per the USB charger spec */ -#define USB_CHARGING_CURRENT_LIMIT_MA 1800 +#define USB_CHARGING_DCP_CURRENT_LIMIT_UA 1800000 +#define USB_CHARGING_CDP_CURRENT_LIMIT_UA 900000 +#define USB_CHARGING_SDP_CURRENT_LIMIT_UA 100000 +#define USB_CHARGING_NON_STANDARD_CHARGER_CURRENT_LIMIT_UA 1800000 + /* 1 sec wait time for charger detection after vbus is detected */ #define USB_CHARGER_DETECTION_WAIT_TIME_MS 1000 #define BOOST_TRIGGER_SIZE 4096 @@ -156,6 +160,8 @@ #define PORTSCX_PORT_SUSPEND 0x00000080 #define PORTSCX_PORT_RESET 0x00000100 #define PORTSCX_LINE_STATUS_BITS 0x00000C00 +#define PORTSCX_LINE_STATUS_DP_BIT 0x00000800 +#define PORTSCX_LINE_STATUS_DM_BIT 0x00000400 #define PORTSCX_PORT_POWER 0x00001000 #define PORTSCX_PORT_INDICTOR_CTRL 0x0000C000 #define PORTSCX_PORT_TEST_CTRL 0x000F0000 @@ -390,11 +396,21 @@ struct tegra_ep { struct ep_queue_head *qh; const struct usb_endpoint_descriptor *desc; struct usb_gadget *gadget; + struct ep_td_struct *last_td; + int last_dtd_count; char name[14]; unsigned stopped:1; }; +enum tegra_connect_type { + CONNECT_TYPE_NONE, + CONNECT_TYPE_SDP, + CONNECT_TYPE_DCP, + CONNECT_TYPE_CDP, + CONNECT_TYPE_NON_STANDARD_CHARGER +}; + struct tegra_udc { struct usb_gadget gadget; struct usb_gadget_driver *driver; @@ -415,6 +431,7 @@ struct tegra_udc { struct work_struct boost_cpufreq_work; /* irq work for controlling the usb power */ struct work_struct irq_work; + enum tegra_connect_type connect_type; void __iomem *regs; size_t ep_qh_size; /* size after alignment adjustment*/ dma_addr_t ep_qh_dma; /* dma address of QH */ diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c index 5c25783a7643..0eb20989d67e 100644 --- a/drivers/usb/host/ehci-q.c +++ b/drivers/usb/host/ehci-q.c @@ -334,6 +334,7 @@ qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh) qh->qh_state = QH_STATE_COMPLETING; stopped = (state == QH_STATE_IDLE); + ehci_sync_qh(ehci, qh); rescan: last = NULL; last_status = -EINPROGRESS; @@ -367,6 +368,7 @@ qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh) if (qtd == end) break; + ehci_sync_qtd(ehci, qtd); /* hardware copies qtd out of qh overlay */ rmb (); token = hc32_to_cpu(ehci, qtd->hw_token); diff --git a/drivers/usb/host/ehci-tegra.c b/drivers/usb/host/ehci-tegra.c index 33504e402611..af6fa87fb645 100644 --- a/drivers/usb/host/ehci-tegra.c +++ b/drivers/usb/host/ehci-tegra.c @@ -156,7 +156,6 @@ static irqreturn_t tegra_ehci_irq(struct usb_hcd *hcd) struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller); struct ehci_hcd *ehci = hcd_to_ehci(hcd); irqreturn_t irq_status; - bool pmc_remote_wakeup = false; spin_lock(&ehci->lock); irq_status = tegra_usb_phy_irq(tegra->phy); @@ -166,8 +165,9 @@ static irqreturn_t tegra_ehci_irq(struct usb_hcd *hcd) } if (tegra_usb_phy_remote_wakeup(tegra->phy)) { ehci_info(ehci, "remote wakeup detected\n"); - pmc_remote_wakeup = true; usb_hcd_resume_root_hub(hcd); + spin_unlock(&ehci->lock); + return irq_status; } spin_unlock(&ehci->lock); @@ -178,10 +178,6 @@ static irqreturn_t tegra_ehci_irq(struct usb_hcd *hcd) irq_status = ehci_irq(hcd); - if (pmc_remote_wakeup) { - ehci->controller_remote_wakeup = false; - } - if (ehci->controller_remote_wakeup) { ehci->controller_remote_wakeup = false; tegra_usb_phy_pre_resume(tegra->phy, true); @@ -202,7 +198,6 @@ static int tegra_ehci_hub_control( { struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller); struct ehci_hcd *ehci = hcd_to_ehci(hcd); - unsigned long flags; int retval = 0; u32 __iomem *status_reg; @@ -216,6 +211,7 @@ static int tegra_ehci_hub_control( switch (typeReq) { case GetPortStatus: if (tegra->port_resuming) { + u32 cmd; int delay = ehci->reset_done[wIndex-1] - jiffies; /* Sometimes it seems we get called too soon... In that case, wait.*/ if (delay > 0) { @@ -230,9 +226,11 @@ static int tegra_ehci_hub_control( tegra_usb_phy_post_resume(tegra->phy); tegra->port_resuming = 0; /* If run bit is not set by now enable it */ - if (ehci->command & CMD_RUN) { + cmd = ehci_readl(ehci, &ehci->regs->command); + if (!(cmd & CMD_RUN)) { + cmd |= CMD_RUN; ehci->command |= CMD_RUN; - ehci_writel(ehci, ehci->command, &ehci->regs->command); + ehci_writel(ehci, cmd, &ehci->regs->command); } /* Now we can safely re-enable irqs */ ehci_writel(ehci, INTR_MASK, &ehci->regs->intr_enable); @@ -553,10 +551,10 @@ static int tegra_ehci_remove(struct platform_device *pdev) tegra_usb_phy_power_on(tegra->phy); usb_remove_hcd(hcd); - usb_put_hcd(hcd); tegra_usb_phy_power_off(tegra->phy); tegra_usb_phy_close(tegra->phy); iounmap(hcd->regs); + usb_put_hcd(hcd); return 0; } diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index cfbdf32ec0b2..569cdf5a4c03 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -767,10 +767,32 @@ static inline void ehci_sync_mem(void) { mb(); } + +/* + * DMA coherent memory on ARM which features speculative prefetcher doesn't + * guarantee coherency, so introduce the helpers which can invalidate QH and + * QTD in L1/L2 cache. It enforces CPU reads from memory directly. + */ +static inline void ehci_sync_qh(struct ehci_hcd *ehci, struct ehci_qh *qh) +{ + dma_sync_single_for_cpu(ehci_to_hcd(ehci)->self.controller, qh->qh_dma, + sizeof(struct ehci_qh_hw), DMA_FROM_DEVICE); +} +static inline void ehci_sync_qtd(struct ehci_hcd *ehci, struct ehci_qtd *qtd) +{ + dma_sync_single_for_cpu(ehci_to_hcd(ehci)->self.controller, + qtd->qtd_dma, sizeof(struct ehci_qtd), DMA_FROM_DEVICE); +} #else static inline void ehci_sync_mem() { } +static inline void ehci_sync_qh(struct ehci_hcd *ehci, struct ehci_qh *qh) +{ +} +static inline void ehci_sync_qtd(struct ehci_hcd *ehci, struct ehci_qtd *qtd) +{ +} #endif /*-------------------------------------------------------------------------*/ diff --git a/drivers/usb/otg/Makefile b/drivers/usb/otg/Makefile index c09f9b829382..cbedff4e254f 100644 --- a/drivers/usb/otg/Makefile +++ b/drivers/usb/otg/Makefile @@ -14,6 +14,7 @@ obj-$(CONFIG_USB_OTG_UTILS) += otg_id.o # transceiver drivers obj-$(CONFIG_USB_GPIO_VBUS) += gpio_vbus.o obj-$(CONFIG_USB_COLIBRI_OTG) += colibri-otg.o +CFLAGS_tegra-otg.o = -Werror obj-$(CONFIG_USB_TEGRA_OTG) += tegra-otg.o obj-$(CONFIG_ISP1301_OMAP) += isp1301_omap.o obj-$(CONFIG_TWL4030_USB) += twl4030-usb.o diff --git a/drivers/usb/otg/tegra-otg.c b/drivers/usb/otg/tegra-otg.c index a5e85bf90d14..22e303ede24e 100644 --- a/drivers/usb/otg/tegra-otg.c +++ b/drivers/usb/otg/tegra-otg.c @@ -53,6 +53,7 @@ struct tegra_otg_data { struct otg_transceiver otg; unsigned long int_status; spinlock_t lock; + struct mutex irq_work_mutex; void __iomem *regs; struct clk *clk; int irq; @@ -62,7 +63,7 @@ struct tegra_otg_data { bool clk_enabled; bool interrupt_mode; bool builtin_host; - bool suspended + bool suspended; }; static struct tegra_otg_data *tegra_clone; @@ -105,7 +106,7 @@ static unsigned long enable_interrupt(struct tegra_otg_data *tegra, bool en) if (tegra->builtin_host) val |= USB_INT_EN; else - val = USB_VBUS_INT_EN | USB_VBUS_WAKEUP_EN | USB_ID_PIN_WAKEUP_EN; + val |= USB_VBUS_INT_EN | USB_VBUS_WAKEUP_EN | USB_ID_PIN_WAKEUP_EN; } else val &= ~USB_INT_EN; @@ -181,6 +182,12 @@ static void tegra_stop_host(struct tegra_otg_data *tegra) DBG("%s(%d) End\n", __func__, __LINE__); } +static void tegra_otg_notify_event(struct otg_transceiver *otg, + enum usb_xceiv_events event) +{ + otg->last_event = event; + atomic_notifier_call_chain(&otg->notifier, event, NULL); +} static void tegra_change_otg_state(struct tegra_otg_data *tegra, enum usb_otg_state to) @@ -202,16 +209,24 @@ static void tegra_change_otg_state(struct tegra_otg_data *tegra, tegra_state_name(to)); if (from == OTG_STATE_A_SUSPEND) { - if (to == OTG_STATE_B_PERIPHERAL && otg->gadget) + if (to == OTG_STATE_B_PERIPHERAL && otg->gadget) { usb_gadget_vbus_connect(otg->gadget); - else if (to == OTG_STATE_A_HOST) + tegra_otg_notify_event(otg, USB_EVENT_VBUS); + } + else if (to == OTG_STATE_A_HOST) { tegra_start_host(tegra); + tegra_otg_notify_event(otg, USB_EVENT_ID); + } } else if (from == OTG_STATE_A_HOST) { - if (to == OTG_STATE_A_SUSPEND) + if (to == OTG_STATE_A_SUSPEND) { tegra_stop_host(tegra); + tegra_otg_notify_event(otg, USB_EVENT_NONE); + } } else if (from == OTG_STATE_B_PERIPHERAL && otg->gadget) { - if (to == OTG_STATE_A_SUSPEND) + if (to == OTG_STATE_A_SUSPEND) { usb_gadget_vbus_disconnect(otg->gadget); + tegra_otg_notify_event(otg, USB_EVENT_NONE); + } } } } @@ -221,16 +236,19 @@ static void irq_work(struct work_struct *work) struct tegra_otg_data *tegra = container_of(work, struct tegra_otg_data, work); struct otg_transceiver *otg = &tegra->otg; - enum usb_otg_state from = otg->state; + enum usb_otg_state from; enum usb_otg_state to = OTG_STATE_UNDEFINED; unsigned long flags; unsigned long status; + mutex_lock(&tegra->irq_work_mutex); + spin_lock_irqsave(&tegra->lock, flags); + from = otg->state; status = tegra->int_status; /* Debug prints */ - DBG("%s(%d) status = 0x%x\n", __func__, __LINE__, status); + DBG("%s(%d) status = 0x%lx\n", __func__, __LINE__, status); if ((status & USB_ID_INT_STATUS) && (status & USB_VBUS_INT_STATUS)) DBG("%s(%d) got vbus & id interrupt\n", __func__, __LINE__); @@ -250,6 +268,7 @@ static void irq_work(struct work_struct *work) spin_unlock_irqrestore(&tegra->lock, flags); tegra_change_otg_state(tegra, to); + mutex_unlock(&tegra->irq_work_mutex); } static irqreturn_t tegra_otg_irq(int irq, void *data) @@ -260,10 +279,10 @@ static irqreturn_t tegra_otg_irq(int irq, void *data) spin_lock_irqsave(&tegra->lock, flags); val = otg_readl(tegra, USB_PHY_WAKEUP); - DBG("%s(%d) interrupt val = 0x%x\n", __func__, __LINE__, val); + DBG("%s(%d) interrupt val = 0x%lx\n", __func__, __LINE__, val); if (val & (USB_VBUS_INT_EN | USB_ID_INT_EN)) { - DBG("%s(%d) PHY_WAKEUP = 0x%x\n", __func__, __LINE__, val); + DBG("%s(%d) PHY_WAKEUP = 0x%lx\n", __func__, __LINE__, val); otg_writel(tegra, val, USB_PHY_WAKEUP); if ((val & USB_ID_INT_STATUS) || (val & USB_VBUS_INT_STATUS)) { tegra->int_status = val; @@ -355,7 +374,7 @@ static ssize_t store_host_en(struct device *dev, struct device_attribute *attr, { struct platform_device *pdev = to_platform_device(dev); struct tegra_otg_data *tegra = platform_get_drvdata(pdev); - unsigned long host; + unsigned int host; if (sscanf(buf, "%d", &host) != 1 || host < 0 || host > 1) return -EINVAL; @@ -395,6 +414,7 @@ static int tegra_otg_probe(struct platform_device *pdev) tegra->otg.set_suspend = tegra_otg_set_suspend; tegra->otg.set_power = tegra_otg_set_power; spin_lock_init(&tegra->lock); + mutex_init(&tegra->irq_work_mutex); if (pdata) { tegra->builtin_host = !pdata->ehci_pdata->builtin_host_disabled; @@ -498,6 +518,7 @@ static int __exit tegra_otg_remove(struct platform_device *pdev) clk_disable(tegra->clk); clk_put(tegra->clk); platform_set_drvdata(pdev, NULL); + mutex_destroy(&tegra->irq_work_mutex); kfree(tegra); return 0; @@ -510,6 +531,8 @@ static int tegra_otg_suspend(struct device *dev) struct tegra_otg_data *tegra = platform_get_drvdata(pdev); struct otg_transceiver *otg = &tegra->otg; int val; + + mutex_lock(&tegra->irq_work_mutex); DBG("%s(%d) BEGIN state : %s\n", __func__, __LINE__, tegra_state_name(otg->state)); @@ -526,6 +549,7 @@ static int tegra_otg_suspend(struct device *dev) tegra->suspended = true; DBG("%s(%d) END\n", __func__, __LINE__); + mutex_unlock(&tegra->irq_work_mutex); return 0; } @@ -533,13 +557,15 @@ static void tegra_otg_resume(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct tegra_otg_data *tegra = platform_get_drvdata(pdev); - struct otg_transceiver *otg = &tegra->otg; int val; unsigned long flags; DBG("%s(%d) BEGIN\n", __func__, __LINE__); - if (!tegra->suspended) + mutex_lock(&tegra->irq_work_mutex); + if (!tegra->suspended) { + mutex_unlock(&tegra->irq_work_mutex); return; + } /* Clear pending interrupts */ clk_enable(tegra->clk); @@ -548,10 +574,6 @@ static void tegra_otg_resume(struct device *dev) DBG("%s(%d) PHY WAKEUP register : 0x%x\n", __func__, __LINE__, val); clk_disable(tegra->clk); - /* Handle if host cable is replaced with device during suspend state */ - if (otg->state == OTG_STATE_A_HOST && (val & USB_ID_STATUS)) - tegra_change_otg_state(tegra, OTG_STATE_A_SUSPEND); - /* Enable interrupt and call work to set to appropriate state */ spin_lock_irqsave(&tegra->lock, flags); if (tegra->builtin_host) @@ -561,13 +583,13 @@ static void tegra_otg_resume(struct device *dev) USB_ID_PIN_WAKEUP_EN; spin_unlock_irqrestore(&tegra->lock, flags); - irq_work(&tegra->work); - + schedule_work(&tegra->work); enable_interrupt(tegra, true); tegra->suspended = false; DBG("%s(%d) END\n", __func__, __LINE__); + mutex_unlock(&tegra->irq_work_mutex); } static const struct dev_pm_ops tegra_otg_pm_ops = { diff --git a/drivers/usb/serial/baseband_usb_chr.c b/drivers/usb/serial/baseband_usb_chr.c index 96db92715207..c4eda3fe1ab7 100644 --- a/drivers/usb/serial/baseband_usb_chr.c +++ b/drivers/usb/serial/baseband_usb_chr.c @@ -74,6 +74,13 @@ static struct workqueue_struct *chr_ipc_wq; static atomic_t g_rx_count = ATOMIC_INIT(0); +struct usb_chr_resource { + unsigned char *ipc_rx; + unsigned char *ipc_tx; + unsigned char *rx_buf; +}; +static struct usb_chr_resource usb_chr_res; + /* baseband ipc functions */ static void baseband_ipc_dump(const char *prefix, unsigned long int offset, @@ -521,9 +528,7 @@ static void baseband_ipc_close(struct baseband_ipc *ipc) memset(&ipc->rx.wait, 0, sizeof(ipc->rx.wait)); /* destroy data buffers */ - kfree(ipc->ipc_tx); ipc->ipc_tx = (unsigned char *) 0; - kfree(ipc->ipc_rx); ipc->ipc_rx = (unsigned char *) 0; list_for_each_entry_safe(ipc_buf, ipc_buf_next, &ipc->tx_free.buf, list) { @@ -601,16 +606,14 @@ static struct baseband_ipc *baseband_ipc_open(work_func_t work_func, ipc_buf); list_add_tail(&ipc_buf->list, &ipc->tx_free.buf); } - ipc->ipc_rx = kmalloc(USB_CHR_RX_BUFSIZ, GFP_KERNEL); + ipc->ipc_rx = usb_chr_res.ipc_rx; if (!ipc->ipc_rx) { - pr_err("baseband_ipc_open - " - "cannot allocate ipc->ipc_rx\n"); + pr_err("%s: cannot find ipc->ipc_rx\n", __func__); goto error_exit; } - ipc->ipc_tx = kmalloc(USB_CHR_TX_BUFSIZ, GFP_KERNEL); + ipc->ipc_tx = usb_chr_res.ipc_tx; if (!ipc->ipc_tx) { - pr_err("baseband_ipc_open - " - "cannot allocate ipc->ipc_tx\n"); + pr_err("%s: cannot find ipc->ipc_tx\n", __func__); goto error_exit; } @@ -867,6 +870,7 @@ static void baseband_usb_driver_disconnect(struct usb_interface *intf) && baseband_usb_chr->ipc->workqueue) flush_workqueue(baseband_usb_chr->ipc->workqueue); usb_device_connection = false; + probe_usb_intf = NULL; } pr_debug("%s(%d) }\n", __func__, __LINE__); } @@ -956,7 +960,6 @@ static void baseband_usb_close(struct baseband_usb *usb) usb_kill_urb(usb->usb.rx_urb); if (usb->usb.rx_urb->transfer_buffer) { pr_debug("%s: free rx urb transfer buffer\n", __func__); - kfree(usb->usb.rx_urb->transfer_buffer); usb->usb.rx_urb->transfer_buffer = (void *) 0; } } @@ -966,13 +969,6 @@ static void baseband_usb_close(struct baseband_usb *usb) flush_work_sync(&usb->ipc->rx_work); } - /* close usb driver */ - if (usb->usb.driver) { - pr_debug("close usb driver {\n"); - usb_deregister(usb->usb.driver); - usb->usb.driver = (struct usb_driver *) 0; - pr_debug("close usb driver }\n"); - } /* close baseband ipc */ if (usb->ipc) { @@ -1014,24 +1010,17 @@ static struct baseband_usb *baseband_usb_open(work_func_t work_func, goto error_exit; } - /* open usb driver */ - probe_usb_intf = (struct usb_interface *) 0; usb->usb.driver = &baseband_usb_driver; - err = usb_register(&baseband_usb_driver); - if (err < 0) { - pr_err("cannot open usb driver - err %d\n", err); - goto error_exit; - } - /* wait for probe */ - pr_info("%s: waiting for usb probe...\n", __func__); - for (i = 0; i < 5 * 10; i++) { + for (i = 0; i < 5 * 50; i++) { if (probe_usb_intf && usb_device_connection) break; - msleep(100); + /* wait for probe */ + pr_debug("%s: waiting for usb probe...\n", __func__); + msleep(20); } if (!probe_usb_intf || !usb_device_connection) { - pr_info("%s: probe timed out!\n", __func__); + pr_err("%s: probe timed out!\n", __func__); goto error_exit; } @@ -1066,9 +1055,9 @@ static struct baseband_usb *baseband_usb_open(work_func_t work_func, pr_err("usb_alloc_urb() failed\n"); goto error_exit; } - buf = kmalloc(USB_CHR_RX_BUFSIZ, GFP_KERNEL); + buf = usb_chr_res.rx_buf; if (!buf) { - pr_err("%s: usb buffer kmalloc() failed\n", __func__); + pr_err("%s: usb rx buffer not found\n", __func__); usb_free_urb(urb); goto error_exit; } @@ -1222,17 +1211,33 @@ static const struct file_operations baseband_usb_chr_fops = { static int baseband_usb_chr_init(void) { - int err; + int err = -ENOMEM; pr_debug("baseband_usb_chr_init {\n"); + usb_chr_res.ipc_rx = kmalloc(USB_CHR_RX_BUFSIZ, GFP_KERNEL); + if (!usb_chr_res.ipc_rx) { + pr_err("cannot allocate ipc_rx\n"); + goto error; + } + usb_chr_res.ipc_tx = kmalloc(USB_CHR_TX_BUFSIZ, GFP_KERNEL); + if (!usb_chr_res.ipc_tx) { + pr_err("cannot allocate ipc_tx\n"); + goto error; + } + usb_chr_res.rx_buf = kmalloc(USB_CHR_RX_BUFSIZ, GFP_KERNEL); + if (!usb_chr_res.rx_buf) { + pr_err("%s: usb buffer kmalloc() failed\n", __func__); + goto error; + } + /* register character device */ err = register_chrdev(BASEBAND_USB_CHR_DEV_MAJOR, BASEBAND_USB_CHR_DEV_NAME, &baseband_usb_chr_fops); if (err < 0) { pr_err("cannot register character device - %d\n", err); - return err; + goto error; } pr_debug("registered baseband usb character device - major %d\n", BASEBAND_USB_CHR_DEV_MAJOR); @@ -1243,11 +1248,30 @@ static int baseband_usb_chr_init(void) pr_err("cannot create workqueue\n"); unregister_chrdev(BASEBAND_USB_CHR_DEV_MAJOR, BASEBAND_USB_CHR_DEV_NAME); - return -ENODEV; + err = -ENODEV; + goto error; + } + + /* register usb driver */ + err = usb_register(&baseband_usb_driver); + if (err < 0) { + pr_err("%s: cannot register usb driver %d\n", __func__, err); + goto error2; } pr_debug("baseband_usb_chr_init }\n"); return 0; + +error2: + unregister_chrdev(BASEBAND_USB_CHR_DEV_MAJOR, + BASEBAND_USB_CHR_DEV_NAME); + destroy_workqueue(chr_ipc_wq); + chr_ipc_wq = NULL; +error: + kfree(usb_chr_res.ipc_rx); + kfree(usb_chr_res.ipc_tx); + kfree(usb_chr_res.rx_buf); + return err; } static void baseband_usb_chr_exit(void) @@ -1262,6 +1286,14 @@ static void baseband_usb_chr_exit(void) destroy_workqueue(chr_ipc_wq); chr_ipc_wq = NULL; } + + /* close usb driver */ + usb_deregister(&baseband_usb_driver); + + kfree(usb_chr_res.ipc_rx); + kfree(usb_chr_res.ipc_tx); + kfree(usb_chr_res.rx_buf); + pr_debug("baseband_usb_chr_exit }\n"); } |