diff options
author | codeworkx <codeworkx@cyanogenmod.com> | 2012-09-22 09:48:20 +0200 |
---|---|---|
committer | codeworkx <codeworkx@cyanogenmod.com> | 2012-09-22 14:02:16 +0200 |
commit | 2489007e7d740ccbc3e0a202914e243ad5178787 (patch) | |
tree | b8e6380ea7b1da63474ad68a5dba997e01146043 /drivers/usb | |
parent | 5f67568eb31e3a813c7c52461dcf66ade15fc2e7 (diff) | |
download | kernel_samsung_smdk4412-2489007e7d740ccbc3e0a202914e243ad5178787.zip kernel_samsung_smdk4412-2489007e7d740ccbc3e0a202914e243ad5178787.tar.gz kernel_samsung_smdk4412-2489007e7d740ccbc3e0a202914e243ad5178787.tar.bz2 |
merge opensource jb u5
Change-Id: I1aaec157aa196f3448eff8636134fce89a814cf2
Diffstat (limited to 'drivers/usb')
42 files changed, 4236 insertions, 175 deletions
diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index 75b4bc0..e1f547e 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -1181,6 +1181,19 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg) udev->state == USB_STATE_SUSPENDED) goto done; +#ifdef CONFIG_MDM_HSIC_PM + /* when additional device attached at ehci hub, interface driver will + * goes to suspend , but hub will not goes to suspend. + * in hsic case, device modem cannot notice this change on host, so + * it does not try to send packet to host + * + * prevent suspend_both when it's parent has more child + */ + if (udev->dev.parent) { + if (atomic_read(&udev->dev.parent->power.child_count) != 1) + return -EBUSY; + } +#endif /* Suspend all the interfaces and then udev itself */ if (udev->actconfig) { n = udev->actconfig->desc.bNumInterfaces; @@ -1332,6 +1345,9 @@ int usb_resume(struct device *dev, pm_message_t msg) * Unbind the interfaces that will need rebinding later. */ } else { + #ifdef CONFIG_MDM_HSIC_PM + pm_runtime_get_sync(dev->parent); + #endif status = usb_resume_both(udev, msg); if (status == 0) { pm_runtime_disable(dev); @@ -1339,6 +1355,9 @@ int usb_resume(struct device *dev, pm_message_t msg) pm_runtime_enable(dev); do_unbind_rebind(udev, DO_REBIND); } + #ifdef CONFIG_MDM_HSIC_PM + pm_runtime_put_sync(dev->parent); + #endif } /* Avoid PM error messages for devices disconnected while suspended diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 069739b..93aa0e7 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -29,6 +29,9 @@ #include <asm/byteorder.h> #include "usb.h" +#ifdef CONFIG_SAMSUNG_SMARTDOCK +#include "sec-dock.h" +#endif /* if we are in debug mode, always announce new devices */ #ifdef DEBUG @@ -766,7 +769,16 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type) "under this hub\n."); } } + #ifdef CONFIG_MDM_HSIC_PM + /* MDM9x15, HSIC device do not need power on delay */ + if (dev_name(hub->intfdev) && + !strcmp(dev_name(hub->intfdev), "1-0:1.0")) + hub_power_on(hub, false); + else + hub_power_on(hub, true); + #else hub_power_on(hub, true); + #endif } else { hub_power_on(hub, true); } @@ -879,7 +891,8 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type) * If any port-status changes do occur during this delay, khubd * will see them later and handle them normally. */ -#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB) +#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB) \ + || defined(CONFIG_MDM_HSIC_PM) if (need_debounce_delay && type != HUB_RESET_RESUME) { #else if (need_debounce_delay) { @@ -1662,6 +1675,10 @@ void usb_disconnect(struct usb_device **pdev) dev_info(&udev->dev, "USB disconnect, device number %d by %pF\n", udev->devnum, __builtin_return_address(0)); +#ifdef CONFIG_SAMSUNG_SMARTDOCK + call_battery_notify(udev, 0); +#endif + usb_lock_device(udev); /* Free up all the children before we remove this device */ @@ -1905,7 +1922,12 @@ int usb_new_device(struct usb_device *udev) /* Tell the world! */ announce_device(udev); - +#ifdef CONFIG_SAMSUNG_SMARTDOCK +#if defined(CONFIG_MUIC_MAX77693_SUPPORT_OTG_AUDIO_DOCK) + call_audiodock_notify(udev); +#endif + call_battery_notify(udev, 1); +#endif device_enable_async_suspend(&udev->dev); /* Register the device. The device driver is responsible * for configuring the device and invoking the add-device @@ -2130,7 +2152,13 @@ static int hub_port_reset(struct usb_hub *hub, int port1, switch (status) { case 0: /* TRSTRCY = 10 ms; plus some extra */ + #ifdef CONFIG_MDM_HSIC_PM + /* MDM9x15, HSIC deivce do not need this delay */ + if (!(udev->quirks & USB_QUIRK_HSIC_TUNE)) + msleep(10 + 40); + #else msleep(10 + 40); + #endif update_devnum(udev, 0); if (hcd->driver->reset_device) { status = hcd->driver->reset_device(hcd, udev); @@ -2276,6 +2304,11 @@ static int check_port_resume_type(struct usb_device *udev, if (portchange & USB_PORT_STAT_C_ENABLE) clear_port_feature(hub->hdev, port1, USB_PORT_FEAT_C_ENABLE); + #ifdef CONFIG_MDM_HSIC_PM + /* MDM9x15, HSIC deivce do need this delay at LPA wake */ + if (udev->quirks & USB_QUIRK_HSIC_TUNE) + msleep(30); + #endif } return status; @@ -2566,8 +2599,9 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg) } SuspendCleared: -#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB) - pr_debug("mif: %s: %d, %d\n", __func__, portstatus, portchange); +#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB) \ + || defined(CONFIG_MDM_HSIC_PM) + pr_info("mif: %s: %d, %d\n", __func__, portstatus, portchange); #endif if (status == 0) { if (hub_is_superspeed(hub->hdev)) { @@ -2591,6 +2625,7 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg) dev_dbg(&udev->dev, "can't resume, status %d\n", status); hub_port_logical_disconnect(hub, port1); } + return status; } @@ -3517,7 +3552,7 @@ static void hub_events(void) * EM interference sometimes causes badly * shielded USB devices to be shutdown by * the hub, this hack enables them again. - * Works at least with mouse driver. + * Works at least with mouse driver. */ if (!(portstatus & USB_PORT_STAT_ENABLE) && !connect_change @@ -3539,7 +3574,14 @@ static void hub_events(void) udev = hdev->children[i-1]; if (udev) { /* TRSMRCY = 10 msec */ + #ifdef CONFIG_MDM_HSIC_PM + /* MDM9x15, HSIC deivce */ + if (udev->quirks & USB_QUIRK_HSIC_TUNE) + msleep(10 + 10); + else + #else msleep(10); + #endif usb_lock_device(udev); ret = usb_remote_wakeup(hdev-> @@ -3555,7 +3597,7 @@ static void hub_events(void) "resume on port %d, status %d\n", i, ret); } - + if (portchange & USB_PORT_STAT_C_OVERCURRENT) { u16 status = 0; u16 unused; diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c index 5b9755d..f826b52 100644 --- a/drivers/usb/core/quirks.c +++ b/drivers/usb/core/quirks.c @@ -165,6 +165,10 @@ static const struct usb_device_id usb_quirk_list[] = { /* STE_MAIN - M7400 */ { USB_DEVICE(0x04cc, 0x2333), .driver_info = USB_QUIRK_HSIC_TUNE }, + /* Qualcomm MDM9x15 */ + { USB_DEVICE(0x05c6, 0x9048), .driver_info = USB_QUIRK_HSIC_TUNE }, + + { USB_DEVICE(0x05c6, 0x904C), .driver_info = USB_QUIRK_HSIC_TUNE }, { } /* terminating entry must be last */ }; diff --git a/drivers/usb/core/sec-dock.h b/drivers/usb/core/sec-dock.h new file mode 100644 index 0000000..1521b67 --- /dev/null +++ b/drivers/usb/core/sec-dock.h @@ -0,0 +1,133 @@ +/* + * drivers/usb/core/sec-dock.h + * + * Copyright (C) 2012 Samsung Electronics + * Author: Woo-kwang Lee <wookwang.lee@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +#if defined(CONFIG_MUIC_MAX77693_SUPPORT_OTG_AUDIO_DOCK) +#include <linux/mfd/max77693.h> +#endif /* CONFIG_MUIC_MAX77693_SUPPORT_OTG_AUDIO_DOCK */ +#include <linux/power_supply.h> + +#define PSY_CHG_NAME "max77693-charger" + +int usb_open_count; +bool is_smartdock; + +static struct usb_device_id battery_notify_exception_table[] = { +/* add exception table list */ +{ USB_DEVICE(0x1d6b, 0x0001), }, /* OHCI Host Controller */ +{ USB_DEVICE(0x1d6b, 0x0002), }, /* EHCI Host Controller */ +{ USB_DEVICE(0x1519, 0x0020), }, /* HSIC Device */ +{ USB_DEVICE(0x05c6, 0x904c), }, /* Qualcomm modem */ +{ USB_DEVICE(0x05c6, 0x9008), }, /* Qualcomm modem */ +{ USB_DEVICE(0x08bb, 0x27c4), }, /* TI USB Audio DAC */ +{ } /* Terminating entry */ +}; + +#if defined(CONFIG_MUIC_MAX77693_SUPPORT_OTG_AUDIO_DOCK) +static struct usb_device_id audio_dock_table[] = { +/* add exception table list */ +{ USB_DEVICE(0x04e8, 0x1220), }, /* Samsung Audio Dock */ +{ USB_DEVICE(0x08bb, 0x27c4), }, /* TI USB Audio DAC */ +{ } /* Terminating entry */ +}; + +static void call_audiodock_notify(struct usb_device *dev) +{ + struct usb_device_id *id = audio_dock_table; + /* check VID, PID */ + for (id = audio_dock_table; id->match_flags; id++) { + if ((id->match_flags & USB_DEVICE_ID_MATCH_VENDOR) && + (id->match_flags & USB_DEVICE_ID_MATCH_PRODUCT) && + id->idVendor == le16_to_cpu(dev->descriptor.idVendor) && + id->idProduct == le16_to_cpu(dev->descriptor.idProduct)) { + dev_info(&dev->dev, "Audio Dock is connected!\n"); + max77693_muic_attach_audio_dock(); + return; + } + } +} +#endif /* CONFIG_MUIC_MAX77693_SUPPORT_OTG_AUDIO_DOCK */ + +/* real battery driver notification function */ +static void set_online(int host_state) +{ + struct power_supply *psy = power_supply_get_by_name(PSY_CHG_NAME); + union power_supply_propval value; + int sub_type; + + if (!psy) { + pr_err("%s: fail to get %s psy\n", __func__, PSY_CHG_NAME); + return; + } + if (host_state) + sub_type = ONLINE_SUB_TYPE_SMART_OTG; + else + sub_type = ONLINE_SUB_TYPE_SMART_NOTG; + + value.intval = 0; + value.intval = (sub_type << 8); + psy->set_property(psy, POWER_SUPPLY_PROP_ONLINE, &value); + return; +} + +static int call_battery_notify(struct usb_device *dev, bool bOnOff) +{ + struct usb_device_id *id = battery_notify_exception_table; + + /* Smart Dock hub must be skipped */ + if ((le16_to_cpu(dev->descriptor.idVendor) == 0x1a40 && + le16_to_cpu(dev->descriptor.idProduct) == 0x0101)) { + if (bOnOff) + is_smartdock = 1; + else + is_smartdock = 0; + return 0; + } + + /* check VID, PID */ + for (id = battery_notify_exception_table; id->match_flags; id++) { + if ((id->match_flags & USB_DEVICE_ID_MATCH_VENDOR) && + (id->match_flags & USB_DEVICE_ID_MATCH_PRODUCT) && + id->idVendor == le16_to_cpu(dev->descriptor.idVendor) && + id->idProduct == le16_to_cpu(dev->descriptor.idProduct)) { + pr_info("%s : VID : 0x%x, PID : 0x%x skipped.\n", + __func__, id->idVendor, id->idProduct); + return 0; + } + } + if (bOnOff) + usb_open_count++; + else + usb_open_count--; + + /* battery driver notification */ + if (usb_open_count == 1 && bOnOff && is_smartdock) { + pr_info("%s : VID : 0x%x, PID : 0x%x set 1000mA.\n", + __func__, + le16_to_cpu(dev->descriptor.idVendor), + le16_to_cpu(dev->descriptor.idProduct)); + set_online(1); + } else if (usb_open_count == 0 && !bOnOff) { + pr_info("%s : VID : 0x%x, PID : 0x%x set 1700mA.\n", + __func__, + le16_to_cpu(dev->descriptor.idVendor), + le16_to_cpu(dev->descriptor.idProduct)); + set_online(0); + } else { + pr_info("%s : VID : 0x%x, PID : 0x%x no action.\n", + __func__, + le16_to_cpu(dev->descriptor.idVendor), + le16_to_cpu(dev->descriptor.idProduct)); + /* Nothing to do */ + } + + return 1; +} + diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index bd8b874..cc02362 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -52,6 +52,7 @@ g_nokia-y := nokia.o g_webcam-y := webcam.o g_ncm-y := ncm.o g_android-y := android.o +g_slp-y := slp.o obj-$(CONFIG_USB_ZERO) += g_zero.o obj-$(CONFIG_USB_AUDIO) += g_audio.o @@ -71,6 +72,7 @@ obj-$(CONFIG_USB_G_NOKIA) += g_nokia.o obj-$(CONFIG_USB_G_WEBCAM) += g_webcam.o obj-$(CONFIG_USB_G_NCM) += g_ncm.o obj-$(CONFIG_USB_G_ANDROID) += g_android.o +obj-$(CONFIG_USB_G_SLP) += g_slp.o obj-$(CONFIG_USB_ANDROID) += gadget_gbhc/android.o obj-$(CONFIG_USB_ANDROID_ACM) += gadget_gbhc/f_acm.o gadget_gbhc/u_serial.o obj-$(CONFIG_USB_ANDROID_ADB) += gadget_gbhc/f_adb.o diff --git a/drivers/usb/gadget/android.c b/drivers/usb/gadget/android.c index 699cf29..c528f4b 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" #ifdef CONFIG_USB_DUN_SUPPORT @@ -61,6 +62,7 @@ #define USB_ETH_RNDIS y #include "f_rndis.c" #include "rndis.c" +#include "f_diag.c" #include "f_dm.c" MODULE_AUTHOR("Mike Lockwood"); @@ -95,10 +97,11 @@ struct android_usb_function { int (*init)(struct android_usb_function *, struct usb_composite_dev *); /* Optional: cleanup during gadget unbind */ void (*cleanup)(struct android_usb_function *); - /* Optional: called when the function is added the list of enabled functions */ - void (*enable)(struct android_usb_function *); - /* Optional: called when it is removed */ - void (*disable)(struct android_usb_function *); + /* Optional: called when the function is added the list of + * enabled functions */ + void (*enable)(struct android_usb_function *); + /* Optional: called when it is removed */ + void (*disable)(struct android_usb_function *); int (*bind_config)(struct android_usb_function *, struct usb_configuration *); @@ -117,7 +120,7 @@ struct android_dev { struct device *dev; bool enabled; - int disable_depth; + int disable_depth; struct mutex mutex; bool connected; bool sw_connected; @@ -142,6 +145,7 @@ static char serial_string[256]; #ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE #include "u_ncm.c" +#include "u_composite_notifier.c" #endif #include "u_ether.c" @@ -207,6 +211,14 @@ static void android_work(struct work_struct *data) kobject_uevent_env(&dev->dev->kobj, KOBJ_CHANGE, uevent_envp); printk(KERN_DEBUG "usb: %s sent uevent %s\n", __func__, uevent_envp[0]); +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + if (uevent_envp != connected) + blocking_notifier_call_chain( + &usb_composite_notifier_list, + (unsigned long)dev->connected, + (void *)function_lists_string); +#endif + } else { printk(KERN_DEBUG "usb: %s did not send uevent (%d %d %p)\n", __func__, dev->connected, dev->sw_connected, cdev->config); @@ -217,8 +229,8 @@ static void android_enable(struct android_dev *dev) { struct usb_composite_dev *cdev = dev->cdev; - if (WARN_ON(!dev->disable_depth)) - return; + BUG_ON(!mutex_is_locked(&dev->mutex)); + BUG_ON(!dev->disable_depth); if (--dev->disable_depth == 0) { usb_add_config(cdev, &android_config_driver, @@ -231,6 +243,8 @@ static void android_disable(struct android_dev *dev) { struct usb_composite_dev *cdev = dev->cdev; + BUG_ON(!mutex_is_locked(&dev->mutex)); + if (dev->disable_depth++ == 0) { usb_gadget_disconnect(cdev->gadget); /* Cancel pending control requests */ @@ -249,9 +263,9 @@ struct adb_data { static int adb_function_init(struct android_usb_function *f, struct usb_composite_dev *cdev) { - f->config = kzalloc(sizeof(struct adb_data), GFP_KERNEL); - if (!f->config) - return -ENOMEM; + f->config = kzalloc(sizeof(struct adb_data), GFP_KERNEL); + if (!f->config) + return -ENOMEM; return adb_setup(); } @@ -259,7 +273,7 @@ static int adb_function_init(struct android_usb_function *f, struct usb_composit static void adb_function_cleanup(struct android_usb_function *f) { adb_cleanup(); - kfree(f->config); + kfree(f->config); } static int adb_function_bind_config(struct android_usb_function *f, struct usb_configuration *c) @@ -678,6 +692,7 @@ static int mass_storage_function_init(struct android_usb_function *f, int err; #ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE int i; + unsigned int cdfs = 0; #endif config = kzalloc(sizeof(struct mass_storage_function_config), GFP_KERNEL); @@ -685,26 +700,28 @@ static int mass_storage_function_init(struct android_usb_function *f, return -ENOMEM; #ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE - if (android_usb_pdata && android_usb_pdata->nluns != 0) { + if (android_usb_pdata && (android_usb_pdata->nluns != 0 + || android_usb_pdata->cdfs_support != 0)) { /* Some device use sd card or not. * If you want to modify nluns, * please change nluns of standard android USB platform data * Please do not modify nluns directly in this function. * Every model uses same android file. */ - printk(KERN_DEBUG "usb: %s pdata->nluns=%d\n", __func__, - android_usb_pdata->nluns); - config->fsg.nluns = android_usb_pdata->nluns; + printk(KERN_DEBUG "usb: %s pdata->nluns=%d, cdfs = %d\n", + __func__, android_usb_pdata->nluns, + android_usb_pdata->cdfs_support); + cdfs = android_usb_pdata->cdfs_support; + config->fsg.nluns = android_usb_pdata->nluns + cdfs; + for (i = 0; i < android_usb_pdata->nluns; i++) { -#if defined(CONFIG_MACH_M0_CTC) - /*FOR CTC PC-MODEM START*/ - if (2 == i) { - printk(KERN_DEBUG "%s Enable cdfs\n", __func__); - config->fsg.luns[i].ro = 1; - config->fsg.luns[i].cdrom = 1; - } - /*FOR CTC PC-MODEM END*/ -#endif + + config->fsg.luns[i].removable = 1; + config->fsg.luns[i].nofua = 1; + } + + if (cdfs) { + config->fsg.luns[i].cdrom = 1; config->fsg.luns[i].removable = 1; config->fsg.luns[i].nofua = 1; } @@ -732,6 +749,23 @@ static int mass_storage_function_init(struct android_usb_function *f, return err; } } + if (cdfs) { + char luns[4]; + err = snprintf(luns, 4, "lun"); + if (err == 0) { + printk(KERN_ERR "usb: %s cdfs snprintf error\n", + __func__); + kfree(config); + return err; + } + err = sysfs_create_link(&f->dev->kobj, + &common->luns[i].dev.kobj, + luns); + if (err) { + kfree(config); + return err; + } + } } else { #endif /* original mainline code */ @@ -906,6 +940,63 @@ static struct android_usb_function accessory_function = { .ctrlrequest = accessory_function_ctrlrequest, }; +/* DIAG : enabled DIAG clients- "diag[,diag_mdm]" */ +static char diag_clients[32]; +static ssize_t clients_store( + struct device *device, struct device_attribute *attr, + const char *buff, size_t size) +{ + strlcpy(diag_clients, buff, sizeof(diag_clients)); + + return size; +} + +static DEVICE_ATTR(clients, S_IWUSR, NULL, clients_store); +static struct device_attribute *diag_function_attributes[] = { + &dev_attr_clients, NULL }; + +static int diag_function_init(struct android_usb_function *f, + struct usb_composite_dev *cdev) +{ + return diag_setup(); +} + +static void diag_function_cleanup(struct android_usb_function *f) +{ + diag_cleanup(); +} + +static int diag_function_bind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + char *name; + char buf[32], *b; + int once = 0, err = -1; + int (*notify)(uint32_t, const char *) = NULL; + + strlcpy(buf, diag_clients, sizeof(buf)); + b = strim(buf); + while (b) { + notify = NULL; + name = strsep(&b, ","); + + if (name) { + err = diag_function_add(c, name, notify); + if (err) + pr_err("%s : usb: diag: Cannot open channel '%s\r\n", + __func__, name); + } + } + return err; +} + +static struct android_usb_function diag_function = { + .name = "diag", + .init = diag_function_init, + .cleanup = diag_function_cleanup, + .bind_config = diag_function_bind_config, + .attributes = diag_function_attributes, +}; static int dm_function_bind_config(struct android_usb_function *f, struct usb_configuration *c) @@ -918,6 +1009,68 @@ static struct android_usb_function dm_function = { .bind_config = dm_function_bind_config, }; +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, &acm_function, @@ -929,7 +1082,9 @@ static struct android_usb_function *supported_functions[] = { #endif &mass_storage_function, &accessory_function, + &diag_function, &dm_function, + &audio_source_function, NULL }; @@ -1092,6 +1247,9 @@ functions_store(struct device *pdev, struct device_attribute *attr, printk(KERN_DEBUG "usb: %s buff=%s\n", __func__, buff); strncpy(buf, buff, sizeof(buf)); b = strim(buf); +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + strncpy(function_lists_string, b, sizeof(buf)); +#endif while (b) { name = strsep(&b, ","); @@ -1140,7 +1298,7 @@ static ssize_t enable_store(struct device *pdev, struct device_attribute *attr, { struct android_dev *dev = dev_get_drvdata(pdev); struct usb_composite_dev *cdev = dev->cdev; - struct android_usb_function *f; + struct android_usb_function *f; int enabled = 0; mutex_lock(&dev->mutex); @@ -1273,10 +1431,7 @@ field ## _store(struct device *dev, struct device_attribute *attr, \ const char *buf, size_t size) \ { \ if (size >= sizeof(buffer)) return -EINVAL; \ - if (sscanf(buf, "%s", buffer) == 1) { \ - return size; \ - } \ - return -1; \ + return strlcpy(buffer, buf, sizeof(buffer)); \ } \ static DEVICE_ATTR(field, S_IRUGO | S_IWUSR, field ## _show, field ## _store); @@ -1480,6 +1635,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; @@ -1570,7 +1730,7 @@ static int __init init(void) if (!dev) return -ENOMEM; - dev->disable_depth = 1; + dev->disable_depth = 1; dev->functions = supported_functions; INIT_LIST_HEAD(&dev->enabled_functions); INIT_WORK(&dev->work, android_work); diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c index 274facf..5f459005 100644 --- a/drivers/usb/gadget/composite.c +++ b/drivers/usb/gadget/composite.c @@ -678,6 +678,19 @@ int usb_add_config(struct usb_composite_dev *cdev, status = bind(config); if (status < 0) { + while (!list_empty(&config->functions)) { + struct usb_function *f; + + f = list_first_entry(&config->functions, + struct usb_function, list); + list_del(&f->list); + if (f->unbind) { + DBG(cdev, "unbind function '%s'/%p\n", + f->name, f); + f->unbind(config, f); + /* may free memory for "f" */ + } + } list_del(&config->list); config->cdev = NULL; } else { @@ -715,7 +728,7 @@ done: return status; } -static int remove_config(struct usb_composite_dev *cdev, +static int unbind_config(struct usb_composite_dev *cdev, struct usb_configuration *config) { while (!list_empty(&config->functions)) { @@ -730,7 +743,6 @@ static int remove_config(struct usb_composite_dev *cdev, /* may free memory for "f" */ } } - list_del(&config->list); if (config->unbind) { DBG(cdev, "unbind config '%s'/%p\n", config->label, config); config->unbind(config); @@ -751,9 +763,11 @@ int usb_remove_config(struct usb_composite_dev *cdev, if (cdev->config == config) reset_config(cdev); + list_del(&config->list); + spin_unlock_irqrestore(&cdev->lock, flags); - return remove_config(cdev, config); + return unbind_config(cdev, config); } /*-------------------------------------------------------------------------*/ @@ -1340,7 +1354,8 @@ composite_unbind(struct usb_gadget *gadget) struct usb_configuration *c; c = list_first_entry(&cdev->configs, struct usb_configuration, list); - remove_config(cdev, c); + list_del(&c->list); + unbind_config(cdev, c); } if (composite->unbind) composite->unbind(cdev); diff --git a/drivers/usb/gadget/exynos_ss_udc.c b/drivers/usb/gadget/exynos_ss_udc.c index c8001e8..9d24396 100644 --- a/drivers/usb/gadget/exynos_ss_udc.c +++ b/drivers/usb/gadget/exynos_ss_udc.c @@ -255,7 +255,7 @@ static int exynos_ss_udc_issue_epcmd(struct exynos_ss_udc *udc, return res; } -#if defined(CONFIG_BATTERY_SAMSUNG) || defined(CONFIG_BATTERY_SAMSUNG_S2PLUS) +#if defined(CONFIG_BATTERY_SAMSUNG) void exynos_ss_udc_cable_connect(struct exynos_ss_udc *udc, bool connect) { static int last_connect; diff --git a/drivers/usb/gadget/exynos_ss_udc.h b/drivers/usb/gadget/exynos_ss_udc.h index a413a44..b726bcc 100644 --- a/drivers/usb/gadget/exynos_ss_udc.h +++ b/drivers/usb/gadget/exynos_ss_udc.h @@ -424,7 +424,7 @@ struct exynos_ss_udc { struct wake_lock usbd_wake_lock; }; -#if defined(CONFIG_BATTERY_SAMSUNG) || defined(CONFIG_BATTERY_SAMSUNG_S2PLUS) +#if defined(CONFIG_BATTERY_SAMSUNG) extern void samsung_cable_check_status(int flag); #endif diff --git a/drivers/usb/gadget/f_accessory.c b/drivers/usb/gadget/f_accessory.c index dfe3e51..6dd53c3 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 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 __init 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 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(5)); + &dev->start_work, msecs_to_jiffies(5)); 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) { @@ -767,7 +1141,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; @@ -780,10 +1158,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_adb.c b/drivers/usb/gadget/f_adb.c index c5d292c..8441e45 100644 --- a/drivers/usb/gadget/f_adb.c +++ b/drivers/usb/gadget/f_adb.c @@ -57,9 +57,6 @@ struct adb_dev { int rx_done; }; -static void adb_ready_callback(void); -static void adb_closed_callback(void); - static struct usb_interface_descriptor adb_interface_desc = { .bLength = USB_DT_INTERFACE_SIZE, .bDescriptorType = USB_DT_INTERFACE, @@ -148,6 +145,8 @@ static struct usb_descriptor_header *ss_adb_descs[] = { NULL, }; +static void adb_ready_callback(void); +static void adb_closed_callback(void); /* temporary variable used between adb_open() and adb_gadget_bind() */ static struct adb_dev *_adb_dev; @@ -456,7 +455,7 @@ static int adb_open(struct inode *ip, struct file *fp) /* clear the error latch */ _adb_dev->error = 0; - adb_ready_callback(); + adb_ready_callback(); return 0; } @@ -464,7 +463,9 @@ static int adb_open(struct inode *ip, struct file *fp) static int adb_release(struct inode *ip, struct file *fp) { pr_info("adb_release\n"); - adb_closed_callback(); + + adb_closed_callback(); + adb_unlock(&_adb_dev->open_excl); return 0; } @@ -484,6 +485,9 @@ static struct miscdevice adb_device = { .fops = &adb_fops, }; + + + static int adb_function_bind(struct usb_configuration *c, struct usb_function *f) { diff --git a/drivers/usb/gadget/f_audio_source.c b/drivers/usb/gadget/f_audio_source.c new file mode 100644 index 0000000..23a7511 --- /dev/null +++ b/drivers/usb/gadget/f_audio_source.c @@ -0,0 +1,825 @@ +/* + * 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); + + pr_debug("audio_set_alt intf %d, alt %d\n", intf, alt); + usb_ep_enable(audio->in_ep, audio->in_desc); + 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/f_diag.c b/drivers/usb/gadget/f_diag.c new file mode 100644 index 0000000..2a24649 --- /dev/null +++ b/drivers/usb/gadget/f_diag.c @@ -0,0 +1,764 @@ +/* drivers/usb/gadget/f_diag.c + * Diag Function Device - Route ARM9 and ARM11 DIAG messages + * between HOST and DEVICE. + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2008-2011, Code Aurora Forum. All rights reserved. + * Author: Brian Swetland <swetland@google.com> + * 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/init.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> + +#include <mach/usbdiag.h> + +#include <linux/usb/composite.h> +#include <linux/usb/gadget.h> +#include <linux/workqueue.h> +#include <linux/debugfs.h> + +static DEFINE_SPINLOCK(ch_lock); +static LIST_HEAD(usb_diag_ch_list); + +static struct usb_interface_descriptor intf_desc = { + .bLength = sizeof intf_desc, + .bDescriptorType = USB_DT_INTERFACE, + .bNumEndpoints = 2, + .bInterfaceClass = 0xFF, +#if defined(CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE) || defined(CONFIG_SLP) + .bInterfaceSubClass = 0x10, + .bInterfaceProtocol = 0x01, +#else + .bInterfaceSubClass = 0xFF, + .bInterfaceProtocol = 0xFF, +#endif +}; + +static struct usb_endpoint_descriptor hs_bulk_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), + .bInterval = 0, +}; +static struct usb_endpoint_descriptor fs_bulk_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(64), + .bInterval = 0, +}; + +static struct usb_endpoint_descriptor hs_bulk_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), + .bInterval = 0, +}; + +static struct usb_endpoint_descriptor fs_bulk_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(64), + .bInterval = 0, +}; + +static struct usb_descriptor_header *fs_diag_desc[] = { + (struct usb_descriptor_header *) &intf_desc, + (struct usb_descriptor_header *) &fs_bulk_in_desc, + (struct usb_descriptor_header *) &fs_bulk_out_desc, + NULL, + }; +static struct usb_descriptor_header *hs_diag_desc[] = { + (struct usb_descriptor_header *) &intf_desc, + (struct usb_descriptor_header *) &hs_bulk_in_desc, + (struct usb_descriptor_header *) &hs_bulk_out_desc, + NULL, +}; + +/** + * struct diag_context - USB diag function driver private structure + * @function: function structure for USB interface + * @out: USB OUT endpoint struct + * @in: USB IN endpoint struct + * @in_desc: USB IN endpoint descriptor struct + * @out_desc: USB OUT endpoint descriptor struct + * @read_pool: List of requests used for Rx (OUT ep) + * @write_pool: List of requests used for Tx (IN ep) + * @config_work: Work item schedule after interface is configured to notify + * CONNECT event to diag char driver and updating product id + * and serial number to MODEM/IMEM. + * @lock: Spinlock to proctect read_pool, write_pool lists + * @cdev: USB composite device struct + * @ch: USB diag channel + * + */ +struct diag_context { + struct usb_function function; + struct usb_ep *out; + struct usb_ep *in; + struct usb_endpoint_descriptor *in_desc; + struct usb_endpoint_descriptor *out_desc; + struct list_head read_pool; + struct list_head write_pool; + struct work_struct config_work; + spinlock_t lock; + unsigned configured; + struct usb_composite_dev *cdev; + int (*update_pid_and_serial_num)(uint32_t, const char *); + struct usb_diag_ch ch; + + /* pkt counters */ + unsigned long dpkts_tolaptop; + unsigned long dpkts_tomodem; + unsigned dpkts_tolaptop_pending; + // zero_pky.patch by jagadish + bool qxdm_ops; +}; + +static inline struct diag_context *func_to_diag(struct usb_function *f) +{ + return container_of(f, struct diag_context, function); +} + +static void usb_config_work_func(struct work_struct *work) +{ + struct diag_context *ctxt = container_of(work, + struct diag_context, config_work); + struct usb_composite_dev *cdev = ctxt->cdev; + struct usb_gadget_strings *table; + struct usb_string *s; + + if (ctxt->ch.notify) + { + ctxt->ch.notify(ctxt->ch.priv, USB_DIAG_CONNECT, NULL); + // zero_pky.patch by jagadish + ctxt->qxdm_ops = 0; + } +} + +static void diag_write_complete(struct usb_ep *ep, + struct usb_request *req) +{ + struct diag_context *ctxt = ep->driver_data; + struct diag_request *d_req = req->context; + unsigned long flags; + + ctxt->dpkts_tolaptop_pending--; + + if (!req->status) { + if ((req->length >= ep->maxpacket) && + ((req->length % ep->maxpacket) == 0)) { + ctxt->dpkts_tolaptop_pending++; + req->length = 0; + d_req->actual = req->actual; + d_req->status = req->status; + /* Queue zero length packet */ + usb_ep_queue(ctxt->in, req, GFP_ATOMIC); + return; + } + } + + spin_lock_irqsave(&ctxt->lock, flags); + list_add_tail(&req->list, &ctxt->write_pool); + if (req->length != 0) { + d_req->actual = req->actual; + d_req->status = req->status; + } + spin_unlock_irqrestore(&ctxt->lock, flags); + + if (ctxt->ch.notify) { + // zero_pky.patch by jagadish + ctxt->qxdm_ops = 1; + ctxt->ch.notify(ctxt->ch.priv, USB_DIAG_WRITE_DONE, d_req); + } + +} + +static void diag_read_complete(struct usb_ep *ep, + struct usb_request *req) +{ + struct diag_context *ctxt = ep->driver_data; + struct diag_request *d_req = req->context; + unsigned long flags; + + d_req->actual = req->actual; + d_req->status = req->status; + + spin_lock_irqsave(&ctxt->lock, flags); + list_add_tail(&req->list, &ctxt->read_pool); + spin_unlock_irqrestore(&ctxt->lock, flags); + + ctxt->dpkts_tomodem++; + + if (ctxt->ch.notify) { + // zero_pky.patch by jagadish + ctxt->qxdm_ops = 1; + ctxt->ch.notify(ctxt->ch.priv, USB_DIAG_READ_DONE, d_req); + } + +} + +/** + * usb_diag_open() - Open a diag channel over USB + * @name: Name of the channel + * @priv: Private structure pointer which will be passed in notify() + * @notify: Callback function to receive notifications + * + * This function iterates overs the available channels and returns + * the channel handler if the name matches. The notify callback is called + * for CONNECT, DISCONNECT, READ_DONE and WRITE_DONE events. + * + */ +struct usb_diag_ch *usb_diag_open(const char *name, void *priv, + void (*notify)(void *, unsigned, struct diag_request *)) +{ + struct usb_diag_ch *ch; + struct diag_context *ctxt; + unsigned long flags; + int found = 0; + + spin_lock_irqsave(&ch_lock, flags); + /* Check if we already have a channel with this name */ + list_for_each_entry(ch, &usb_diag_ch_list, list) { + if (!strcmp(name, ch->name)) { + found = 1; + break; + } + } + spin_unlock_irqrestore(&ch_lock, flags); + + if (!found) { + ctxt = kzalloc(sizeof(*ctxt), GFP_KERNEL); + if (!ctxt) + return ERR_PTR(-ENOMEM); + + ch = &ctxt->ch; + } + + ch->name = name; + ch->priv = priv; + ch->notify = notify; + + spin_lock_irqsave(&ch_lock, flags); + list_add_tail(&ch->list, &usb_diag_ch_list); + spin_unlock_irqrestore(&ch_lock, flags); + + return ch; +} +EXPORT_SYMBOL(usb_diag_open); + +/** + * usb_diag_close() - Close a diag channel over USB + * @ch: Channel handler + * + * This function closes the diag channel. + * + */ +void usb_diag_close(struct usb_diag_ch *ch) +{ + struct diag_context *dev = container_of(ch, struct diag_context, ch); + unsigned long flags; + + spin_lock_irqsave(&ch_lock, flags); + ch->priv = NULL; + ch->notify = NULL; + /* Free-up the resources if channel is no more active */ + if (!ch->priv_usb) { + list_del(&ch->list); + kfree(dev); + } + + spin_unlock_irqrestore(&ch_lock, flags); +} +EXPORT_SYMBOL(usb_diag_close); + +/** + * usb_diag_free_req() - Free USB requests + * @ch: Channel handler + * + * This function free read and write USB requests for the interface + * associated with this channel. + * + */ +void usb_diag_free_req(struct usb_diag_ch *ch) +{ + struct diag_context *ctxt = ch->priv_usb; + struct usb_request *req; + struct list_head *act, *tmp; + + if (!ctxt) + return; + + list_for_each_safe(act, tmp, &ctxt->write_pool) { + req = list_entry(act, struct usb_request, list); + list_del(&req->list); + usb_ep_free_request(ctxt->in, req); + } + + list_for_each_safe(act, tmp, &ctxt->read_pool) { + req = list_entry(act, struct usb_request, list); + list_del(&req->list); + usb_ep_free_request(ctxt->out, req); + } +} +EXPORT_SYMBOL(usb_diag_free_req); + +/** + * usb_diag_alloc_req() - Allocate USB requests + * @ch: Channel handler + * @n_write: Number of requests for Tx + * @n_read: Number of requests for Rx + * + * This function allocate read and write USB requests for the interface + * associated with this channel. The actual buffer is not allocated. + * The buffer is passed by diag char driver. + * + */ +int usb_diag_alloc_req(struct usb_diag_ch *ch, int n_write, int n_read) +{ + struct diag_context *ctxt = ch->priv_usb; + struct usb_request *req; + int i; + + if (!ctxt) + return -ENODEV; + + for (i = 0; i < n_write; i++) { + req = usb_ep_alloc_request(ctxt->in, GFP_ATOMIC); + if (!req) + goto fail; + req->complete = diag_write_complete; + list_add_tail(&req->list, &ctxt->write_pool); + } + + for (i = 0; i < n_read; i++) { + req = usb_ep_alloc_request(ctxt->out, GFP_ATOMIC); + if (!req) + goto fail; + req->complete = diag_read_complete; + list_add_tail(&req->list, &ctxt->read_pool); + } + + return 0; + +fail: + usb_diag_free_req(ch); + return -ENOMEM; + +} +EXPORT_SYMBOL(usb_diag_alloc_req); + +/** + * usb_diag_read() - Read data from USB diag channel + * @ch: Channel handler + * @d_req: Diag request struct + * + * Enqueue a request on OUT endpoint of the interface corresponding to this + * channel. This function returns proper error code when interface is not + * in configured state, no Rx requests available and ep queue is failed. + * + * This function operates asynchronously. READ_DONE event is notified after + * completion of OUT request. + * + */ +int usb_diag_read(struct usb_diag_ch *ch, struct diag_request *d_req) +{ + struct diag_context *ctxt = ch->priv_usb; + unsigned long flags; + struct usb_request *req; + + if (!ctxt) + return -ENODEV; + + spin_lock_irqsave(&ctxt->lock, flags); + + if (!ctxt->configured) { + spin_unlock_irqrestore(&ctxt->lock, flags); + return -EIO; + } + + if (list_empty(&ctxt->read_pool)) { + spin_unlock_irqrestore(&ctxt->lock, flags); + ERROR(ctxt->cdev, "%s: no requests available\n", __func__); + return -EAGAIN; + } + + req = list_first_entry(&ctxt->read_pool, struct usb_request, list); + list_del(&req->list); + spin_unlock_irqrestore(&ctxt->lock, flags); + + req->buf = d_req->buf; + req->length = d_req->length; + req->context = d_req; + if (usb_ep_queue(ctxt->out, req, GFP_ATOMIC)) { + /* If error add the link to linked list again*/ + spin_lock_irqsave(&ctxt->lock, flags); + list_add_tail(&req->list, &ctxt->read_pool); + spin_unlock_irqrestore(&ctxt->lock, flags); + ERROR(ctxt->cdev, "%s: cannot queue" + " read request\n", __func__); + return -EIO; + } + + return 0; +} +EXPORT_SYMBOL(usb_diag_read); + +/** + * usb_diag_write() - Write data from USB diag channel + * @ch: Channel handler + * @d_req: Diag request struct + * + * Enqueue a request on IN endpoint of the interface corresponding to this + * channel. This function returns proper error code when interface is not + * in configured state, no Tx requests available and ep queue is failed. + * + * This function operates asynchronously. WRITE_DONE event is notified after + * completion of IN request. + * + */ +int usb_diag_write(struct usb_diag_ch *ch, struct diag_request *d_req) +{ + struct diag_context *ctxt = ch->priv_usb; + unsigned long flags; + struct usb_request *req = NULL; + + if (!ctxt) + return -ENODEV; + + spin_lock_irqsave(&ctxt->lock, flags); + + if (!ctxt->configured) { + spin_unlock_irqrestore(&ctxt->lock, flags); + return -EIO; + } + + if (list_empty(&ctxt->write_pool)) { + spin_unlock_irqrestore(&ctxt->lock, flags); + ERROR(ctxt->cdev, "%s: no requests available\n", __func__); + return -EAGAIN; + } + + req = list_first_entry(&ctxt->write_pool, struct usb_request, list); + list_del(&req->list); + spin_unlock_irqrestore(&ctxt->lock, flags); + + req->buf = d_req->buf; + req->length = d_req->length; + req->context = d_req; + if (usb_ep_queue(ctxt->in, req, GFP_ATOMIC)) { + /* If error add the link to linked list again*/ + spin_lock_irqsave(&ctxt->lock, flags); + list_add_tail(&req->list, &ctxt->write_pool); + spin_unlock_irqrestore(&ctxt->lock, flags); + ERROR(ctxt->cdev, "%s: cannot queue" + " read request\n", __func__); + return -EIO; + } + + ctxt->dpkts_tolaptop++; + ctxt->dpkts_tolaptop_pending++; + + return 0; +} +EXPORT_SYMBOL(usb_diag_write); + +static void diag_function_disable(struct usb_function *f) +{ + struct diag_context *dev = func_to_diag(f); + unsigned long flags; + + DBG(dev->cdev, "diag_function_disable\n"); + + spin_lock_irqsave(&dev->lock, flags); + dev->configured = 0; + spin_unlock_irqrestore(&dev->lock, flags); + + // zero_pky.patch by jagadish + if (dev->ch.notify) { + if (dev->qxdm_ops) + dev->ch.notify(dev->ch.priv, USB_DIAG_QXDM_DISCONNECT, NULL); + else + dev->ch.notify(dev->ch.priv, USB_DIAG_DISCONNECT, NULL); + dev->qxdm_ops = 0; + } + + + usb_ep_disable(dev->in); + dev->in->driver_data = NULL; + + usb_ep_disable(dev->out); + dev->out->driver_data = NULL; + +} + +static int diag_function_set_alt(struct usb_function *f, + unsigned intf, unsigned alt) +{ + struct diag_context *dev = func_to_diag(f); + struct usb_composite_dev *cdev = f->config->cdev; + unsigned long flags; + int rc = 0; + + dev->in_desc = ep_choose(cdev->gadget, + (struct usb_endpoint_descriptor *)f->hs_descriptors[1], + (struct usb_endpoint_descriptor *)f->descriptors[1]); + dev->out_desc = ep_choose(cdev->gadget, + (struct usb_endpoint_descriptor *)f->hs_descriptors[2], + (struct usb_endpoint_descriptor *)f->descriptors[2]); + dev->in->driver_data = dev; + rc = usb_ep_enable(dev->in, dev->in_desc); + if (rc) { + ERROR(dev->cdev, "can't enable %s, result %d\n", + dev->in->name, rc); + return rc; + } + dev->out->driver_data = dev; + rc = usb_ep_enable(dev->out, dev->out_desc); + if (rc) { + ERROR(dev->cdev, "can't enable %s, result %d\n", + dev->out->name, rc); + usb_ep_disable(dev->in); + return rc; + } + schedule_work(&dev->config_work); + + dev->dpkts_tolaptop = 0; + dev->dpkts_tomodem = 0; + dev->dpkts_tolaptop_pending = 0; + + spin_lock_irqsave(&dev->lock, flags); + dev->configured = 1; + spin_unlock_irqrestore(&dev->lock, flags); + + return rc; +} + +static void diag_function_unbind(struct usb_configuration *c, + struct usb_function *f) +{ + struct diag_context *ctxt = func_to_diag(f); + + if (gadget_is_dualspeed(c->cdev->gadget)) + usb_free_descriptors(f->hs_descriptors); + + usb_free_descriptors(f->descriptors); + ctxt->ch.priv_usb = NULL; +} + +static int diag_function_bind(struct usb_configuration *c, + struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct diag_context *ctxt = func_to_diag(f); + struct usb_ep *ep; + int status = -ENODEV; + + intf_desc.bInterfaceNumber = usb_interface_id(c, f); + + ep = usb_ep_autoconfig(cdev->gadget, &fs_bulk_in_desc); + if (!ep) + goto fail; + ctxt->in = ep; + ep->driver_data = ctxt; + + ep = usb_ep_autoconfig(cdev->gadget, &fs_bulk_out_desc); + if (!ep) + goto fail; + ctxt->out = ep; + ep->driver_data = ctxt; + + /* copy descriptors, and track endpoint copies */ + f->descriptors = usb_copy_descriptors(fs_diag_desc); + if (!f->descriptors) + goto fail; + + if (gadget_is_dualspeed(c->cdev->gadget)) { + hs_bulk_in_desc.bEndpointAddress = + fs_bulk_in_desc.bEndpointAddress; + hs_bulk_out_desc.bEndpointAddress = + fs_bulk_out_desc.bEndpointAddress; + + /* copy descriptors, and track endpoint copies */ + f->hs_descriptors = usb_copy_descriptors(hs_diag_desc); + } + return 0; +fail: + if (ctxt->out) + ctxt->out->driver_data = NULL; + if (ctxt->in) + ctxt->in->driver_data = NULL; + return status; + +} + +int diag_function_add(struct usb_configuration *c, const char *name, + int (*update_pid)(uint32_t, const char *)) +{ + struct diag_context *dev; + struct usb_diag_ch *_ch; + int found = 0, ret; + + DBG(c->cdev, "diag_function_add\n"); + + list_for_each_entry(_ch, &usb_diag_ch_list, list) { + if (!strcmp(name, _ch->name)) { + found = 1; + break; + } + } + if (!found) { + ERROR(c->cdev, "usb: unable to get diag usb channel\n"); + + return -ENODEV; + } + + dev = container_of(_ch, struct diag_context, ch); + /* claim the channel for this USB interface */ + _ch->priv_usb = dev; + + dev->update_pid_and_serial_num = update_pid; + dev->cdev = c->cdev; + dev->function.name = _ch->name; + dev->function.descriptors = fs_diag_desc; + dev->function.hs_descriptors = hs_diag_desc; + dev->function.bind = diag_function_bind; + dev->function.unbind = diag_function_unbind; + dev->function.set_alt = diag_function_set_alt; + dev->function.disable = diag_function_disable; + spin_lock_init(&dev->lock); + INIT_LIST_HEAD(&dev->read_pool); + INIT_LIST_HEAD(&dev->write_pool); + INIT_WORK(&dev->config_work, usb_config_work_func); + + ret = usb_add_function(c, &dev->function); + if (ret) { + INFO(c->cdev, "usb_add_function failed\n"); + _ch->priv_usb = NULL; + } + + return ret; +} + + +#if defined(CONFIG_DEBUG_FS) +static char debug_buffer[PAGE_SIZE]; + +static ssize_t debug_read_stats(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + char *buf = debug_buffer; + int temp = 0; + struct usb_diag_ch *ch; + + list_for_each_entry(ch, &usb_diag_ch_list, list) { + struct diag_context *ctxt; + + ctxt = ch->priv_usb; + + temp += scnprintf(buf + temp, PAGE_SIZE - temp, + "---Name: %s---\n" + "endpoints: %s, %s\n" + "dpkts_tolaptop: %lu\n" + "dpkts_tomodem: %lu\n" + "pkts_tolaptop_pending: %u\n", + ch->name, + ctxt->in->name, ctxt->out->name, + ctxt->dpkts_tolaptop, + ctxt->dpkts_tomodem, + ctxt->dpkts_tolaptop_pending); + } + + return simple_read_from_buffer(ubuf, count, ppos, buf, temp); +} + +static ssize_t debug_reset_stats(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct usb_diag_ch *ch; + + list_for_each_entry(ch, &usb_diag_ch_list, list) { + struct diag_context *ctxt; + + ctxt = ch->priv_usb; + + ctxt->dpkts_tolaptop = 0; + ctxt->dpkts_tomodem = 0; + ctxt->dpkts_tolaptop_pending = 0; + } + + return count; +} + +static int debug_open(struct inode *inode, struct file *file) +{ + return 0; +} + +static const struct file_operations debug_fdiag_ops = { + .open = debug_open, + .read = debug_read_stats, + .write = debug_reset_stats, +}; + +struct dentry *dent_diag; +static void fdiag_debugfs_init(void) +{ + dent_diag = debugfs_create_dir("usb_diag", 0); + if (IS_ERR(dent_diag)) + return; + + debugfs_create_file("status", 0444, dent_diag, 0, &debug_fdiag_ops); +} +#else +static void fdiag_debugfs_init(void) +{ + return; +} +#endif + +static void diag_cleanup(void) +{ + struct diag_context *dev; + struct list_head *act, *tmp; + struct usb_diag_ch *_ch; + unsigned long flags; + + debugfs_remove_recursive(dent_diag); + + list_for_each_safe(act, tmp, &usb_diag_ch_list) { + _ch = list_entry(act, struct usb_diag_ch, list); + dev = container_of(_ch, struct diag_context, ch); + + spin_lock_irqsave(&ch_lock, flags); + /* Free if diagchar is not using the channel anymore */ + if (!_ch->priv) { + list_del(&_ch->list); + kfree(dev); + } + spin_unlock_irqrestore(&ch_lock, flags); + } +} + +static int diag_setup(void) +{ + fdiag_debugfs_init(); + + return 0; +} diff --git a/drivers/usb/gadget/f_diag.h b/drivers/usb/gadget/f_diag.h new file mode 100644 index 0000000..82d9a25 --- /dev/null +++ b/drivers/usb/gadget/f_diag.h @@ -0,0 +1,24 @@ +/* drivers/usb/gadget/f_diag.h + * + * Diag Function Device - Route DIAG frames between SMD and USB + * + * Copyright (C) 2008-2009 Google, Inc. + * Copyright (c) 2009, Code Aurora Forum. All rights reserved. + * Author: Brian Swetland <swetland@google.com> + * 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. + * + */ +#ifndef __F_DIAG_H +#define __F_DIAG_H + +int diag_function_add(struct usb_configuration *c, const char *); + +#endif /* __F_DIAG_H */ + diff --git a/drivers/usb/gadget/f_mass_storage.c b/drivers/usb/gadget/f_mass_storage.c index 92b7736..ec1d520 100644 --- a/drivers/usb/gadget/f_mass_storage.c +++ b/drivers/usb/gadget/f_mass_storage.c @@ -297,6 +297,11 @@ #include "gadget_chips.h" +#ifdef CONFIG_USB_CDFS_SUPPORT +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE +#define _SUPPORT_MAC_ /* support to recognize CDFS on OSX (MAC PC) */ +#endif +#endif /*------------------------------------------------------------------------*/ @@ -456,6 +461,62 @@ struct fsg_dev { struct usb_ep *bulk_out; }; +#ifdef CONFIG_USB_CDFS_SUPPORT +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE +static int send_message(struct fsg_common *common, char *msg) +{ + char name_buf[120]; + char state_buf[120]; + char *envp[3]; + int env_offset = 0; + struct usb_gadget *gadget = common->gadget; + + DBG(common, "%s called\n", __func__); + printk(KERN_INFO "%s (%s)\n", __func__, msg); + + if (gadget) { + snprintf(name_buf, sizeof(name_buf), + "SWITCH_NAME=USB_MESSAGE"); + envp[env_offset++] = name_buf; + + snprintf(state_buf, sizeof(state_buf), + "SWITCH_STATE=%s", msg); + envp[env_offset++] = state_buf; + + envp[env_offset] = NULL; + + if (!gadget->dev.class) { + gadget->dev.class = class_create(THIS_MODULE, + "usb_msg"); + if (IS_ERR(gadget->dev.class)) + return -1; + } + + DBG(common, "Send cd eject message to daemon\n"); + + kobject_uevent_env(&gadget->dev.kobj, KOBJ_CHANGE, envp); + } + + return 0; +} + +static int do_autorun_check(struct fsg_common *common) +{ + printk(KERN_INFO "%s called\n", __func__); + send_message(common, "autorun"); + + return 0; +} + +static int do_switch_atmode(struct fsg_common *common) +{ + printk(KERN_INFO "%s called\n", __func__); + send_message(common, "Load AT"); + + return 0; +} +#endif /* CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE */ +#endif static inline int __fsg_is_set(struct fsg_common *common, const char *func, unsigned line) { @@ -738,6 +799,243 @@ static int sleep_thread(struct fsg_common *common) return rc; } +#ifdef CONFIG_USB_CDFS_SUPPORT +#ifdef _SUPPORT_MAC_ +static void _lba_to_msf(u8 *buf, int lba) +{ + lba += 150; + buf[0] = (lba / 75) / 60; + buf[1] = (lba / 75) % 60; + buf[2] = lba % 75; +} + +static int _read_toc_raw(struct fsg_common *common, struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = common->curlun; + int msf = common->cmnd[1] & 0x02; + u8 *buf = (u8 *) bh->buf; + + u8 *q; + int len; + + q = buf + 2; + *q++ = 1; /* first session */ + *q++ = 1; /* last session */ + + *q++ = 1; /* session number */ + *q++ = 0x14; /* data track */ + *q++ = 0; /* track number */ + *q++ = 0xa0; /* lead-in */ + *q++ = 0; /* min */ + *q++ = 0; /* sec */ + *q++ = 0; /* frame */ + *q++ = 0; + *q++ = 1; /* first track */ + *q++ = 0x00; /* disk type */ + *q++ = 0x00; + + *q++ = 1; /* session number */ + *q++ = 0x14; /* data track */ + *q++ = 0; /* track number */ + *q++ = 0xa1; + *q++ = 0; /* min */ + *q++ = 0; /* sec */ + *q++ = 0; /* frame */ + *q++ = 0; + *q++ = 1; /* last track */ + *q++ = 0x00; + *q++ = 0x00; + + *q++ = 1; /* session number */ + *q++ = 0x14; /* data track */ + *q++ = 0; /* track number */ + *q++ = 0xa2; /* lead-out */ + *q++ = 0; /* min */ + *q++ = 0; /* sec */ + *q++ = 0; /* frame */ + if (msf) { + *q++ = 0; /* reserved */ + _lba_to_msf(q, curlun->num_sectors); + q += 3; + } else { + put_unaligned_be32(curlun->num_sectors, q); + q += 4; + } + + *q++ = 1; /* session number */ + *q++ = 0x14; /* ADR, control */ + *q++ = 0; /* track number */ + *q++ = 1; /* point */ + *q++ = 0; /* min */ + *q++ = 0; /* sec */ + *q++ = 0; /* frame */ + if (msf) { + *q++ = 0; + _lba_to_msf(q, 0); + q += 3; + } else { + *q++ = 0; + *q++ = 0; + *q++ = 0; + *q++ = 0; + } + + len = q - buf; + put_unaligned_be16(len - 2, buf); + + return len; +} + +static void cd_data_to_raw(u8 *buf, int lba) +{ + /* sync bytes */ + buf[0] = 0x00; + memset(buf + 1, 0xff, 10); + buf[11] = 0x00; + buf += 12; + /* MSF */ + _lba_to_msf(buf, lba); + buf[3] = 0x01; /* mode 1 data */ + buf += 4; + /* data */ + buf += 2048; + /* XXX: ECC not computed */ + memset(buf, 0, 288); +} + +static int do_read_cd(struct fsg_common *common) +{ + struct fsg_lun *curlun = common->curlun; + u32 lba; + struct fsg_buffhd *bh; + int rc; + u32 amount_left; + loff_t file_offset, file_offset_tmp; + unsigned int amount; + unsigned int partial_page; + ssize_t nread; + + u32 nb_sectors, transfer_request; + + nb_sectors = (common->cmnd[6] << 16) | + (common->cmnd[7] << 8) | common->cmnd[8]; + lba = get_unaligned_be32(&common->cmnd[2]); + + if (nb_sectors == 0) + return 0; + + if (lba >= curlun->num_sectors) { + curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + return -EINVAL; + } + + transfer_request = common->cmnd[9]; + if ((transfer_request & 0xf8) == 0xf8) { + file_offset = ((loff_t) lba) << 11; + /* read all data - 2352 byte */ + amount_left = 2352; + } else { + file_offset = ((loff_t) lba) << 9; + /* Carry out the file reads */ + amount_left = common->data_size_from_cmnd; + } + + if (unlikely(amount_left == 0)) + return -EIO; /* No default reply */ + + for (;;) { + + /* Figure out how much we need to read: + * Try to read the remaining amount. + * But don't read more than the buffer size. + * And don't try to read past the end of the file. + * Finally, if we're not at a page boundary, don't read past + * the next page. + * If this means reading 0 then we were asked to read past + * the end of file. */ + amount = min(amount_left, FSG_BUFLEN); + amount = min((loff_t) amount, + curlun->file_length - file_offset); + partial_page = file_offset & (PAGE_CACHE_SIZE - 1); + if (partial_page > 0) + amount = min(amount, (unsigned int) PAGE_CACHE_SIZE - + partial_page); + + /* Wait for the next buffer to become available */ + bh = common->next_buffhd_to_fill; + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(common); + if (rc) + return rc; + } + + /* If we were asked to read past the end of file, + * end with an empty buffer. */ + if (amount == 0) { + curlun->sense_data = + SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + curlun->sense_data_info = file_offset >> 9; + curlun->info_valid = 1; + bh->inreq->length = 0; + bh->state = BUF_STATE_FULL; + break; + } + + /* Perform the read */ + file_offset_tmp = file_offset; + if ((transfer_request & 0xf8) == 0xf8) { + nread = vfs_read(curlun->filp, + ((char __user *)bh->buf)+16, + amount, &file_offset_tmp); + } else { + nread = vfs_read(curlun->filp, + (char __user *)bh->buf, + amount, &file_offset_tmp); + } + VLDBG(curlun, "file read %u @ %llu -> %d\n", amount, + (unsigned long long) file_offset, + (int) nread); + if (signal_pending(current)) + return -EINTR; + + if (nread < 0) { + LDBG(curlun, "error in file read: %d\n", + (int) nread); + nread = 0; + } else if (nread < amount) { + LDBG(curlun, "partial file read: %d/%u\n", + (int) nread, amount); + nread -= (nread & 511); /* Round down to a block */ + } + file_offset += nread; + amount_left -= nread; + common->residue -= nread; + bh->inreq->length = nread; + bh->state = BUF_STATE_FULL; + + /* If an error occurred, report it and its position */ + if (nread < amount) { + curlun->sense_data = SS_UNRECOVERED_READ_ERROR; + curlun->sense_data_info = file_offset >> 9; + curlun->info_valid = 1; + break; + } + + if (amount_left == 0) + break; /* No more left to read */ + + /* Send this buffer and go read some more */ + start_in_transfer(common, bh); + common->next_buffhd_to_fill = bh->next; + } + + if ((transfer_request & 0xf8) == 0xf8) + cd_data_to_raw(bh->buf, lba); + + return -EIO; /* No default reply */ +} +#endif /* _SUPPORT_MAC_ */ +#endif /*-------------------------------------------------------------------------*/ @@ -1397,13 +1695,22 @@ static int do_read_toc(struct fsg_common *common, struct fsg_buffhd *bh) int msf = common->cmnd[1] & 0x02; int start_track = common->cmnd[6]; u8 *buf = (u8 *)bh->buf; - +#if defined(CONFIG_USB_CDFS_SUPPORT) +#ifdef _SUPPORT_MAC_ + int format = (common->cmnd[9] & 0xC0) >> 6; +#endif +#endif if ((common->cmnd[1] & ~0x02) != 0 || /* Mask away MSF */ start_track > 1) { curlun->sense_data = SS_INVALID_FIELD_IN_CDB; return -EINVAL; } - +#if defined(CONFIG_USB_CDFS_SUPPORT) +#ifdef _SUPPORT_MAC_ + if (format == 2) + return _read_toc_raw(common, bh); +#endif +#endif memset(buf, 0, 20); buf[1] = (20-2); /* TOC data length */ buf[2] = 1; /* First track number */ @@ -1528,6 +1835,12 @@ static int do_start_stop(struct fsg_common *common) * available for use as soon as it is loaded. */ if (start) { +#if defined(CONFIG_USB_CDFS_SUPPORT) +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + if (loej) + send_message(common, "Load AT"); +#endif +#endif if (!fsg_lun_is_open(curlun)) { curlun->sense_data = SS_MEDIUM_NOT_PRESENT; return -EINVAL; @@ -1561,6 +1874,12 @@ static int do_start_stop(struct fsg_common *common) up_write(&common->filesem); down_read(&common->filesem); +#if defined(CONFIG_USB_CDFS_SUPPORT) +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + send_message(common, "Load User"); +#endif +#endif + return common->ops && common->ops->post_eject ? min(0, common->ops->post_eject(common, curlun, curlun - common->luns)) @@ -2153,7 +2472,16 @@ static int do_scsi_command(struct fsg_common *common) common->data_size_from_cmnd = get_unaligned_be16(&common->cmnd[7]); reply = check_command(common, 10, DATA_DIR_TO_HOST, +#if defined(CONFIG_USB_CDFS_SUPPORT) +#ifdef _SUPPORT_MAC_ + (0xf<<6) | (1<<1), 1, +#else (7<<6) | (1<<1), 1, +#endif +#else + (7<<6) | (1<<1), 1, +#endif + "READ TOC"); if (reply == 0) reply = do_read_toc(common, bh); @@ -2246,15 +2574,43 @@ static int do_scsi_command(struct fsg_common *common) reply = do_write(common); break; - /* - * Some mandatory commands that we recognize but don't implement. +#if defined(CONFIG_USB_CDFS_SUPPORT) +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + case RELEASE: /* SC_AUTORUN_CHECK0 : 0x17 */ + reply = do_switch_atmode(common); + break; + + case RESERVE: /* SC_AUTORUN_CHECK1 : 0x16 */ + reply = do_autorun_check(common); + break; + +#ifdef _SUPPORT_MAC_ + case READ_CD: + common->data_size_from_cmnd = ((common->cmnd[6] << 16) + | (common->cmnd[7] << 8) + | (common->cmnd[8])) << 9; + reply = check_command(common, 12, DATA_DIR_TO_HOST, + (0xf<<2) | (7<<7), 1, + "READ CD"); + if (reply == 0) + reply = do_read_cd(common); + break; + +#endif /* _SUPPORT_MAC_ */ +#endif +#endif + /* Some mandatory commands that we recognize but don't implement. * They don't mean much in this setting. It's left as an exercise * for anyone interested to implement RESERVE and RELEASE in terms * of Posix locks. */ case FORMAT_UNIT: +#ifndef CONFIG_USB_CDFS_SUPPORT +#ifndef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE case RELEASE: case RESERVE: +#endif +#endif case SEND_DIAGNOSTIC: /* Fall through */ diff --git a/drivers/usb/gadget/f_mtp.h b/drivers/usb/gadget/f_mtp.h index 5892e3f..00a30b7 100644 --- a/drivers/usb/gadget/f_mtp.h +++ b/drivers/usb/gadget/f_mtp.h @@ -31,6 +31,7 @@ #define SEND_RESET_ACK 8 #define SET_ZLP_DATA 9 #define GET_HIGH_FULL_SPEED 10 +#define SEND_FILE_WITH_HEADER 11 #define SIG_SETUP 44 /*PIMA15740-2000 spec*/ @@ -53,4 +54,20 @@ struct usb_mtp_ctrlrequest { struct usb_ctrlrequest setup; }; + +struct usb_container_header { + uint32_t Length;/* the valid size, in BYTES, of the container */ + uint16_t Type;/* Container type */ + uint16_t Code;/* Operation code, response code, or Event code */ + uint32_t TransactionID;/* host generated number */ +}; + +struct read_send_info { + int Fd;/* Media File fd */ + uint64_t Length;/* the valid size, in BYTES, of the container */ + uint16_t Code;/* Operation code, response code, or Event code */ + uint32_t TransactionID;/* host generated number */ +}; + + #endif /* __F_MTP_H */ diff --git a/drivers/usb/gadget/f_mtp_samsung.c b/drivers/usb/gadget/f_mtp_samsung.c index 7f0dec8..304eca3 100644 --- a/drivers/usb/gadget/f_mtp_samsung.c +++ b/drivers/usb/gadget/f_mtp_samsung.c @@ -93,12 +93,12 @@ #endif /*-------------------------------------------------------------------------*/ -#define MTPG_BULK_BUFFER_SIZE 4096 +#define MTPG_BULK_BUFFER_SIZE 32768 #define MTPG_INTR_BUFFER_SIZE 28 /* number of rx and tx requests to allocate */ -#define MTPG_RX_REQ_MAX 4 -#define MTPG_MTPG_TX_REQ_MAX 4 +#define MTPG_RX_REQ_MAX 8 +#define MTPG_MTPG_TX_REQ_MAX 8 #define MTPG_INTR_REQ_MAX 5 /* ID for Microsoft MTP OS String */ @@ -137,13 +137,24 @@ struct mtpg_dev { struct usb_ep *bulk_in; struct usb_ep *bulk_out; struct usb_ep *int_in; + struct usb_request *notify_req; + struct workqueue_struct *wq; + struct work_struct read_send_work; + struct file *read_send_file; + + int64_t read_send_length; + + uint16_t read_send_cmd; + uint32_t read_send_id; + int read_send_result; atomic_t read_excl; atomic_t write_excl; atomic_t ioctl_excl; atomic_t open_excl; atomic_t wintfd_excl; char cancel_io_buf[USB_PTPREQUEST_CANCELIO_SIZE+1]; + int cancel_io; }; /* Global mtpg_dev Structure @@ -437,13 +448,14 @@ static int mtp_send_signal(int value) info.si_int = value; rcu_read_lock(); - if (!current->nsproxy) { + if (!current->nsproxy) { printk(KERN_DEBUG "process has gone\n"); rcu_read_unlock(); return -ENODEV; } t = pid_task(find_vpid(mtp_pid), PIDTYPE_PID); + if (t == NULL) { printk(KERN_DEBUG "no such pid\n"); rcu_read_unlock(); @@ -753,6 +765,108 @@ static ssize_t interrupt_write(struct file *fd, return ret; } +static void read_send_work(struct work_struct *work) +{ + struct mtpg_dev *dev = container_of(work, struct mtpg_dev, + read_send_work); + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req = 0; + struct usb_container_header *hdr; + struct file *file; + loff_t file_pos = 0; + int64_t count = 0; + int xfer = 0; + int ret = -1; + int hdr_length = 0; + int r = 0; + int ZLP_flag = 0; + + /* read our parameters */ + smp_rmb(); + file = dev->read_send_file; + count = dev->read_send_length; + hdr_length = sizeof(struct usb_container_header); + count += hdr_length; + + printk(KERN_DEBUG "[%s:%d] offset=[%lld]\t leth+hder=[%lld]\n", + __func__, __LINE__, file_pos, count); + + /* Zero Length Packet should be sent if the last trasfer + * size is equals to the max packet size. + */ + if ((count & (dev->bulk_in->maxpacket - 1)) == 0) + ZLP_flag = 1; + + while (count > 0 || ZLP_flag) { + /*Breaking the loop after sending Zero Length Packet*/ + if (count == 0) + ZLP_flag = 0; + + if (dev->cancel_io == 1) { + dev->cancel_io = 0; /*reported to user space*/ + r = -EIO; + printk(KERN_DEBUG "[%s]\t%d ret = %d\n", + __func__, __LINE__, r); + break; + } + /* get an idle tx request to use */ + req = 0; + ret = wait_event_interruptible(dev->write_wq, + ((req = mtpg_req_get(dev, &dev->tx_idle)) + || dev->error)); + if (ret < 0) { + r = ret; + printk(KERN_DEBUG "[%s]\t%d ret = %d\n", + __func__, __LINE__, r); + break; + } + + if (count > MTPG_BULK_BUFFER_SIZE) + xfer = MTPG_BULK_BUFFER_SIZE; + else + xfer = count; + + if (hdr_length) { + hdr = (struct usb_container_header *)req->buf; + hdr->Length = __cpu_to_le32(count); + hdr->Type = __cpu_to_le16(2); + hdr->Code = __cpu_to_le16(dev->read_send_cmd); + hdr->TransactionID = __cpu_to_le32(dev->read_send_id); + } + + ret = vfs_read(file, req->buf + hdr_length, + xfer - hdr_length, &file_pos); + if (ret < 0) { + r = ret; + break; + } + xfer = ret + hdr_length; + hdr_length = 0; + + req->length = xfer; + ret = usb_ep_queue(dev->bulk_in, req, GFP_KERNEL); + if (ret < 0) { + dev->error = 1; + r = -EIO; + printk(KERN_DEBUG "[%s]\t%d ret = %d\n", + __func__, __LINE__, r); + break; + } + + count -= xfer; + + req = 0; + } + + if (req) + mtpg_req_put(dev, &dev->tx_idle, req); + + DEBUG_MTPB("[%s] \tline = [%d] \t r = [%d]\n", __func__, __LINE__, r); + + dev->read_send_result = r; + smp_wmb(); +} + static long mtpg_ioctl(struct file *fd, unsigned int code, unsigned long arg) { struct mtpg_dev *dev = fd->private_data; @@ -781,7 +895,7 @@ static long mtpg_ioctl(struct file *fd, unsigned int code, unsigned long arg) switch (code) { case MTP_ONLY_ENABLE: printk(KERN_DEBUG "[%s:%d] MTP_ONLY_ENABLE ioctl:\n", - __func__, __LINE__); + __func__, __LINE__); if (dev->cdev && dev->cdev->gadget) { usb_gadget_disconnect(cdev->gadget); printk(KERN_DEBUG "[%s:%d] B4 disconectng gadget\n", @@ -789,7 +903,7 @@ static long mtpg_ioctl(struct file *fd, unsigned int code, unsigned long arg) msleep(20); usb_gadget_connect(cdev->gadget); printk(KERN_DEBUG "[%s:%d] after usb_gadget_connect\n", - __func__, __LINE__); + __func__, __LINE__); } status = 10; printk(KERN_DEBUG "[%s:%d] MTP_ONLY_ENABLE clearing error 0\n", @@ -910,9 +1024,48 @@ static long mtpg_ioctl(struct file *fd, unsigned int code, unsigned long arg) else status = 512; break; + case SEND_FILE_WITH_HEADER: + { + struct read_send_info info; + struct work_struct *work; + struct file *file = NULL; + printk(KERN_DEBUG "[%s]SEND_FILE_WITH_HEADER line=[%d]\n", + __func__, __LINE__); + + if (copy_from_user(&info, (void __user *)arg, sizeof(info))) { + status = -EFAULT; + goto exit; + } + + file = fget(info.Fd); + if (!file) { + status = -EBADF; + printk(KERN_DEBUG "[%s] line=[%d] bad file number\n", + __func__, __LINE__); + goto exit; + } + + dev->read_send_file = file; + dev->read_send_length = info.Length; + smp_wmb(); + + work = &dev->read_send_work; + dev->read_send_cmd = info.Code; + dev->read_send_id = info.TransactionID; + queue_work(dev->wq, work); + /* Wait for the work to be complted on work queue */ + flush_workqueue(dev->wq); + + fput(file); + + smp_rmb(); + status = dev->read_send_result; + break; + } default: status = -ENOTTY; } +exit: return status; } @@ -1138,7 +1291,6 @@ mtpg_function_bind(struct usb_configuration *c, struct usb_function *f) fs_mtpg_out_desc.bEndpointAddress; int_hs_notify_desc.bEndpointAddress = int_fs_notify_desc.bEndpointAddress; - } mtpg->cdev = cdev; @@ -1208,6 +1360,7 @@ static int mtpg_function_set_alt(struct usb_function *f, dev->online = 1; dev->error = 0; dev->read_ready = 1; + dev->cancel_io = 0; /* readers may be blocked waiting for us to go online */ wake_up(&dev->read_wq); @@ -1259,6 +1412,7 @@ mtp_complete_cancel_io(struct usb_ep *ep, struct usb_request *req) memset(dev->cancel_io_buf, 0, USB_PTPREQUEST_CANCELIO_SIZE+1); memcpy(dev->cancel_io_buf, req->buf, USB_PTPREQUEST_CANCELIO_SIZE); + dev->cancel_io = 1; /*Debugging*/ for (i = 0; i < USB_PTPREQUEST_CANCELIO_SIZE; i++) DEBUG_MTPB("[%s]cancel_io_buf[%d]=%x\tline = [%d]\n", @@ -1299,7 +1453,8 @@ static int mtp_ctrlrequest(struct usb_composite_dev *cdev, } return value; } else if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_VENDOR) { - if (ctrl->bRequest == 1 + if ((ctrl->bRequest == 1 || ctrl->bRequest == 0x54 || + ctrl->bRequest == 0x6F || ctrl->bRequest == 0xFE) && (ctrl->bRequestType & USB_DIR_IN) && (w_index == 4 || w_index == 5)) { value = (w_length < sizeof(mtpg_ext_config_desc) ? @@ -1443,6 +1598,14 @@ static int mtp_setup(void) INIT_LIST_HEAD(&mtpg->rx_done); INIT_LIST_HEAD(&mtpg->tx_idle); INIT_LIST_HEAD(&mtpg->intr_idle); + mtpg->wq = create_singlethread_workqueue("mtp_read_send"); + if (!mtpg->wq) { + printk(KERN_ERR "mtpg_dev_alloc work queue creation failed\n"); + rc = -ENOMEM; + goto err_work; + } + + INIT_WORK(&mtpg->read_send_work, read_send_work); /* the_mtpg must be set before calling usb_gadget_register_driver */ the_mtpg = mtpg; @@ -1454,7 +1617,7 @@ static int mtp_setup(void) } return 0; - +err_work: err_misc_register: the_mtpg = NULL; kfree(mtpg); diff --git a/drivers/usb/gadget/multi_config.c b/drivers/usb/gadget/multi_config.c index 2e4f52e..95f2ac3 100644 --- a/drivers/usb/gadget/multi_config.c +++ b/drivers/usb/gadget/multi_config.c @@ -258,3 +258,13 @@ void set_string_mode(u16 w_length) stringMode = OTHER_REQUEST; } } + +/* Description : Get Host OS type + * Return value : type - u16 + * - 0 : MAC PC + * - 1 : Windows and Linux PC + */ +u16 get_host_os_type(void) +{ + return stringMode; +} diff --git a/drivers/usb/gadget/multi_config.h b/drivers/usb/gadget/multi_config.h index 792bbae..cc944ad 100644 --- a/drivers/usb/gadget/multi_config.h +++ b/drivers/usb/gadget/multi_config.h @@ -132,4 +132,11 @@ void set_interface_count(struct usb_configuration *config, * - Windows and Linux PC always request 255 size. */ void set_string_mode(u16 w_length); + +/* Description : Get Host OS type + * Return value : type - u16 + * - 0 : MAC PC + * - 1 : Windows and Linux PC + */ +u16 get_host_os_type(void); #endif /* __MULTI_CONFIG_H */ diff --git a/drivers/usb/gadget/s3c_udc.h b/drivers/usb/gadget/s3c_udc.h index a75f8f2..c6bf986 100644 --- a/drivers/usb/gadget/s3c_udc.h +++ b/drivers/usb/gadget/s3c_udc.h @@ -142,7 +142,11 @@ struct s3c_udc { unsigned req_pending:1, req_std:1, req_config:1; struct wake_lock usbd_wake_lock; struct wake_lock usb_cb_wake_lock; + int softconnect; int udc_enabled; + int is_usb_ready; + struct delayed_work usb_ready_work; + struct mutex mutex; }; extern struct s3c_udc *the_controller; diff --git a/drivers/usb/gadget/s3c_udc_otg.c b/drivers/usb/gadget/s3c_udc_otg.c index 6703973..a4f33cd 100644 --- a/drivers/usb/gadget/s3c_udc_otg.c +++ b/drivers/usb/gadget/s3c_udc_otg.c @@ -218,6 +218,21 @@ udc_proc_read(char *page, char **start, off_t off, int count, #include "s3c_udc_otg_xfer_dma.c" /* +* udc_core_disconnect +* Ask On Connection - Vzw requirement +*/ +static void udc_core_disconect(struct s3c_udc *dev) +{ + u32 uTemp; + + printk(KERN_DEBUG "usb: %s -dev->softconnect=%d\n", + __func__, dev->softconnect); + uTemp = __raw_readl(dev->regs + S3C_UDC_OTG_DCTL); + uTemp |= SOFT_DISCONNECT; + __raw_writel(uTemp, dev->regs + S3C_UDC_OTG_DCTL); +} + +/* * udc_disable - disable USB device controller */ static void udc_disable(struct s3c_udc *dev) @@ -307,6 +322,18 @@ int s3c_vbus_enable(struct usb_gadget *gadget, int is_active) { unsigned long flags; struct s3c_udc *dev = container_of(gadget, struct s3c_udc, gadget); + mutex_lock(&dev->mutex); + + if (dev->is_usb_ready) { + printk(KERN_DEBUG "usb: %s, ready u_e: %d, is_active: %d\n", + __func__, dev->udc_enabled, is_active); + } else { /* USB is not ready to enable USB PHY */ + printk(KERN_DEBUG "usb: %s, not ready u_e: %d, is_active: %d\n", + __func__, dev->udc_enabled, is_active); + dev->udc_enabled = is_active; + mutex_unlock(&dev->mutex); + return 0; + } if (dev->udc_enabled != is_active) { dev->udc_enabled = is_active; @@ -324,11 +351,14 @@ int s3c_vbus_enable(struct usb_gadget *gadget, int is_active) wake_lock_timeout(&dev->usbd_wake_lock, HZ * 5); wake_lock_timeout(&dev->usb_cb_wake_lock, HZ * 5); } else { - printk(KERN_DEBUG "usb: %s is_active=%d(udc_enable)\n", - __func__, is_active); + printk(KERN_DEBUG "usb: %s is_active=%d(udc_enable)," + "softconnect=%d\n", + __func__, is_active, dev->softconnect); wake_lock(&dev->usb_cb_wake_lock); udc_reinit(dev); udc_enable(dev); + if (!dev->softconnect) + udc_core_disconect(dev); } } else { printk(KERN_DEBUG "usb: %s, udc_enabled : %d, is_active : %d\n", @@ -336,7 +366,7 @@ int s3c_vbus_enable(struct usb_gadget *gadget, int is_active) } - + mutex_unlock(&dev->mutex); return 0; } @@ -894,6 +924,9 @@ static void s3c_udc_soft_disconnect(void) static int s3c_udc_pullup(struct usb_gadget *gadget, int is_on) { + struct s3c_udc *dev = container_of(gadget, struct s3c_udc, gadget); + dev->softconnect = is_on; + if (is_on) s3c_udc_soft_connect(); else @@ -1157,6 +1190,27 @@ static struct s3c_udc memory = { }, }; +static void usb_ready(struct work_struct *work) +{ + struct s3c_udc *dev = + container_of(work, struct s3c_udc, usb_ready_work.work); + + if (!dev) { + printk(KERN_DEBUG "usb: %s dev is NULL\n", __func__); + return ; + } + + printk(KERN_DEBUG "usb: %s udc_enable=%d\n", + __func__, dev->udc_enabled); + + dev->is_usb_ready = true; + + if (dev->udc_enabled) { + dev->udc_enabled = 0; + s3c_vbus_enable(&dev->gadget, 1); + } +} + /* * probe - binds to the platform device */ @@ -1262,6 +1316,10 @@ static int s3c_udc_probe(struct platform_device *pdev) create_proc_files(); + INIT_DELAYED_WORK(&dev->usb_ready_work, usb_ready); + schedule_delayed_work(&dev->usb_ready_work, msecs_to_jiffies(15000)); + mutex_init(&dev->mutex); + return retval; err_clk: clk_put(dev->clk); @@ -1298,6 +1356,8 @@ static int s3c_udc_remove(struct platform_device *pdev) the_controller = 0; wake_lock_destroy(&dev->usbd_wake_lock); wake_lock_destroy(&dev->usb_cb_wake_lock); + cancel_delayed_work(&dev->usb_ready_work); + mutex_destroy(&dev->mutex); return 0; } diff --git a/drivers/usb/gadget/s3c_udc_otg_xfer_dma.c b/drivers/usb/gadget/s3c_udc_otg_xfer_dma.c index 7554772..97fc349 100644 --- a/drivers/usb/gadget/s3c_udc_otg_xfer_dma.c +++ b/drivers/usb/gadget/s3c_udc_otg_xfer_dma.c @@ -52,7 +52,7 @@ static u8 test_pkt[TEST_PKT_SIZE] __attribute__((aligned(8))) = { static void s3c_udc_ep_set_stall(struct s3c_ep *ep); -#if defined(CONFIG_BATTERY_SAMSUNG) || defined(CONFIG_BATTERY_SAMSUNG_S2PLUS) +#if defined(CONFIG_BATTERY_SAMSUNG) u32 cable_connected; void s3c_udc_cable_connect(struct s3c_udc *dev) @@ -559,7 +559,7 @@ static irqreturn_t s3c_udc_irq(int irq, void *_dev) spin_lock(&dev->lock); } -#if defined(CONFIG_BATTERY_SAMSUNG) || defined(CONFIG_BATTERY_SAMSUNG_S2PLUS) +#if defined(CONFIG_BATTERY_SAMSUNG) s3c_udc_cable_disconnect(dev); #endif } @@ -1346,7 +1346,7 @@ static void s3c_ep0_setup(struct s3c_udc *dev) reset_available = 1; dev->req_config = 1; } -#if defined(CONFIG_BATTERY_SAMSUNG) || defined(CONFIG_BATTERY_SAMSUNG_S2PLUS) +#if defined(CONFIG_BATTERY_SAMSUNG) s3c_udc_cable_connect(dev); #endif break; diff --git a/drivers/usb/gadget/storage_common.c b/drivers/usb/gadget/storage_common.c index 4dd598c..70a474c 100644 --- a/drivers/usb/gadget/storage_common.c +++ b/drivers/usb/gadget/storage_common.c @@ -205,6 +205,11 @@ struct interrupt_data { /* Length of a SCSI Command Data Block */ #define MAX_COMMAND_SIZE 16 +#if defined(CONFIG_USB_CDFS_SUPPORT) +/* SCSI commands that we recognize */ +#define READ_CD 0xbe +#endif + /* SCSI Sense Key/Additional Sense Code/ASC Qualifier values */ #define SS_NO_SENSE 0 #define SS_COMMUNICATION_FAILURE 0x040800 diff --git a/drivers/usb/gadget/u_composite_notifier.c b/drivers/usb/gadget/u_composite_notifier.c new file mode 100644 index 0000000..62ff621 --- /dev/null +++ b/drivers/usb/gadget/u_composite_notifier.c @@ -0,0 +1,40 @@ +/* + * File Name : u_composite_notifier.c + * + * Copyright (C) 2012 Samsung Electronics + * + * 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. + * + */ +static char function_lists_string[256]; + +static BLOCKING_NOTIFIER_HEAD(usb_composite_notifier_list); + +int register_usb_composite_notifier(struct notifier_block *notifier) +{ + int retval; + + retval = blocking_notifier_chain_register( + &usb_composite_notifier_list, notifier); + + return retval; +} +EXPORT_SYMBOL(register_usb_composite_notifier); + +int unregister_usb_composite_notifier(struct notifier_block *notifier) +{ + int retval; + + retval = blocking_notifier_chain_unregister( + &usb_composite_notifier_list, notifier); + + return retval; +} +EXPORT_SYMBOL(unregister_usb_composite_notifier); diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index 50bb6e0..75636d6 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -527,8 +527,26 @@ static void ehci_stop (struct usb_hcd *hcd) /* root hub is shut down separately (first, when possible) */ spin_lock_irq (&ehci->lock); +#ifdef CONFIG_MDM_HSIC_PM + if (ehci->async) { + /* + * TODO: Observed that ehci->async next ptr is not + * NULL sometimes which leads to crash in mem_cleanup. + * Root cause is not yet known why this messup is + * happenning. + * The follwing workaround fixes the crash caused + * by this temporarily. + * check if async next ptr is not NULL and unlink + * explictly. + */ + if (ehci->async->qh_next.ptr != NULL) + start_unlink_async(ehci, ehci->async->qh_next.qh); + ehci_work(ehci); + } +#else if (ehci->async) ehci_work (ehci); +#endif spin_unlock_irq (&ehci->lock); ehci_mem_cleanup (ehci); @@ -881,6 +899,13 @@ static irqreturn_t ehci_irq (struct usb_hcd *hcd) pstatus = ehci_readl(ehci, &ehci->regs->port_status[i]); +#ifdef CONFIG_MDM_HSIC_PM + /*set RS bit in case of remote wakeup*/ + if (ehci_is_TDI(ehci) && !(cmd & CMD_RUN) && + (pstatus & PORT_SUSPEND)) + ehci_writel(ehci, cmd | CMD_RUN, + &ehci->regs->command); +#endif if (pstatus & PORT_OWNER) continue; if (!(test_bit(i, &ehci->suspended_ports) && @@ -911,7 +936,7 @@ static irqreturn_t ehci_irq (struct usb_hcd *hcd) */ #ifdef CONFIG_LINK_DEVICE_HSIC /* ensure suspend bit clear by adding 5 msec delay. */ - ehci->reset_done[i] = jiffies + msecs_to_jiffies(30); + ehci->reset_done[i] = jiffies + msecs_to_jiffies(50); #else ehci->reset_done[i] = jiffies + msecs_to_jiffies(25); #endif diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c index fb0394b..037fd42 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -31,6 +31,14 @@ #define PORT_WAKE_BITS (PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E) +#if defined(CONFIG_EMI_ERROR_RECOVERY) +#define MDM_HSIC_PORT_NUM 2 +#define PORT_ENABLE_DISABLE (1 << 2) +#define PORT_ENABLE_DISABLE_CHANGE (1 << 3) +/* count reported port status change */ +static int portstatus_chg_cnt; +#endif + #ifdef CONFIG_PM static int ehci_hub_control( @@ -261,6 +269,12 @@ static int ehci_bus_suspend (struct usb_hcd *hcd) if (t1 & PORT_OWNER) set_bit(port, &ehci->owned_ports); else if ((t1 & PORT_PE) && !(t1 & PORT_SUSPEND)) { +#ifdef CONFIG_MDM_HSIC_PM + /*clear RS bit before setting SUSP bit + * and wait for HCH to get set*/ + if (ehci->susp_sof_bug) + ehci_halt(ehci); +#endif t2 |= PORT_SUSPEND; set_bit(port, &ehci->bus_suspended); } @@ -311,8 +325,12 @@ static int ehci_bus_suspend (struct usb_hcd *hcd) if (ehci->bus_suspended) udelay(150); - /* turn off now-idle HC */ - ehci_halt (ehci); +#ifdef CONFIG_MDM_HSIC_PM + /*if this bit is set, controller is already haled*/ + if (!ehci->susp_sof_bug) +#endif + /* turn off now-idle HC */ + ehci_halt(ehci); hcd->state = HC_STATE_SUSPENDED; if (ehci->reclaim) @@ -1349,6 +1367,27 @@ static int ehci_hub_control ( if (status & ~0xffff) /* only if wPortChange is interesting */ #endif dbg_port (ehci, "GetStatus", wIndex + 1, temp); + +#if defined(CONFIG_EMI_ERROR_RECOVERY) + if (temp & PORT_ENABLE_DISABLE_CHANGE) { + temp = ehci_readl(ehci, status_reg); + ehci_dbg(ehci, "recovery port status %d +\n", temp); + + /* ignore 'Current Status Change', by writing 1 */ + temp &= ~PORT_ENABLE_DISABLE; + + /* clear 'Port Enable/Disable Change', by writng 1 */ + temp |= PORT_ENABLE_DISABLE_CHANGE; + + ehci_writel(ehci, temp, status_reg); + ehci_readl(ehci, status_reg); + + ehci_dbg(ehci, "recovery port status %d -\n", + ehci_readl(ehci, status_reg)); + + portstatus_chg_cnt++; + } +#endif put_unaligned_le32(status, buf); break; case SetHubFeature: @@ -1388,6 +1427,12 @@ static int ehci_hub_control ( if ((temp & PORT_PE) == 0 || (temp & PORT_RESET) != 0) goto error; +#ifdef CONFIG_MDM_HSIC_PM + /*port gets suspended as part of bus suspend routine*/ + if (!ehci->susp_sof_bug) + ehci_writel(ehci, temp | PORT_SUSPEND, + status_reg); +#endif /* After above check the port must be connected. * Set appropriate bit thus could put phy into low power @@ -1395,7 +1440,13 @@ static int ehci_hub_control ( */ temp &= ~PORT_WKCONN_E; temp |= PORT_WKDISC_E | PORT_WKOC_E; - ehci_writel(ehci, temp | PORT_SUSPEND, status_reg); +#ifdef CONFIG_MDM_HSIC_PM + if (ehci->susp_sof_bug) + ehci_writel(ehci, temp, status_reg); + else +#endif + ehci_writel(ehci, temp | PORT_SUSPEND, + status_reg); if (hostpc_reg) { spin_unlock_irqrestore(&ehci->lock, flags); msleep(5);/* 5ms for HCD enter low pwr mode */ diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c index 5aa7cec..666f051 100644 --- a/drivers/usb/host/ehci-q.c +++ b/drivers/usb/host/ehci-q.c @@ -1002,12 +1002,6 @@ static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh) head->qh_next.qh = qh; head->hw->hw_next = dma; - /* - * flush qh descriptor into memory immediately, - * see comments in qh_append_tds. - * */ - ehci_sync_mem(); - qh_get(qh); qh->xacterrs = 0; qh->qh_state = QH_STATE_LINKED; @@ -1095,18 +1089,6 @@ static struct ehci_qh *qh_append_tds ( wmb (); dummy->hw_token = token; - /* - * Writing to dma coherent buffer on ARM may - * be delayed to reach memory, so HC may not see - * hw_token of dummy qtd in time, which can cause - * the qtd transaction to be executed very late, - * and degrade performance a lot. ehci_sync_mem - * is added to flush 'token' immediatelly into - * memory, so that ehci can execute the transaction - * ASAP. - * */ - ehci_sync_mem(); - urb->hcpriv = qh_get (qh); } } diff --git a/drivers/usb/host/ehci-s5p.c b/drivers/usb/host/ehci-s5p.c index 8cb7ae2..78399c2 100644 --- a/drivers/usb/host/ehci-s5p.c +++ b/drivers/usb/host/ehci-s5p.c @@ -24,6 +24,18 @@ #include <mach/regs-usb-host.h> #include <mach/board_rev.h> +#ifdef CONFIG_MDM_HSIC_PM +#include <linux/mdm_hsic_pm.h> +static const char hsic_pm_dev[] = "mdm_hsic_pm0"; +#endif + +#if defined(CONFIG_EHCI_IRQ_DISTRIBUTION) +#include <linux/cpu.h> +#endif +#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB) +#include <mach/sec_modem.h> +#endif + struct s5p_ehci_hcd { struct device *dev; struct usb_hcd *hcd; @@ -81,14 +93,22 @@ static int s5p_ehci_configurate(struct usb_hcd *hcd) delay_count); /* DMA burst Enable, set utmi suspend_on_n */ - writel(readl(INSNREG00(hcd->regs)) | ENA_DMA_INCR | OHCI_SUSP_LGCY, +#ifdef CONFIG_USB_OHCI_S5P + writel(readl(INSNREG00(hcd->regs)) | ENA_DMA_INCR, +#else + writel(readl(INSNREG00(hcd->regs)) | ENA_DMA_INCR, +#endif INSNREG00(hcd->regs)); return 0; } #if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB) ||\ - defined(CONFIG_CDMA_MODEM_MDM6600) -#define CP_PORT 2 /* HSIC0 in S5PC210 */ + defined(CONFIG_CDMA_MODEM_MDM6600) || defined(CONFIG_MDM_HSIC_PM) +#ifdef CONFIG_MACH_P8LTE +#define CP_PORT 1 /* HSIC0 in S5PC210 */ +#else +#define CP_PORT 2 /* HSIC0 in S5PC210 */ +#endif #define RETRY_CNT_LIMIT 30 /* Max 300ms wait for cp resume*/ int s5p_ehci_port_control(struct platform_device *pdev, int port, int enable) @@ -105,7 +125,10 @@ int s5p_ehci_port_control(struct platform_device *pdev, int port, int enable) ehci_readl(ehci, &ehci->regs->command); return 0; } +#endif +#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB) \ + || defined(CONFIG_MDM_HSIC_PM) static void s5p_wait_for_cp_resume(struct platform_device *pdev, struct usb_hcd *hcd) { @@ -114,11 +137,17 @@ static void s5p_wait_for_cp_resume(struct platform_device *pdev, u32 __iomem *portsc ; u32 val32, retry_cnt = 0; +#if !defined(CONFIG_MDM_HSIC_PM) + /* when use usb3503 hub, need not wait cp resume */ + if (modem_using_hub()) + return; +#endif portsc = &ehci->regs->port_status[CP_PORT-1]; +#if !defined(CONFIG_MDM_HSIC_PM) if (pdata && pdata->noti_host_states) pdata->noti_host_states(pdev, S5P_HOST_ON); - +#endif do { msleep(10); val32 = ehci_readl(ehci, portsc); @@ -172,6 +201,29 @@ static int s5p_ehci_suspend(struct device *dev) unsigned long flags; int rc = 0; +#ifdef CONFIG_MDM_HSIC_PM + /* + * check suspend returns 1 if it is possible to suspend + * otherwise, it returns 0 impossible or returns some error + */ + rc = check_udev_suspend_allowed(hsic_pm_dev); + if (rc > 0) { + set_host_stat(hsic_pm_dev, POWER_OFF); + if (wait_dev_pwr_stat(hsic_pm_dev, POWER_OFF) < 0) { + set_host_stat(hsic_pm_dev, POWER_ON); + pm_runtime_resume(&pdev->dev); + return -EBUSY; + } + } else if (rc == -ENODEV) { + /* no hsic pm driver loaded, proceed suspend */ + pr_debug("%s: suspend without hsic pm\n", __func__); + } else { + pm_runtime_resume(&pdev->dev); + return -EBUSY; + } + rc = 0; +#endif + if (time_before(jiffies, ehci->next_statechange)) msleep(10); @@ -261,7 +313,12 @@ static int s5p_ehci_resume(struct device *dev) ehci_port_power(ehci, 1); hcd->state = HC_STATE_SUSPENDED; -#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB) +#ifdef CONFIG_MDM_HSIC_PM + set_host_stat(hsic_pm_dev, POWER_ON); + wait_dev_pwr_stat(hsic_pm_dev, POWER_ON); +#endif +#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB) \ + || defined(CONFIG_MDM_HSIC_PM) s5p_wait_for_cp_resume(pdev, hcd); #endif return 0; @@ -280,7 +337,9 @@ static int s5p_ehci_runtime_suspend(struct device *dev) if (pdata && pdata->phy_suspend) pdata->phy_suspend(pdev, S5P_USB_PHY_HOST); - +#ifdef CONFIG_MDM_HSIC_PM + request_active_lock_release(hsic_pm_dev); +#endif return 0; } @@ -296,6 +355,9 @@ static int s5p_ehci_runtime_resume(struct device *dev) if (dev->power.is_suspended) return 0; +#ifdef CONFIG_MDM_HSIC_PM + request_active_lock_set(hsic_pm_dev); +#endif /* platform device isn't suspended */ if (pdata && pdata->phy_resume) rc = pdata->phy_resume(pdev, S5P_USB_PHY_HOST); @@ -313,14 +375,17 @@ static int s5p_ehci_runtime_resume(struct device *dev) usb_root_hub_lost_power(hcd->self.root_hub); ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag); - ehci_writel(ehci, INTR_MASK, &ehci->regs->intr_enable); - (void)ehci_readl(ehci, &ehci->regs->intr_enable); /* here we "know" root ports should always stay powered */ ehci_port_power(ehci, 1); hcd->state = HC_STATE_SUSPENDED; -#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB) +#ifdef CONFIG_MDM_HSIC_PM + set_host_stat(hsic_pm_dev, POWER_ON); + wait_dev_pwr_stat(hsic_pm_dev, POWER_ON); +#endif +#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB) \ + || defined(CONFIG_MDM_HSIC_PM) s5p_wait_for_cp_resume(pdev, hcd); #endif } @@ -497,6 +562,50 @@ static inline void remove_ehci_sys_file(struct ehci_hcd *ehci) #endif } +#if defined(CONFIG_EHCI_IRQ_DISTRIBUTION) +static int s5p_ehci_irq_no = 0; +static int s5p_ehci_irq_cpu = 0; + +/* total cpu core numbers to irq cpu (cpu0 is default) + * 1 (single): cpu0 + * 2 (dual) : cpu1 + * 3 : cpu1 + * 4 (quad) : cpu3 + */ +static int s5p_ehci_cpus[] = {0, 1, 1, 3}; + +static int __cpuinit s5p_ehci_cpu_notify(struct notifier_block *self, + unsigned long action, void *hcpu) +{ + int cpu = (unsigned long)hcpu; + + if (!s5p_ehci_irq_no || cpu != s5p_ehci_irq_cpu) + goto exit; + + switch (action) { + case CPU_ONLINE: + case CPU_DOWN_FAILED: + case CPU_ONLINE_FROZEN: + irq_set_affinity(s5p_ehci_irq_no, cpumask_of(s5p_ehci_irq_cpu)); + pr_debug("%s: set ehci irq to cpu%d\n", __func__, cpu); + break; + case CPU_DOWN_PREPARE: + case CPU_DOWN_PREPARE_FROZEN: + irq_set_affinity(s5p_ehci_irq_no, cpumask_of(0)); + pr_debug("%s: set ehci irq to cpu%d\n", __func__, 0); + break; + default: + break; + } +exit: + return NOTIFY_OK; +} + +static struct notifier_block __cpuinitdata s5p_ehci_cpu_notifier = { + .notifier_call = s5p_ehci_cpu_notify, +}; +#endif + static int __devinit s5p_ehci_probe(struct platform_device *pdev) { struct s5p_ehci_platdata *pdata; @@ -590,6 +699,27 @@ static int __devinit s5p_ehci_probe(struct platform_device *pdev) pm_runtime_set_active(&pdev->dev); pm_runtime_enable(&pdev->dev); #endif +#ifdef CONFIG_MDM_HSIC_PM + /* halt controller before driving suspend on ths bus */ + ehci->susp_sof_bug = 1; + + set_host_stat(hsic_pm_dev, POWER_ON); + pm_runtime_allow(&pdev->dev); + pm_runtime_set_autosuspend_delay(&hcd->self.root_hub->dev, 0); + + pm_runtime_forbid(&pdev->dev); + enable_periodic(ehci); +#endif + +#ifdef CONFIG_EHCI_IRQ_DISTRIBUTION + if (num_possible_cpus() > 1) { + s5p_ehci_irq_no = irq; + s5p_ehci_irq_cpu = s5p_ehci_cpus[num_possible_cpus() - 1]; + irq_set_affinity(s5p_ehci_irq_no, cpumask_of(s5p_ehci_irq_cpu)); + register_cpu_notifier(&s5p_ehci_cpu_notifier); + } +#endif + #if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB) /* for cp enumeration */ pm_runtime_forbid(&pdev->dev); @@ -619,13 +749,30 @@ static int __devexit s5p_ehci_remove(struct platform_device *pdev) struct s5p_ehci_hcd *s5p_ehci = platform_get_drvdata(pdev); struct usb_hcd *hcd = s5p_ehci->hcd; +/* pm_runtime_disable called twice during pdev unregistering + * it causes disable_depth mismatching, so rpm for this device + * cannot works from disable_depth count + * replace it to runtime forbid. + */ #ifdef CONFIG_USB_SUSPEND +#ifdef CONFIG_MDM_HSIC_PM + pm_runtime_forbid(&pdev->dev); +#else pm_runtime_disable(&pdev->dev); #endif +#endif s5p_ehci->power_on = 0; remove_ehci_sys_file(hcd_to_ehci(hcd)); usb_remove_hcd(hcd); +#ifdef CONFIG_EHCI_IRQ_DISTRIBUTION + if (num_possible_cpus() > 1) { + s5p_ehci_irq_no = 0; + s5p_ehci_irq_cpu = 0; + unregister_cpu_notifier(&s5p_ehci_cpu_notifier); + } +#endif + #if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB) /*HSIC IPC control the ACTIVE_STATE*/ if (pdata && pdata->noti_host_states) diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index f3db2b3..5a923a9 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -138,6 +138,9 @@ struct ehci_hcd { /* one per controller */ unsigned use_dummy_qh:1; /* AMD Frame List table quirk*/ unsigned has_synopsys_hc_bug:1; /* Synopsys HC */ unsigned frame_index_bug:1; /* MosChip (AKA NetMos) */ +#ifdef CONFIG_MDM_HSIC_PM + unsigned susp_sof_bug; /*Chip Idea HC*/ +#endif /* required for usb32 quirk */ #define OHCI_CTRL_HCFS (3 << 6) diff --git a/drivers/usb/host/ohci-s5p.c b/drivers/usb/host/ohci-s5p.c index fce68ef..641e40c 100644 --- a/drivers/usb/host/ohci-s5p.c +++ b/drivers/usb/host/ohci-s5p.c @@ -191,6 +191,20 @@ static int ohci_hcd_s5p_drv_runtime_resume(struct device *dev) #define ohci_hcd_s5p_drv_runtime_resume NULL #endif +static int ohci_s5p_init(struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + int ret; + + ohci_dbg(ohci, "ohci_s5p_init, ohci:%p", ohci); + + ret = ohci_init(ohci); + if (ret < 0) + return ret; + + return 0; +} + static int ohci_s5p_start(struct usb_hcd *hcd) { struct ohci_hcd *ohci = hcd_to_ohci(hcd); @@ -198,10 +212,6 @@ static int ohci_s5p_start(struct usb_hcd *hcd) ohci_dbg(ohci, "ohci_s5p_start, ohci:%p", ohci); - ret = ohci_init(ohci); - if (ret < 0) - return ret; - ret = ohci_run(ohci); if (ret < 0) { err("can't start %s", hcd->self.bus_name); @@ -220,6 +230,7 @@ static const struct hc_driver ohci_s5p_hc_driver = { .irq = ohci_irq, .flags = HCD_MEMORY|HCD_USB11, + .reset = ohci_s5p_init, .start = ohci_s5p_start, .stop = ohci_stop, .shutdown = ohci_shutdown, @@ -447,7 +458,12 @@ static void ohci_hcd_s5p_drv_shutdown(struct platform_device *pdev) { struct s5p_ohci_platdata *pdata = pdev->dev.platform_data; struct s5p_ohci_hcd *s5p_ohci = platform_get_drvdata(pdev); - struct usb_hcd *hcd = s5p_ohci->hcd; + struct usb_hcd *hcd; + + if (!pdata || !s5p_ohci) + return; + + hcd = s5p_ohci->hcd; if (!s5p_ohci->power_on) return; diff --git a/drivers/usb/misc/diag_bridge.c b/drivers/usb/misc/diag_bridge.c index 9794918..bdd6654 100644 --- a/drivers/usb/misc/diag_bridge.c +++ b/drivers/usb/misc/diag_bridge.c @@ -23,8 +23,15 @@ #include <linux/debugfs.h> #include <mach/diag_bridge.h> +#ifdef CONFIG_MDM_HSIC_PM +#include <linux/mdm_hsic_pm.h> +static const char rmnet_pm_dev[] = "mdm_hsic_pm0"; +#endif + #define DRIVER_DESC "USB host diag bridge driver" #define DRIVER_VERSION "1.0" +/* zero_pky.patch */ +#define IN_BUF_SIZE 16384 struct diag_bridge { struct usb_device *udev; @@ -36,6 +43,9 @@ struct diag_bridge { struct kref kref; struct diag_bridge_ops *ops; struct platform_device *pdev; + /* zero_pky.patch */ + unsigned char *buf_in; + /* debugging counters */ unsigned long bytes_to_host; @@ -56,11 +66,72 @@ int diag_bridge_open(struct diag_bridge_ops *ops) dev->ops = ops; dev->err = 0; + usb_kill_anchored_urbs(&dev->submitted); return 0; } EXPORT_SYMBOL(diag_bridge_open); +/* zero_pky.patch */ +/* Even when no driver is using the diag bridge + we are setting default read on this endpoint. + This will consume any packet sent by CP + and its dropped */ +static void read_hsic_cb(struct urb* urb); +static void read_hsic(void) +{ + struct diag_bridge *dev = __dev; + struct urb *urb = NULL; + unsigned int pipe; + int ret; + + dev_info(&dev->udev->dev, "%s:\n", __func__); + if (!dev->ifc) { + dev_err(&dev->udev->dev, "device is disconnected\n"); + return; + } + + /* if there was a previous unrecoverable error, just quit */ + if (dev->err) + return; + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + dev_err(&dev->udev->dev, "unable to allocate urb\n"); + return; + } + + pipe = usb_rcvbulkpipe(dev->udev, dev->in_epAddr); + usb_fill_bulk_urb(urb, dev->udev, pipe, dev->buf_in,IN_BUF_SIZE, + read_hsic_cb, dev); + usb_anchor_urb(urb, &dev->submitted); + ret = usb_submit_urb(urb, GFP_KERNEL); + if (ret) { + dev_err(&dev->udev->dev, "submitting urb failed err:%d\n", ret); + dev->pending_reads--; + usb_unanchor_urb(urb); + usb_free_urb(urb); + return; + } + + usb_free_urb(urb); +} + +static void read_hsic_cb(struct urb *urb) +{ + struct diag_bridge *dev = urb->context; + struct diag_bridge_ops *cbs = dev->ops; + + pr_info("%s: status:%d actual:%d\n", __func__, + urb->status, urb->actual_length); + + /* Drop the packet */ + if (urb->status == -EPROTO) { + pr_err("%s: drop packet from protocol error\n", __func__); + return; + } +} + void diag_bridge_close(void) { struct diag_bridge *dev = __dev; @@ -78,9 +149,6 @@ static void diag_bridge_read_cb(struct urb *urb) struct diag_bridge *dev = urb->context; struct diag_bridge_ops *cbs = dev->ops; - dev_dbg(&dev->udev->dev, "%s: status:%d actual:%d\n", __func__, - urb->status, urb->actual_length); - if (urb->status == -EPROTO) { dev_err(&dev->udev->dev, "%s: proto error\n", __func__); /* save error so that subsequent read/write returns ESHUTDOWN */ @@ -88,7 +156,8 @@ static void diag_bridge_read_cb(struct urb *urb) return; } - cbs->read_complete_cb(cbs->ctxt, + if (cbs && cbs->read_complete_cb) + cbs->read_complete_cb(cbs->ctxt, urb->transfer_buffer, urb->transfer_buffer_length, urb->status < 0 ? urb->status : urb->actual_length); @@ -103,8 +172,10 @@ int diag_bridge_read(char *data, int size) unsigned int pipe; struct diag_bridge *dev = __dev; int ret; + int spin = 50; - dev_dbg(&dev->udev->dev, "%s:\n", __func__); + if (!dev || !dev->udev) + return -ENODEV; if (!size) { dev_err(&dev->udev->dev, "invalid size:%d\n", size); @@ -120,6 +191,16 @@ int diag_bridge_read(char *data, int size) if (dev->err) return -ESHUTDOWN; + while (check_request_blocked(rmnet_pm_dev) && spin--) { + pr_debug("%s: wake up wait loop\n", __func__); + msleep(20); + } + + if (check_request_blocked(rmnet_pm_dev)) { + pr_err("%s: in lpa wakeup, return EAGAIN\n", __func__); + return -EAGAIN; + } + urb = usb_alloc_urb(0, GFP_KERNEL); if (!urb) { dev_err(&dev->udev->dev, "unable to allocate urb\n"); @@ -161,24 +242,25 @@ static void diag_bridge_write_cb(struct urb *urb) struct diag_bridge *dev = urb->context; struct diag_bridge_ops *cbs = dev->ops; - dev_dbg(&dev->udev->dev, "%s:\n", __func__); - usb_autopm_put_interface_async(dev->ifc); if (urb->status == -EPROTO) { dev_err(&dev->udev->dev, "%s: proto error\n", __func__); /* save error so that subsequent read/write returns ESHUTDOWN */ dev->err = urb->status; + usb_free_urb(urb); return; } - cbs->write_complete_cb(cbs->ctxt, + if (cbs && cbs->write_complete_cb) + cbs->write_complete_cb(cbs->ctxt, urb->transfer_buffer, urb->transfer_buffer_length, urb->status < 0 ? urb->status : urb->actual_length); dev->bytes_to_mdm += urb->actual_length; dev->pending_writes--; + usb_free_urb(urb); } int diag_bridge_write(char *data, int size) @@ -187,8 +269,10 @@ int diag_bridge_write(char *data, int size) unsigned int pipe; struct diag_bridge *dev = __dev; int ret; + int spin; - dev_dbg(&dev->udev->dev, "%s:\n", __func__); + if (!dev || !dev->udev) + return -ENODEV; if (!size) { dev_err(&dev->udev->dev, "invalid size:%d\n", size); @@ -204,19 +288,48 @@ int diag_bridge_write(char *data, int size) if (dev->err) return -ESHUTDOWN; + spin = 50; + while (check_request_blocked(rmnet_pm_dev) && spin--) { + pr_info("%s: wake up wait loop\n", __func__); + msleep(20); + } + + if (check_request_blocked(rmnet_pm_dev)) { + pr_err("%s: in lpa wakeup, return EAGAIN\n", __func__); + return -EAGAIN; + } + urb = usb_alloc_urb(0, GFP_KERNEL); if (!urb) { err("unable to allocate urb"); return -ENOMEM; } - ret = usb_autopm_get_interface(dev->ifc); + ret = usb_autopm_get_interface_async(dev->ifc); if (ret < 0) { dev_err(&dev->udev->dev, "autopm_get failed:%d\n", ret); usb_free_urb(urb); return ret; } + for (spin = 0; spin < 10; spin++) { + /* check rpm active */ + if (dev->udev->dev.power.runtime_status == RPM_ACTIVE) { + ret = 0; + break; + } else { + dev_err(&dev->udev->dev, "waiting rpm active\n"); + ret = -EAGAIN; + } + msleep(20); + } + if (ret < 0) { + dev_err(&dev->udev->dev, "rpm active failed:%d\n", ret); + usb_free_urb(urb); + usb_autopm_put_interface(dev->ifc); + return ret; + } + pipe = usb_sndbulkpipe(dev->udev, dev->out_epAddr); usb_fill_bulk_urb(urb, dev->udev, pipe, data, size, diag_bridge_write_cb, dev); @@ -232,9 +345,9 @@ int diag_bridge_write(char *data, int size) usb_autopm_put_interface(dev->ifc); return ret; } - +#if 0 usb_free_urb(urb); - +#endif return 0; } EXPORT_SYMBOL(diag_bridge_write); @@ -351,6 +464,12 @@ diag_bridge_probe(struct usb_interface *ifc, const struct usb_device_id *id) kfree(dev); return -ENOMEM; } + /* zero_pky.patch */ + dev->buf_in = kzalloc(IN_BUF_SIZE, GFP_KERNEL); + if (!dev->buf_in) { + pr_err("%s: unable to allocate dev->buf_in\n", __func__); + return -ENOMEM; + } __dev = dev; dev->udev = usb_get_dev(interface_to_usbdev(ifc)); @@ -397,6 +516,8 @@ static void diag_bridge_disconnect(struct usb_interface *ifc) dev_dbg(&dev->udev->dev, "%s:\n", __func__); platform_device_del(dev->pdev); + /* zero_pky.patch */ + kfree(dev->buf_in); diag_bridge_debugfs_cleanup(); kref_put(&dev->kref, diag_bridge_delete); usb_set_intfdata(ifc, NULL); @@ -415,10 +536,11 @@ static int diag_bridge_suspend(struct usb_interface *ifc, pm_message_t message) "%s: diag veto'd suspend\n", __func__); return ret; } - - usb_kill_anchored_urbs(&dev->submitted); } + /* zero_pky.patch */ + usb_kill_anchored_urbs(&dev->submitted); + return ret; } @@ -430,6 +552,9 @@ static int diag_bridge_resume(struct usb_interface *ifc) if (cbs && cbs->resume) cbs->resume(cbs->ctxt); + /* set the default read */ /* zero_pky.patch */ + else + read_hsic(); return 0; } @@ -455,6 +580,7 @@ static struct usb_driver diag_bridge_driver = { .disconnect = diag_bridge_disconnect, .suspend = diag_bridge_suspend, .resume = diag_bridge_resume, + .reset_resume = diag_bridge_resume, .id_table = diag_bridge_ids, .supports_autosuspend = 1, }; diff --git a/drivers/usb/misc/exynos-usb-switch.c b/drivers/usb/misc/exynos-usb-switch.c index 519c845..3acb66e 100644 --- a/drivers/usb/misc/exynos-usb-switch.c +++ b/drivers/usb/misc/exynos-usb-switch.c @@ -34,7 +34,7 @@ static const char switch_name[] = "exynos_usb_switch"; static struct exynos_usb_switch *our_switch; -#if defined(CONFIG_BATTERY_SAMSUNG) || defined(CONFIG_BATTERY_SAMSUNG_S2PLUS) +#if defined(CONFIG_BATTERY_SAMSUNG) void exynos_usb_cable_connect(void) { samsung_cable_check_status(1); @@ -158,13 +158,13 @@ static int exynos_change_usb_mode(struct exynos_usb_switch *usb_switch, if (usb_switch->gpio_host_vbus) set_host_vbus(usb_switch, 0); -#if defined(CONFIG_BATTERY_SAMSUNG) || defined(CONFIG_BATTERY_SAMSUNG_S2PLUS) +#if defined(CONFIG_BATTERY_SAMSUNG) exynos_usb_cable_disconnect(); #endif clear_bit(USB_HOST_ATTACHED, &usb_switch->connect); break; case USB_HOST_ATTACHED: -#if defined(CONFIG_BATTERY_SAMSUNG) || defined(CONFIG_BATTERY_SAMSUNG_S2PLUS) +#if defined(CONFIG_BATTERY_SAMSUNG) exynos_usb_cable_connect(); #endif if (usb_switch->gpio_host_vbus) diff --git a/drivers/usb/misc/mdm_ctrl_bridge.c b/drivers/usb/misc/mdm_ctrl_bridge.c index 4755790..6635819 100644 --- a/drivers/usb/misc/mdm_ctrl_bridge.c +++ b/drivers/usb/misc/mdm_ctrl_bridge.c @@ -25,6 +25,12 @@ #include <linux/termios.h> #include <asm/unaligned.h> #include <mach/usb_bridge.h> +#include <linux/mdm_hsic_pm.h> + +#ifdef CONFIG_MDM_HSIC_PM +#include <linux/mdm_hsic_pm.h> +static const char rmnet_pm_dev[] = "mdm_hsic_pm0"; +#endif static const char const *ctrl_bridge_names[] = { "dun_ctrl_hsic0", @@ -132,12 +138,17 @@ static void resp_avail_cb(struct urb *urb) int status = 0; int resubmit_urb = 1; struct bridge *brdg = dev->brdg; + unsigned int iface_num; udev = interface_to_usbdev(dev->intf); + iface_num = dev->intf->cur_altsetting->desc.bInterfaceNumber; + switch (urb->status) { + case -ENOENT: case 0: /*success*/ dev->get_encap_res++; + pr_info("[RACB:%d]<\n", iface_num); if (brdg && brdg->ops.send_pkt) brdg->ops.send_pkt(brdg->ctx, urb->transfer_buffer, urb->actual_length); @@ -145,7 +156,6 @@ static void resp_avail_cb(struct urb *urb) /*do not resubmit*/ case -ESHUTDOWN: - case -ENOENT: case -ECONNRESET: /* unplug */ case -EPROTO: @@ -158,16 +168,15 @@ static void resp_avail_cb(struct urb *urb) __func__, urb->status); } - if (resubmit_urb) { + if (urb->status != -ENOENT && resubmit_urb) { /*re- submit int urb to check response available*/ - usb_anchor_urb(dev->inturb, &dev->tx_submitted); status = usb_submit_urb(dev->inturb, GFP_ATOMIC); if (status) { dev_err(&udev->dev, "%s: Error re-submitting Int URB %d\n", __func__, status); - usb_unanchor_urb(dev->inturb); } + pr_info("[CHKRA:%d]>\n", iface_num); } } @@ -180,11 +189,19 @@ static void notification_available_cb(struct urb *urb) struct bridge *brdg = dev->brdg; unsigned int ctrl_bits; unsigned char *data; + unsigned int iface_num; + + /* if this intf is already disconnected, this urb free-ed before + * calling from qh_completions. just return and do nothing */ + if (!dev->intf) + return; udev = interface_to_usbdev(dev->intf); + iface_num = dev->intf->cur_altsetting->desc.bInterfaceNumber; switch (urb->status) { case 0: + pr_info("[NACB:%d]<\n", iface_num); /*success*/ break; case -ESHUTDOWN: @@ -216,15 +233,14 @@ static void notification_available_cb(struct urb *urb) DEFAULT_READ_URB_LENGTH, resp_avail_cb, dev); - usb_anchor_urb(dev->readurb, &dev->tx_submitted); status = usb_submit_urb(dev->readurb, GFP_ATOMIC); if (status) { dev_err(&udev->dev, "%s: Error submitting Read URB %d\n", __func__, status); - usb_unanchor_urb(dev->readurb); goto resubmit_int_urb; - } + } else + pr_info("[NRA:%d]>\n", iface_num); return; case USB_CDC_NOTIFY_NETWORK_CONNECTION: dev_dbg(&udev->dev, "%s network\n", ctrl->wValue ? @@ -237,6 +253,10 @@ static void notification_available_cb(struct urb *urb) dev->cbits_tohost = ctrl_bits; if (brdg && brdg->ops.send_cbits) brdg->ops.send_cbits(brdg->ctx, ctrl_bits); +#ifdef CONFIG_MDM_HSIC_PM + pr_info("%s: set lpa handling to false\n", __func__); + lpa_handling = false; +#endif break; default: dev_err(&udev->dev, "%s: unknown notification %d received:" @@ -246,38 +266,57 @@ static void notification_available_cb(struct urb *urb) } resubmit_int_urb: - usb_anchor_urb(urb, &dev->tx_submitted); status = usb_submit_urb(urb, GFP_ATOMIC); if (status) { dev_err(&udev->dev, "%s: Error re-submitting Int URB %d\n", __func__, status); - usb_unanchor_urb(urb); - } + } else + pr_info("[CHKRA:%d]>\n", iface_num); } int ctrl_bridge_start_read(struct ctrl_bridge *dev) { - int retval = 0; + int retval = 0; + struct usb_device *udev; + unsigned int iface_num; + + udev = interface_to_usbdev(dev->intf); + iface_num = dev->intf->cur_altsetting->desc.bInterfaceNumber; if (!dev->inturb) { dev_err(&dev->udev->dev, "%s: inturb is NULL\n", __func__); return -ENODEV; } - if (!dev->inturb->anchor) { - usb_anchor_urb(dev->inturb, &dev->tx_submitted); - retval = usb_submit_urb(dev->inturb, GFP_KERNEL); - if (retval < 0) { - dev_err(&dev->udev->dev, - "%s error submitting int urb %d\n", - __func__, retval); - usb_unanchor_urb(dev->inturb); - } - } + retval = usb_submit_urb(dev->inturb, GFP_KERNEL); + if (retval < 0) { + dev_err(&dev->udev->dev, + "%s error submitting int urb %d\n", + __func__, retval); + + } else + pr_info("[CHKRA:%d]>\n", iface_num); return retval; } +void ctrl_bridge_stop_read(struct ctrl_bridge *dev) +{ + usb_kill_urb(dev->readurb); + usb_kill_urb(dev->inturb); +} + +void ctrl_bridge_stop_all(void) +{ + int id; + + for (id = 0; id < MAX_BRIDGE_DEVICES; id++) { + if (__dev[id]) + ctrl_bridge_stop_read(__dev[id]); + } +} +EXPORT_SYMBOL(ctrl_bridge_stop_all); + int ctrl_bridge_open(struct bridge *brdg) { struct ctrl_bridge *dev; @@ -349,7 +388,8 @@ static void ctrl_write_callback(struct urb *urb) kfree(urb->transfer_buffer); kfree(urb->setup_packet); usb_free_urb(urb); - usb_autopm_put_interface_async(dev->intf); + if (dev->intf) + usb_autopm_put_interface_async(dev->intf); } int ctrl_bridge_write(unsigned int id, char *data, size_t size) @@ -359,6 +399,7 @@ int ctrl_bridge_write(unsigned int id, char *data, size_t size) struct usb_ctrlrequest *out_ctlreq; struct usb_device *udev; struct ctrl_bridge *dev; + int spin = 50; if (id >= MAX_BRIDGE_DEVICES) { result = -EINVAL; @@ -372,6 +413,17 @@ int ctrl_bridge_write(unsigned int id, char *data, size_t size) goto free_data; } + /* move it to mdm _hsic pm .c, check return code */ + while (lpa_handling && spin--) { + pr_info("%s: lpa wake wait loop\n", __func__); + msleep(20); + } + + if (lpa_handling) { + pr_err("%s: in lpa wakeup, return EAGAIN\n", __func__); + return -EAGAIN; + } + udev = interface_to_usbdev(dev->intf); dev_dbg(&udev->dev, "%s:[id]:%u: write (%d bytes)\n", @@ -471,6 +523,7 @@ int ctrl_bridge_suspend(unsigned int id) set_bit(SUSPENDED, &dev->flags); usb_kill_anchored_urbs(&dev->tx_submitted); + ctrl_bridge_stop_read(dev); return 0; } @@ -505,9 +558,15 @@ int ctrl_bridge_resume(unsigned int id) } /* if the bridge is open, resume reading */ +#ifndef CONFIG_MDM_HSIC_PM if (dev->brdg) return ctrl_bridge_start_read(dev); - +#else + /* if the bridge is open or not, resume to consume mdm request + * because this link is not dead, it's alive + */ + return ctrl_bridge_start_read(dev); +#endif return 0; } @@ -711,6 +770,12 @@ ctrl_bridge_probe(struct usb_interface *ifc, struct usb_host_endpoint *int_in, ch_id++; +#ifdef CONFIG_MDM_HSIC_PM + /* if the bridge is open or not, resume to consume mdm request + * because this link is not dead, it's alive + */ + ctrl_bridge_start_read(dev); +#endif return retval; free_rbuf: @@ -737,6 +802,10 @@ void ctrl_bridge_disconnect(unsigned int id) platform_device_del(dev->pdev); + dev->intf = NULL; + usb_kill_urb(dev->readurb); + usb_kill_urb(dev->inturb); + kfree(dev->in_ctlreq); kfree(dev->readbuf); kfree(dev->intbuf); diff --git a/drivers/usb/misc/mdm_data_bridge.c b/drivers/usb/misc/mdm_data_bridge.c index 6af9664..2dedc6d 100644 --- a/drivers/usb/misc/mdm_data_bridge.c +++ b/drivers/usb/misc/mdm_data_bridge.c @@ -20,6 +20,10 @@ #include <linux/uaccess.h> #include <linux/ratelimit.h> #include <mach/usb_bridge.h> +#ifdef CONFIG_MDM_HSIC_PM +#include <linux/mdm_hsic_pm.h> +static const char rmnet_pm_dev[] = "mdm_hsic_pm0"; +#endif #define MAX_RX_URBS 50 #define RMNET_RX_BUFSIZE 2048 @@ -117,7 +121,17 @@ static inline bool rx_halted(struct data_bridge *dev) static inline bool rx_throttled(struct bridge *brdg) { +#ifndef CONFIG_MDM_HSIC_PM return test_bit(RX_THROTTLED, &brdg->flags); +#else + /* if the bridge is open or not, resume to consume mdm request + * because this link is not dead, it's alive + */ + if (brdg) + return test_bit(RX_THROTTLED, &brdg->flags); + else + return 0; +#endif } int data_bridge_unthrottle_rx(unsigned int id) @@ -149,11 +163,26 @@ static void data_bridge_process_rx(struct work_struct *work) container_of(work, struct data_bridge, process_rx_w); struct bridge *brdg = dev->brdg; - +#if !defined(CONFIG_MDM_HSIC_PM) + /* if the bridge is open or not, resume to consume mdm request + * because this link is not dead, it's alive + */ if (!brdg || !brdg->ops.send_pkt || rx_halted(dev)) return; +#endif while (!rx_throttled(brdg) && (skb = skb_dequeue(&dev->rx_done))) { +#ifdef CONFIG_MDM_HSIC_PM + /* if the bridge is open or not, resume to consume mdm request + * because this link is not dead, it's alive + */ + if (!brdg) { + print_hex_dump(KERN_INFO, "dun:", 0, 1, 1, skb->data, + skb->len, false); + dev_kfree_skb_any(skb); + continue; + } +#endif dev->to_host++; info = (struct timestamp_info *)skb->cb; info->rx_done_sent = get_timestamp(); @@ -197,12 +226,18 @@ static void data_bridge_read_cb(struct urb *urb) skb_put(skb, urb->actual_length); switch (urb->status) { + case -ENOENT: /* suspended */ case 0: /* success */ queue = 1; info->rx_done = get_timestamp(); spin_lock(&dev->rx_done.lock); __skb_queue_tail(&dev->rx_done, skb); spin_unlock(&dev->rx_done.lock); +#ifdef CONFIG_MDM_HSIC_PM + /* wakelock for fast dormancy */ + if (urb->actual_length) + fast_dormancy_wakelock(rmnet_pm_dev); +#endif break; /*do not resubmit*/ @@ -212,7 +247,6 @@ static void data_bridge_read_cb(struct urb *urb) schedule_work(&dev->kevent); /* FALLTHROUGH */ case -ESHUTDOWN: - case -ENOENT: /* suspended */ case -ECONNRESET: /* unplug */ case -EPROTO: dev_kfree_skb_any(skb); @@ -229,9 +263,14 @@ static void data_bridge_read_cb(struct urb *urb) } spin_lock(&dev->rx_done.lock); + urb->context = NULL; list_add_tail(&urb->urb_list, &dev->rx_idle); spin_unlock(&dev->rx_done.lock); + /* during suspend handle rx packet, but do not queue rx work */ + if (urb->status == -ENOENT) + return; + if (queue) queue_work(dev->wq, &dev->process_rx_w); } @@ -266,6 +305,7 @@ static int submit_rx_urb(struct data_bridge *dev, struct urb *rx_urb, if (retval) goto fail; + usb_mark_last_busy(dev->udev); return 0; fail: usb_unanchor_urb(rx_urb); @@ -320,9 +360,9 @@ int data_bridge_open(struct bridge *brdg) dev->tx_unthrottled_cnt = 0; dev->rx_throttled_cnt = 0; dev->rx_unthrottled_cnt = 0; - +#ifndef CONFIG_MDM_HSIC_PM queue_work(dev->wq, &dev->process_rx_w); - +#endif return 0; } EXPORT_SYMBOL(data_bridge_open); @@ -563,8 +603,12 @@ static int data_bridge_resume(struct data_bridge *dev) dev->to_modem++; dev->txurb_drp_cnt--; } - + /* if the bridge is open or not, resume to consume mdm request + * because this link is not dead, it's alive + */ +#ifndef CONFIG_MDM_HSIC_PM if (dev->brdg) +#endif queue_work(dev->wq, &dev->process_rx_w); return 0; @@ -662,7 +706,12 @@ static int data_bridge_probe(struct usb_interface *iface, /*allocate list of rx urbs*/ data_bridge_prepare_rx(dev); - +#ifdef CONFIG_MDM_HSIC_PM + /* if the bridge is open or not, resume to consume mdm request + * because this link is not dead, it's alive + */ + queue_work(dev->wq, &dev->process_rx_w); +#endif platform_device_add(dev->pdev); return 0; @@ -979,6 +1028,7 @@ static void bridge_disconnect(struct usb_interface *intf) struct data_bridge *dev = usb_get_intfdata(intf); struct list_head *head; struct urb *rx_urb; + struct sk_buff *skb; unsigned long flags; if (!dev) { @@ -995,12 +1045,20 @@ static void bridge_disconnect(struct usb_interface *intf) cancel_work_sync(&dev->process_rx_w); cancel_work_sync(&dev->kevent); + spin_lock_irqsave(&dev->rx_done.lock, flags); + while ((skb = __skb_dequeue(&dev->rx_done))) + dev_kfree_skb_any(skb); + spin_unlock_irqrestore(&dev->rx_done.lock, flags); + /*free rx urbs*/ head = &dev->rx_idle; spin_lock_irqsave(&dev->rx_done.lock, flags); while (!list_empty(head)) { rx_urb = list_entry(head->next, struct urb, urb_list); list_del(&rx_urb->urb_list); + skb = (struct sk_buff *)rx_urb->context; + if (skb) + dev_kfree_skb_any(skb); usb_free_urb(rx_urb); } spin_unlock_irqrestore(&dev->rx_done.lock, flags); @@ -1040,6 +1098,7 @@ static struct usb_driver bridge_driver = { .id_table = bridge_ids, .suspend = bridge_suspend, .resume = bridge_resume, + .reset_resume = bridge_resume, .supports_autosuspend = 1, }; diff --git a/drivers/usb/serial/Kconfig b/drivers/usb/serial/Kconfig index b71e309..caa5fda 100644 --- a/drivers/usb/serial/Kconfig +++ b/drivers/usb/serial/Kconfig @@ -651,6 +651,15 @@ config USB_SERIAL_SSU100 To compile this driver as a module, choose M here: the module will be called ssu100. +config USB_SERIAL_CSVT + tristate "USB serial driver for Circuit-Switched Video Telephony" + help + Say Y here if you want to use usb serial driver for Circuit-Switched + Video Telephony + + To compile this driver as a module, choose M here: the + module will be called csvt. + config USB_SERIAL_DEBUG tristate "USB Debugging Device" help diff --git a/drivers/usb/serial/Makefile b/drivers/usb/serial/Makefile index 9e536ee..2d085b2 100644 --- a/drivers/usb/serial/Makefile +++ b/drivers/usb/serial/Makefile @@ -60,3 +60,4 @@ obj-$(CONFIG_USB_SERIAL_WHITEHEAT) += whiteheat.o obj-$(CONFIG_USB_SERIAL_XIRCOM) += keyspan_pda.o obj-$(CONFIG_USB_SERIAL_VIVOPAY_SERIAL) += vivopay-serial.o obj-$(CONFIG_USB_SERIAL_ZIO) += zio.o +obj-$(CONFIG_USB_SERIAL_CSVT) += csvt.o diff --git a/drivers/usb/serial/csvt.c b/drivers/usb/serial/csvt.c new file mode 100644 index 0000000..28eba8a --- /dev/null +++ b/drivers/usb/serial/csvt.c @@ -0,0 +1,446 @@ +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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/slab.h> +#include <linux/tty.h> +#include <linux/serial.h> +#include <linux/tty_flip.h> +#include <linux/uaccess.h> +#include <linux/usb.h> +#include <linux/usb/ch9.h> +#include <linux/usb/cdc.h> +#include <linux/usb/serial.h> +#include <asm/unaligned.h> + + +/* output control lines*/ +#define CSVT_CTRL_DTR 0x01 +#define CSVT_CTRL_RTS 0x02 + +/* input control lines*/ +#define CSVT_CTRL_CTS 0x01 +#define CSVT_CTRL_DSR 0x02 +#define CSVT_CTRL_RI 0x04 +#define CSVT_CTRL_CD 0x08 + +static int debug; +module_param(debug, int, S_IRUGO | S_IWUSR); + +struct csvt_ctrl_dev { + struct mutex dev_lock; + + /* input control lines (DSR, CTS, CD, RI) */ + unsigned int cbits_tolocal; + + /* output control lines (DTR, RTS) */ + unsigned int cbits_tomdm; +}; + +static const struct usb_device_id id_table[] = { + { USB_DEVICE_AND_INTERFACE_INFO(0x05c6 , 0x904c, 0xff, 0xfe, 0xff)}, + {}, /* terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, id_table); + +static struct usb_driver csvt_driver = { + .name = "qc_csvt", + .probe = usb_serial_probe, + .disconnect = usb_serial_disconnect, + .id_table = id_table, + .suspend = usb_serial_suspend, + .resume = usb_serial_resume, + .supports_autosuspend = true, +}; + +#define CSVT_IFC_NUM 4 + +static int csvt_probe(struct usb_serial *serial, const struct usb_device_id *id) +{ + struct usb_host_interface *intf = + serial->interface->cur_altsetting; + + pr_debug("%s:\n", __func__); + + if (intf->desc.bInterfaceNumber != CSVT_IFC_NUM) + return -ENODEV; + + usb_enable_autosuspend(serial->dev); + + return 0; +} + +static int csvt_ctrl_write_cmd(struct csvt_ctrl_dev *dev, + struct usb_serial_port *port) +{ + struct usb_device *udev = port->serial->dev; + struct usb_interface *iface = port->serial->interface; + unsigned int iface_num; + int retval = 0; + + retval = usb_autopm_get_interface(iface); + if (retval < 0) { + dev_err(&port->dev, "%s: Unable to resume interface: %d\n", + __func__, retval); + return retval; + } + + dev_dbg(&port->dev, "%s: cbits to mdm 0x%x\n", __func__, + dev->cbits_tomdm); + + iface_num = iface->cur_altsetting->desc.bInterfaceNumber; + + retval = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), + USB_CDC_REQ_SET_CONTROL_LINE_STATE, + (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE), + dev->cbits_tomdm, + iface_num, + NULL, 0, USB_CTRL_SET_TIMEOUT); + usb_autopm_put_interface(iface); + + return retval; +} + +static void csvt_ctrl_dtr_rts(struct usb_serial_port *port, int on) +{ + struct csvt_ctrl_dev *dev = usb_get_serial_port_data(port); + + if (!dev) + return; + + dev_dbg(&port->dev, "%s", __func__); + + mutex_lock(&dev->dev_lock); + if (on) { + dev->cbits_tomdm |= CSVT_CTRL_DTR; + dev->cbits_tomdm |= CSVT_CTRL_RTS; + } else { + dev->cbits_tomdm &= ~CSVT_CTRL_DTR; + dev->cbits_tomdm &= ~CSVT_CTRL_RTS; + } + mutex_unlock(&dev->dev_lock); + + csvt_ctrl_write_cmd(dev, port); +} + +static int get_serial_info(struct usb_serial_port *port, + struct serial_struct __user *retinfo) +{ + struct serial_struct tmp; + + if (!retinfo) + return -EFAULT; + + memset(&tmp, 0, sizeof(tmp)); + tmp.line = port->serial->minor; + tmp.port = port->number; + tmp.baud_base = tty_get_baud_rate(port->port.tty); + tmp.close_delay = port->port.close_delay / 10; + tmp.closing_wait = + port->port.closing_wait == ASYNC_CLOSING_WAIT_NONE ? + ASYNC_CLOSING_WAIT_NONE : + port->port.closing_wait / 10; + + if (copy_to_user(retinfo, &tmp, sizeof(*retinfo))) + return -EFAULT; + return 0; +} + +static int set_serial_info(struct usb_serial_port *port, + struct serial_struct __user *newinfo) +{ + struct serial_struct new_serial; + unsigned int closing_wait; + unsigned int close_delay; + int retval = 0; + + if (copy_from_user(&new_serial, newinfo, sizeof(new_serial))) + return -EFAULT; + + close_delay = new_serial.close_delay * 10; + closing_wait = new_serial.closing_wait == ASYNC_CLOSING_WAIT_NONE ? + ASYNC_CLOSING_WAIT_NONE : new_serial.closing_wait * 10; + + mutex_lock(&port->port.mutex); + + if (!capable(CAP_SYS_ADMIN)) { + if ((close_delay != port->port.close_delay) || + (closing_wait != port->port.closing_wait)) + retval = -EPERM; + else + retval = -EOPNOTSUPP; + } else { + port->port.close_delay = close_delay; + port->port.closing_wait = closing_wait; + } + + mutex_unlock(&port->port.mutex); + return retval; +} + +static int csvt_ctrl_ioctl(struct tty_struct *tty, unsigned int cmd, + unsigned long arg) +{ + struct usb_serial_port *port = tty->driver_data; + + dev_dbg(&port->dev, "%s cmd 0x%04x", __func__, cmd); + + switch (cmd) { + case TIOCGSERIAL: + return get_serial_info(port, + (struct serial_struct __user *) arg); + case TIOCSSERIAL: + return set_serial_info(port, + (struct serial_struct __user *) arg); + default: + break; + } + + dev_err(&port->dev, "%s arg not supported", __func__); + + return -ENOIOCTLCMD; +} + +static int csvt_ctrl_tiocmget(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct csvt_ctrl_dev *dev = usb_get_serial_port_data(port); + unsigned int control_state = 0; + + if (!dev) + return -ENODEV; + + mutex_lock(&dev->dev_lock); + control_state = (dev->cbits_tomdm & CSVT_CTRL_DTR ? TIOCM_DTR : 0) | + (dev->cbits_tomdm & CSVT_CTRL_RTS ? TIOCM_RTS : 0) | + (dev->cbits_tolocal & CSVT_CTRL_DSR ? TIOCM_DSR : 0) | + (dev->cbits_tolocal & CSVT_CTRL_RI ? TIOCM_RI : 0) | + (dev->cbits_tolocal & CSVT_CTRL_CD ? TIOCM_CD : 0) | + (dev->cbits_tolocal & CSVT_CTRL_CTS ? TIOCM_CTS : 0); + mutex_unlock(&dev->dev_lock); + + dev_dbg(&port->dev, "%s -- %x", __func__, control_state); + + return control_state; +} + +static int csvt_ctrl_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + struct usb_serial_port *port = tty->driver_data; + struct csvt_ctrl_dev *dev = usb_get_serial_port_data(port); + + if (!dev) + return -ENODEV; + + dev_dbg(&port->dev, "%s\n", __func__); + + mutex_lock(&dev->dev_lock); + if (set & CSVT_CTRL_DTR) + dev->cbits_tomdm |= TIOCM_DTR; + if (set & CSVT_CTRL_RTS) + dev->cbits_tomdm |= TIOCM_RTS; + + if (clear & CSVT_CTRL_DTR) + dev->cbits_tomdm &= ~TIOCM_DTR; + if (clear & CSVT_CTRL_RTS) + dev->cbits_tomdm &= ~TIOCM_RTS; + mutex_unlock(&dev->dev_lock); + + return csvt_ctrl_write_cmd(dev, port); +} + +static void csvt_ctrl_set_termios(struct tty_struct *tty, + struct usb_serial_port *port, + struct ktermios *old_termios) +{ + struct csvt_ctrl_dev *dev = usb_get_serial_port_data(port); + + if (!dev) + return; + + dev_dbg(&port->dev, "%s", __func__); + + /* Doesn't support option setting */ + tty_termios_copy_hw(tty->termios, old_termios); + + csvt_ctrl_write_cmd(dev, port); +} + +static void csvt_ctrl_int_cb(struct urb *urb) +{ + int status; + struct usb_cdc_notification *ctrl; + struct usb_serial_port *port = urb->context; + struct csvt_ctrl_dev *dev; + unsigned int ctrl_bits; + unsigned char *data; + + switch (urb->status) { + case 0: + /*success*/ + break; + case -ESHUTDOWN: + case -ENOENT: + case -ECONNRESET: + case -EPROTO: + /* unplug */ + return; + case -EPIPE: + dev_err(&port->dev, "%s: stall on int endpoint\n", __func__); + /* TBD : halt to be cleared in work */ + case -EOVERFLOW: + default: + pr_debug_ratelimited("%s: non zero urb status = %d\n", + __func__, urb->status); + goto resubmit_int_urb; + } + + dev = usb_get_serial_port_data(port); + if (!dev) + return; + + ctrl = urb->transfer_buffer; + data = (unsigned char *)(ctrl + 1); + + usb_serial_debug_data(debug, &port->dev, __func__, + urb->actual_length, data); + + switch (ctrl->bNotificationType) { + case USB_CDC_NOTIFY_NETWORK_CONNECTION: + dev_dbg(&port->dev, "%s network\n", ctrl->wValue ? + "connected to" : "disconnected from"); + break; + case USB_CDC_NOTIFY_SERIAL_STATE: + ctrl_bits = get_unaligned_le16(data); + dev_dbg(&port->dev, "serial state: %d\n", ctrl_bits); + dev->cbits_tolocal = ctrl_bits; + break; + default: + dev_err(&port->dev, "%s: unknown notification %d received:" + "index %d len %d data0 %d data1 %d", + __func__, ctrl->bNotificationType, ctrl->wIndex, + ctrl->wLength, data[0], data[1]); + } + +resubmit_int_urb: + status = usb_submit_urb(urb, GFP_ATOMIC); + if (status) + dev_err(&port->dev, "%s: Error re-submitting Int URB %d\n", + __func__, status); + +} + +static int csvt_ctrl_open(struct tty_struct *tty, + struct usb_serial_port *port) +{ + int retval; + + dev_dbg(&port->dev, "%s port %d", __func__, port->number); + + retval = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL); + if (retval) { + dev_err(&port->dev, "usb_submit_urb(read int) failed\n"); + return retval; + } + + retval = usb_serial_generic_open(tty, port); + if (retval) + usb_kill_urb(port->interrupt_in_urb); + + return retval; +} + +static void csvt_ctrl_close(struct usb_serial_port *port) +{ + dev_dbg(&port->dev, "%s port %d", __func__, port->number); + + usb_serial_generic_close(port); + usb_kill_urb(port->interrupt_in_urb); +} + +static int csvt_ctrl_attach(struct usb_serial *serial) +{ + struct csvt_ctrl_dev *dev; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + mutex_init(&dev->dev_lock); + usb_set_serial_port_data(serial->port[0], dev); + + return 0; +} + +static void csvt_ctrl_release(struct usb_serial *serial) +{ + struct usb_serial_port *port = serial->port[0]; + struct csvt_ctrl_dev *dev = usb_get_serial_port_data(port); + + dev_dbg(&port->dev, "%s", __func__); + + kfree(dev); + usb_set_serial_port_data(port, NULL); +} + +static struct usb_serial_driver csvt_device = { + .driver = { + .owner = THIS_MODULE, + .name = "qc_csvt", + }, + .description = "qc_csvt", + .id_table = id_table, + .usb_driver = &csvt_driver, + .num_ports = 1, + .open = csvt_ctrl_open, + .close = csvt_ctrl_close, + .probe = csvt_probe, + .dtr_rts = csvt_ctrl_dtr_rts, + .tiocmget = csvt_ctrl_tiocmget, + .tiocmset = csvt_ctrl_tiocmset, + .ioctl = csvt_ctrl_ioctl, + .set_termios = csvt_ctrl_set_termios, + .read_int_callback = csvt_ctrl_int_cb, + .attach = csvt_ctrl_attach, + .release = csvt_ctrl_release, +}; + +static int __init csvt_init(void) +{ + int retval; + + retval = usb_serial_register(&csvt_device); + if (retval) { + err("%s: usb serial register failed\n", __func__); + return retval; + } + + retval = usb_register(&csvt_driver); + if (retval) { + usb_serial_deregister(&csvt_device); + err("%s: usb register failed\n", __func__); + return retval; + } + + return 0; +} + +static void __exit csvt_exit(void) +{ + usb_deregister(&csvt_driver); + usb_serial_deregister(&csvt_device); +} + +module_init(csvt_init); +module_exit(csvt_exit); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/serial/qcserial.c b/drivers/usb/serial/qcserial.c index a89c0ac..879dd87 100644 --- a/drivers/usb/serial/qcserial.c +++ b/drivers/usb/serial/qcserial.c @@ -16,6 +16,9 @@ #include <linux/usb.h> #include <linux/usb/serial.h> #include <linux/slab.h> +#ifdef CONFIG_MDM_HSIC_PM +#include "qcserial.h" +#endif #include "usb-wwan.h" #define DRIVER_AUTHOR "Qualcomm Inc" @@ -105,6 +108,8 @@ static const struct usb_device_id id_table[] = { {USB_DEVICE(0x413c, 0x8193)}, /* Dell Gobi 3000 QDL */ {USB_DEVICE(0x413c, 0x8194)}, /* Dell Gobi 3000 Composite */ {USB_DEVICE(0x1199, 0x9013)}, /* Sierra Wireless Gobi 3000 Modem device (MC8355) */ + {USB_DEVICE(0x05c6, 0x9048)}, /* MDM9x15 device */ + {USB_DEVICE(0x05c6, 0x904C)}, /* MDM9x15 device */ {USB_DEVICE(0x12D1, 0x14F0)}, /* Sony Gobi 3000 QDL */ {USB_DEVICE(0x12D1, 0x14F1)}, /* Sony Gobi 3000 Composite */ { } /* Terminating entry */ @@ -120,9 +125,26 @@ static struct usb_driver qcdriver = { .id_table = id_table, .suspend = usb_serial_suspend, .resume = usb_serial_resume, + .reset_resume = usb_serial_resume, .supports_autosuspend = true, }; +#ifdef CONFIG_MDM_HSIC_PM +void check_chip_configuration(char *product) +{ + if (!product) + return; + + pr_info("%s: usb product name = %s\n", __func__, product); + + if (!strncmp(product, PRODUCT_DLOAD, sizeof(PRODUCT_DLOAD))) + mdm_set_chip_configuration(true); + else if (!strncmp(product, PRODUCT_SAHARA, sizeof(PRODUCT_SAHARA))) + mdm_set_chip_configuration(false); + else + panic("UnKnown MDM product"); +} +#endif static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id) { struct usb_wwan_intf_private *data; @@ -146,9 +168,17 @@ static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id) return -ENOMEM; spin_lock_init(&data->susp_lock); - +#ifdef CONFIG_MDM_HSIC_PM + if (id->idVendor == 0x05c6 && id->idProduct == 0x9008) + check_chip_configuration(serial->dev->product); + + if (id->idVendor == 0x05c6 && + (id->idProduct == 0x9008 || id->idProduct == 0x9048 || + id->idProduct == 0x904c)) + goto set_interface; usb_enable_autosuspend(serial->dev); - +set_interface: +#endif switch (nintf) { case 1: /* QDL mode */ diff --git a/drivers/usb/serial/qcserial.h b/drivers/usb/serial/qcserial.h new file mode 100644 index 0000000..8504173 --- /dev/null +++ b/drivers/usb/serial/qcserial.h @@ -0,0 +1,10 @@ +#if !defined(_QCSERIAL_H_) +#define _QCSERIAL_H_ +#include <linux/wakelock.h> + +#define PRODUCT_DLOAD "QHSUSB_DLOAD" +#define PRODUCT_SAHARA "QHSUSB__BULK" + +extern void mdm_set_chip_configuration(bool dload); + +#endif diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c index 5d7b71b..9667684 100644 --- a/drivers/usb/serial/usb-serial.c +++ b/drivers/usb/serial/usb-serial.c @@ -355,9 +355,10 @@ static int serial_write(struct tty_struct *tty, const unsigned char *buf, if (port->serial->dev->state == USB_STATE_NOTATTACHED) goto exit; - - dbg("%s - port %d, %d byte(s)", __func__, port->number, count); - +#ifdef CONFIG_MACH_M0 + printk(KERN_INFO "%s - port %d, %d byte(s)", + __func__, port->number, count); +#endif /* pass on to the driver specific version of this function */ retval = port->serial->type->write(tty, port, buf, count); diff --git a/drivers/usb/serial/usb-wwan.h b/drivers/usb/serial/usb-wwan.h index de8d490..a99577b 100644 --- a/drivers/usb/serial/usb-wwan.h +++ b/drivers/usb/serial/usb-wwan.h @@ -31,7 +31,11 @@ extern int usb_wwan_resume(struct usb_serial *serial); /* per port private data */ +#ifdef CONFIG_MDM_HSIC_PM +#define N_IN_URB 1 +#else #define N_IN_URB 5 +#endif #define N_OUT_URB 5 #define IN_BUFLEN 65536 #define OUT_BUFLEN 65536 diff --git a/drivers/usb/serial/usb_wwan.c b/drivers/usb/serial/usb_wwan.c index 2a9d21d..2e2a60d 100644 --- a/drivers/usb/serial/usb_wwan.c +++ b/drivers/usb/serial/usb_wwan.c @@ -219,6 +219,11 @@ int usb_wwan_write(struct tty_struct *tty, struct usb_serial_port *port, dbg("%s: write (%d chars)", __func__, count); +#ifdef CONFIG_MDM_HSIC_PM + if (port->serial->dev->actconfig->desc.bNumInterfaces == 9) + pr_info("%s: write (%d chars)", __func__, count); +#endif + i = 0; left = count; for (i = 0; left > 0 && i < N_OUT_URB; i++) { @@ -237,6 +242,8 @@ int usb_wwan_write(struct tty_struct *tty, struct usb_serial_port *port, dbg("%s: endpoint %d buf %d", __func__, usb_pipeendpoint(this_urb->pipe), i); + usb_mark_last_busy( + interface_to_usbdev(port->serial->interface)); err = usb_autopm_get_interface_async(port->serial->interface); if (err < 0) break; @@ -285,29 +292,61 @@ static void usb_wwan_indat_callback(struct urb *urb) struct tty_struct *tty; unsigned char *data = urb->transfer_buffer; int status = urb->status; + int rx_len = urb->actual_length; + struct usb_wwan_intf_private *intfdata; dbg("%s: %p", __func__, urb); endpoint = usb_pipeendpoint(urb->pipe); port = urb->context; + intfdata = port->serial->private; + usb_mark_last_busy(interface_to_usbdev(port->serial->interface)); if (status) { dbg("%s: nonzero status: %d on endpoint %02x.", __func__, status, endpoint); +#ifdef CONFIG_MDM_HSIC_PM + if (status == -ENOENT && (urb->actual_length > 0)) { + pr_info("%s: handle dropped packet\n", __func__); + goto handle_rx; + } +#endif } else { +#ifdef CONFIG_MDM_HSIC_PM +handle_rx: +#endif tty = tty_port_tty_get(&port->port); if (tty) { - if (urb->actual_length) { - tty_insert_flip_string(tty, data, - urb->actual_length); + if (urb->actual_length > 0) { +#ifdef CONFIG_MDM_HSIC_PM + struct usb_device *udev = port->serial->dev; + /* corner case : efs packet rx at rpm suspend + * it can control twice in racing condition + * rx call back and suspend, both of then ca + * call this function , so clear the packet + * length once it handled + */ + urb->status = -EINPROGRESS; + urb->actual_length = 0; + if (udev->actconfig->desc.bNumInterfaces == 9) + pr_info("%s: read urb received : %d\n", + __func__, rx_len); +#endif + tty_insert_flip_string(tty, data, rx_len); tty_flip_buffer_push(tty); } else dbg("%s: empty read urb received", __func__); tty_kref_put(tty); - } + } else + return; +#ifdef CONFIG_MDM_HSIC_PM + /* do not re-submit urb for no entry status */ + if (status == -ENOENT) + return; +#endif /* Resubmit urb so we continue receiving */ - if (status != -ESHUTDOWN) { + if (status != -ESHUTDOWN || !intfdata->suspended) { err = usb_submit_urb(urb, GFP_ATOMIC); if (err) { if (err != -EPERM) { @@ -319,6 +358,8 @@ static void usb_wwan_indat_callback(struct urb *urb) } else { usb_mark_last_busy(port->serial->dev); } + usb_mark_last_busy( + interface_to_usbdev(port->serial->interface)); } } @@ -338,6 +379,7 @@ static void usb_wwan_outdat_callback(struct urb *urb) usb_serial_port_softint(port); usb_autopm_put_interface_async(port->serial->interface); + usb_mark_last_busy(interface_to_usbdev(port->serial->interface)); portdata = usb_get_serial_port_data(port); spin_lock(&intfdata->susp_lock); intfdata->in_flight--; @@ -405,6 +447,7 @@ int usb_wwan_open(struct tty_struct *tty, struct usb_serial_port *port) portdata = usb_get_serial_port_data(port); intfdata = serial->private; + intfdata->suspended = 0; /* explicitly set the driver mode to raw */ tty->raw = 1; @@ -582,8 +625,7 @@ bail_out_error2: kfree(portdata->out_buffer[j]); bail_out_error: for (j = 0; j < N_IN_URB; j++) - if (portdata->in_buffer[j]) - free_page((unsigned long)portdata->in_buffer[j]); + kfree(portdata->in_buffer[j]); kfree(portdata); return 1; } @@ -608,8 +650,10 @@ static void stop_read_write_urbs(struct usb_serial *serial) void usb_wwan_disconnect(struct usb_serial *serial) { + struct usb_wwan_intf_private *intfdata = serial->private; dbg("%s", __func__); + intfdata->suspended = 1; stop_read_write_urbs(serial); } EXPORT_SYMBOL(usb_wwan_disconnect); @@ -629,8 +673,7 @@ void usb_wwan_release(struct usb_serial *serial) for (j = 0; j < N_IN_URB; j++) { usb_free_urb(portdata->in_urbs[j]); - free_page((unsigned long) - portdata->in_buffer[j]); + kfree(portdata->in_buffer[j]); portdata->in_urbs[j] = NULL; } for (j = 0; j < N_OUT_URB; j++) { @@ -736,6 +779,10 @@ int usb_wwan_resume(struct usb_serial *serial) } } + spin_lock_irq(&intfdata->susp_lock); + intfdata->suspended = 0; + spin_unlock_irq(&intfdata->susp_lock); + for (i = 0; i < serial->num_ports; i++) { /* walk all ports */ port = serial->port[i]; @@ -761,9 +808,6 @@ int usb_wwan_resume(struct usb_serial *serial) play_delayed(port); spin_unlock_irq(&intfdata->susp_lock); } - spin_lock_irq(&intfdata->susp_lock); - intfdata->suspended = 0; - spin_unlock_irq(&intfdata->susp_lock); err_out: return err; } |