aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb/gadget
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/gadget')
-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
20 files changed, 2910 insertions, 70 deletions
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);