aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb')
-rw-r--r--drivers/usb/core/driver.c19
-rw-r--r--drivers/usb/core/hub.c54
-rw-r--r--drivers/usb/core/quirks.c4
-rw-r--r--drivers/usb/core/sec-dock.h133
-rw-r--r--drivers/usb/gadget/Makefile2
-rw-r--r--drivers/usb/gadget/android.c220
-rw-r--r--drivers/usb/gadget/composite.c23
-rw-r--r--drivers/usb/gadget/exynos_ss_udc.c2
-rw-r--r--drivers/usb/gadget/exynos_ss_udc.h2
-rw-r--r--drivers/usb/gadget/f_accessory.c404
-rw-r--r--drivers/usb/gadget/f_adb.c14
-rw-r--r--drivers/usb/gadget/f_audio_source.c825
-rw-r--r--drivers/usb/gadget/f_diag.c764
-rw-r--r--drivers/usb/gadget/f_diag.h24
-rw-r--r--drivers/usb/gadget/f_mass_storage.c364
-rw-r--r--drivers/usb/gadget/f_mtp.h17
-rw-r--r--drivers/usb/gadget/f_mtp_samsung.c181
-rw-r--r--drivers/usb/gadget/multi_config.c10
-rw-r--r--drivers/usb/gadget/multi_config.h7
-rw-r--r--drivers/usb/gadget/s3c_udc.h4
-rw-r--r--drivers/usb/gadget/s3c_udc_otg.c66
-rw-r--r--drivers/usb/gadget/s3c_udc_otg_xfer_dma.c6
-rw-r--r--drivers/usb/gadget/storage_common.c5
-rw-r--r--drivers/usb/gadget/u_composite_notifier.c40
-rw-r--r--drivers/usb/host/ehci-hcd.c27
-rw-r--r--drivers/usb/host/ehci-hub.c57
-rw-r--r--drivers/usb/host/ehci-q.c18
-rw-r--r--drivers/usb/host/ehci-s5p.c165
-rw-r--r--drivers/usb/host/ehci.h3
-rw-r--r--drivers/usb/host/ohci-s5p.c26
-rw-r--r--drivers/usb/misc/diag_bridge.c154
-rw-r--r--drivers/usb/misc/exynos-usb-switch.c6
-rw-r--r--drivers/usb/misc/mdm_ctrl_bridge.c115
-rw-r--r--drivers/usb/misc/mdm_data_bridge.c71
-rw-r--r--drivers/usb/serial/Kconfig9
-rw-r--r--drivers/usb/serial/Makefile1
-rw-r--r--drivers/usb/serial/csvt.c446
-rw-r--r--drivers/usb/serial/qcserial.c34
-rw-r--r--drivers/usb/serial/qcserial.h10
-rw-r--r--drivers/usb/serial/usb-serial.c7
-rw-r--r--drivers/usb/serial/usb-wwan.h4
-rw-r--r--drivers/usb/serial/usb_wwan.c68
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;
}