unsigned short                  report_desc_length;
        char                            *report_desc;
        unsigned short                  report_length;
+       /*
+        * use_out_ep - if true, the OUT Endpoint (interrupt out method)
+        *              will be used to receive reports from the host
+        *              using functions with the "intout" suffix.
+        *              Otherwise, the OUT Endpoint will not be configured
+        *              and the SETUP/SET_REPORT method ("ssreport" suffix)
+        *              will be used to receive reports.
+        */
+       bool                            use_out_ep;
 
        /* recv report */
-       struct list_head                completed_out_req;
        spinlock_t                      read_spinlock;
        wait_queue_head_t               read_queue;
+       /* recv report - interrupt out only (use_out_ep == 1) */
+       struct list_head                completed_out_req;
        unsigned int                    qlen;
+       /* recv report - setup set_report only (use_out_ep == 0) */
+       char                            *set_report_buf;
+       unsigned int                    set_report_length;
 
        /* send report */
        spinlock_t                      write_spinlock;
        .bDescriptorType        = USB_DT_INTERFACE,
        /* .bInterfaceNumber    = DYNAMIC */
        .bAlternateSetting      = 0,
-       .bNumEndpoints          = 2,
+       /* .bNumEndpoints       = DYNAMIC (depends on use_out_ep) */
        .bInterfaceClass        = USB_CLASS_HID,
        /* .bInterfaceSubClass  = DYNAMIC */
        /* .bInterfaceProtocol  = DYNAMIC */
        /* .wBytesPerInterval   = DYNAMIC */
 };
 
-static struct usb_descriptor_header *hidg_ss_descriptors[] = {
+static struct usb_descriptor_header *hidg_ss_descriptors_intout[] = {
        (struct usb_descriptor_header *)&hidg_interface_desc,
        (struct usb_descriptor_header *)&hidg_desc,
        (struct usb_descriptor_header *)&hidg_ss_in_ep_desc,
        NULL,
 };
 
+static struct usb_descriptor_header *hidg_ss_descriptors_ssreport[] = {
+       (struct usb_descriptor_header *)&hidg_interface_desc,
+       (struct usb_descriptor_header *)&hidg_desc,
+       (struct usb_descriptor_header *)&hidg_ss_in_ep_desc,
+       (struct usb_descriptor_header *)&hidg_ss_in_comp_desc,
+       NULL,
+};
+
 /* High-Speed Support */
 
 static struct usb_endpoint_descriptor hidg_hs_in_ep_desc = {
                                      */
 };
 
-static struct usb_descriptor_header *hidg_hs_descriptors[] = {
+static struct usb_descriptor_header *hidg_hs_descriptors_intout[] = {
        (struct usb_descriptor_header *)&hidg_interface_desc,
        (struct usb_descriptor_header *)&hidg_desc,
        (struct usb_descriptor_header *)&hidg_hs_in_ep_desc,
        NULL,
 };
 
+static struct usb_descriptor_header *hidg_hs_descriptors_ssreport[] = {
+       (struct usb_descriptor_header *)&hidg_interface_desc,
+       (struct usb_descriptor_header *)&hidg_desc,
+       (struct usb_descriptor_header *)&hidg_hs_in_ep_desc,
+       NULL,
+};
+
 /* Full-Speed Support */
 
 static struct usb_endpoint_descriptor hidg_fs_in_ep_desc = {
                                       */
 };
 
-static struct usb_descriptor_header *hidg_fs_descriptors[] = {
+static struct usb_descriptor_header *hidg_fs_descriptors_intout[] = {
        (struct usb_descriptor_header *)&hidg_interface_desc,
        (struct usb_descriptor_header *)&hidg_desc,
        (struct usb_descriptor_header *)&hidg_fs_in_ep_desc,
        NULL,
 };
 
+static struct usb_descriptor_header *hidg_fs_descriptors_ssreport[] = {
+       (struct usb_descriptor_header *)&hidg_interface_desc,
+       (struct usb_descriptor_header *)&hidg_desc,
+       (struct usb_descriptor_header *)&hidg_fs_in_ep_desc,
+       NULL,
+};
+
 /*-------------------------------------------------------------------------*/
 /*                                 Strings                                 */
 
 /*-------------------------------------------------------------------------*/
 /*                              Char Device                                */
 
-static ssize_t f_hidg_read(struct file *file, char __user *buffer,
-                       size_t count, loff_t *ptr)
+static ssize_t f_hidg_intout_read(struct file *file, char __user *buffer,
+                                 size_t count, loff_t *ptr)
 {
        struct f_hidg *hidg = file->private_data;
        struct f_hidg_req_list *list;
 
        spin_lock_irqsave(&hidg->read_spinlock, flags);
 
-#define READ_COND (!list_empty(&hidg->completed_out_req))
+#define READ_COND_INTOUT (!list_empty(&hidg->completed_out_req))
 
        /* wait for at least one buffer to complete */
-       while (!READ_COND) {
+       while (!READ_COND_INTOUT) {
                spin_unlock_irqrestore(&hidg->read_spinlock, flags);
                if (file->f_flags & O_NONBLOCK)
                        return -EAGAIN;
 
-               if (wait_event_interruptible(hidg->read_queue, READ_COND))
+               if (wait_event_interruptible(hidg->read_queue, READ_COND_INTOUT))
                        return -ERESTARTSYS;
 
                spin_lock_irqsave(&hidg->read_spinlock, flags);
        return count;
 }
 
+#define READ_COND_SSREPORT (hidg->set_report_buf != NULL)
+
+static ssize_t f_hidg_ssreport_read(struct file *file, char __user *buffer,
+                                   size_t count, loff_t *ptr)
+{
+       struct f_hidg *hidg = file->private_data;
+       char *tmp_buf = NULL;
+       unsigned long flags;
+
+       if (!count)
+               return 0;
+
+       spin_lock_irqsave(&hidg->read_spinlock, flags);
+
+       while (!READ_COND_SSREPORT) {
+               spin_unlock_irqrestore(&hidg->read_spinlock, flags);
+               if (file->f_flags & O_NONBLOCK)
+                       return -EAGAIN;
+
+               if (wait_event_interruptible(hidg->read_queue, READ_COND_SSREPORT))
+                       return -ERESTARTSYS;
+
+               spin_lock_irqsave(&hidg->read_spinlock, flags);
+       }
+
+       count = min_t(unsigned int, count, hidg->set_report_length);
+       tmp_buf = hidg->set_report_buf;
+       hidg->set_report_buf = NULL;
+
+       spin_unlock_irqrestore(&hidg->read_spinlock, flags);
+
+       if (tmp_buf != NULL) {
+               count -= copy_to_user(buffer, tmp_buf, count);
+               kfree(tmp_buf);
+       } else {
+               count = -ENOMEM;
+       }
+
+       wake_up(&hidg->read_queue);
+
+       return count;
+}
+
+static ssize_t f_hidg_read(struct file *file, char __user *buffer,
+                          size_t count, loff_t *ptr)
+{
+       struct f_hidg *hidg = file->private_data;
+
+       if (hidg->use_out_ep)
+               return f_hidg_intout_read(file, buffer, count, ptr);
+       else
+               return f_hidg_ssreport_read(file, buffer, count, ptr);
+}
+
 static void f_hidg_req_complete(struct usb_ep *ep, struct usb_request *req)
 {
        struct f_hidg *hidg = (struct f_hidg *)ep->driver_data;
        if (WRITE_COND)
                ret |= EPOLLOUT | EPOLLWRNORM;
 
-       if (READ_COND)
-               ret |= EPOLLIN | EPOLLRDNORM;
+       if (hidg->use_out_ep) {
+               if (READ_COND_INTOUT)
+                       ret |= EPOLLIN | EPOLLRDNORM;
+       } else {
+               if (READ_COND_SSREPORT)
+                       ret |= EPOLLIN | EPOLLRDNORM;
+       }
 
        return ret;
 }
 
 #undef WRITE_COND
-#undef READ_COND
+#undef READ_COND_SSREPORT
+#undef READ_COND_INTOUT
 
 static int f_hidg_release(struct inode *inode, struct file *fd)
 {
        return alloc_ep_req(ep, length);
 }
 
-static void hidg_set_report_complete(struct usb_ep *ep, struct usb_request *req)
+static void hidg_intout_complete(struct usb_ep *ep, struct usb_request *req)
 {
        struct f_hidg *hidg = (struct f_hidg *) req->context;
        struct usb_composite_dev *cdev = hidg->func.config->cdev;
        }
 }
 
+static void hidg_ssreport_complete(struct usb_ep *ep, struct usb_request *req)
+{
+       struct f_hidg *hidg = (struct f_hidg *)req->context;
+       struct usb_composite_dev *cdev = hidg->func.config->cdev;
+       char *new_buf = NULL;
+       unsigned long flags;
+
+       if (req->status != 0 || req->buf == NULL || req->actual == 0) {
+               ERROR(cdev,
+                     "%s FAILED: status=%d, buf=%p, actual=%d\n",
+                     __func__, req->status, req->buf, req->actual);
+               return;
+       }
+
+       spin_lock_irqsave(&hidg->read_spinlock, flags);
+
+       new_buf = krealloc(hidg->set_report_buf, req->actual, GFP_ATOMIC);
+       if (new_buf == NULL) {
+               spin_unlock_irqrestore(&hidg->read_spinlock, flags);
+               return;
+       }
+       hidg->set_report_buf = new_buf;
+
+       hidg->set_report_length = req->actual;
+       memcpy(hidg->set_report_buf, req->buf, req->actual);
+
+       spin_unlock_irqrestore(&hidg->read_spinlock, flags);
+
+       wake_up(&hidg->read_queue);
+}
+
 static int hidg_setup(struct usb_function *f,
                const struct usb_ctrlrequest *ctrl)
 {
        case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8
                  | HID_REQ_SET_REPORT):
                VDBG(cdev, "set_report | wLength=%d\n", ctrl->wLength);
-               goto stall;
+               if (hidg->use_out_ep)
+                       goto stall;
+               req->complete = hidg_ssreport_complete;
+               req->context  = hidg;
+               goto respond;
                break;
 
        case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8
        unsigned long flags;
 
        usb_ep_disable(hidg->in_ep);
-       usb_ep_disable(hidg->out_ep);
 
-       spin_lock_irqsave(&hidg->read_spinlock, flags);
-       list_for_each_entry_safe(list, next, &hidg->completed_out_req, list) {
-               free_ep_req(hidg->out_ep, list->req);
-               list_del(&list->list);
-               kfree(list);
+       if (hidg->out_ep) {
+               usb_ep_disable(hidg->out_ep);
+
+               spin_lock_irqsave(&hidg->read_spinlock, flags);
+               list_for_each_entry_safe(list, next, &hidg->completed_out_req, list) {
+                       free_ep_req(hidg->out_ep, list->req);
+                       list_del(&list->list);
+                       kfree(list);
+               }
+               spin_unlock_irqrestore(&hidg->read_spinlock, flags);
        }
-       spin_unlock_irqrestore(&hidg->read_spinlock, flags);
 
        spin_lock_irqsave(&hidg->write_spinlock, flags);
        if (!hidg->write_pending) {
                }
        }
 
-
-       if (hidg->out_ep != NULL) {
+       if (hidg->use_out_ep && hidg->out_ep != NULL) {
                /* restart endpoint */
                usb_ep_disable(hidg->out_ep);
 
                                        hidg_alloc_ep_req(hidg->out_ep,
                                                          hidg->report_length);
                        if (req) {
-                               req->complete = hidg_set_report_complete;
+                               req->complete = hidg_intout_complete;
                                req->context  = hidg;
                                status = usb_ep_queue(hidg->out_ep, req,
                                                      GFP_ATOMIC);
        }
        return 0;
 disable_out_ep:
-       usb_ep_disable(hidg->out_ep);
+       if (hidg->out_ep)
+               usb_ep_disable(hidg->out_ep);
 free_req_in:
        if (req_in)
                free_ep_req(hidg->in_ep, req_in);
                goto fail;
        hidg->in_ep = ep;
 
-       ep = usb_ep_autoconfig(c->cdev->gadget, &hidg_fs_out_ep_desc);
-       if (!ep)
-               goto fail;
-       hidg->out_ep = ep;
+       hidg->out_ep = NULL;
+       if (hidg->use_out_ep) {
+               ep = usb_ep_autoconfig(c->cdev->gadget, &hidg_fs_out_ep_desc);
+               if (!ep)
+                       goto fail;
+               hidg->out_ep = ep;
+       }
+
+       /* used only if use_out_ep == 1 */
+       hidg->set_report_buf = NULL;
 
        /* set descriptor dynamic values */
        hidg_interface_desc.bInterfaceSubClass = hidg->bInterfaceSubClass;
        hidg_interface_desc.bInterfaceProtocol = hidg->bInterfaceProtocol;
+       hidg_interface_desc.bNumEndpoints = hidg->use_out_ep ? 2 : 1;
        hidg->protocol = HID_REPORT_PROTOCOL;
        hidg->idle = 1;
        hidg_ss_in_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length);
        hidg_ss_out_ep_desc.bEndpointAddress =
                hidg_fs_out_ep_desc.bEndpointAddress;
 
-       status = usb_assign_descriptors(f, hidg_fs_descriptors,
-                       hidg_hs_descriptors, hidg_ss_descriptors,
-                       hidg_ss_descriptors);
+       if (hidg->use_out_ep)
+               status = usb_assign_descriptors(f,
+                       hidg_fs_descriptors_intout,
+                       hidg_hs_descriptors_intout,
+                       hidg_ss_descriptors_intout,
+                       hidg_ss_descriptors_intout);
+       else
+               status = usb_assign_descriptors(f,
+                       hidg_fs_descriptors_ssreport,
+                       hidg_hs_descriptors_ssreport,
+                       hidg_ss_descriptors_ssreport,
+                       hidg_ss_descriptors_ssreport);
+
        if (status)
                goto fail;
 
 
 F_HID_OPT(subclass, 8, 255);
 F_HID_OPT(protocol, 8, 255);
+F_HID_OPT(no_out_endpoint, 8, 1);
 F_HID_OPT(report_length, 16, 65535);
 
 static ssize_t f_hid_opts_report_desc_show(struct config_item *item, char *page)
 static struct configfs_attribute *hid_attrs[] = {
        &f_hid_opts_attr_subclass,
        &f_hid_opts_attr_protocol,
+       &f_hid_opts_attr_no_out_endpoint,
        &f_hid_opts_attr_report_length,
        &f_hid_opts_attr_report_desc,
        &f_hid_opts_attr_dev,
        hidg = func_to_hidg(f);
        opts = container_of(f->fi, struct f_hid_opts, func_inst);
        kfree(hidg->report_desc);
+       kfree(hidg->set_report_buf);
        kfree(hidg);
        mutex_lock(&opts->lock);
        --opts->refcnt;
                        return ERR_PTR(-ENOMEM);
                }
        }
+       hidg->use_out_ep = !opts->no_out_endpoint;
 
        mutex_unlock(&opts->lock);