mirror of
https://github.com/BobTheBlinker/android_kernel_motorola_sm6375.git
synced 2026-10-13 08:07:32 -04:00
usb: f_cdev: USB remote wake up feature implementation for DUN
Implement usb remote wake up feature for dun on sdx12. For super speed this feature supports wake up of a particular function from suspend, where as for high speed, remote wakeup doesn't support wakeup of a particular function from suspend, but entire usb bus needs to wakeup. When usb_func_wakeup in composite driver fails because it needs to wake usb bus before function wakeup is triggered, -EAGAIN is returned which cannot be considered as a function wakeup failure. Hence add proper handling in function driver for EAGAIN return case. Change-Id: I15c18a4a7203e82c74ad1b4905c08eff91d91b4f Signed-off-by: Rohith Kollalsi <rkollals@codeaurora.org>
This commit is contained in:
parent
de4a213901
commit
e3d4ff77a7
1 changed files with 132 additions and 21 deletions
|
|
@ -38,6 +38,7 @@
|
|||
#include <linux/module.h>
|
||||
#include <asm/ioctls.h>
|
||||
#include <asm-generic/termios.h>
|
||||
#include <linux/usb/dwc3-msm.h>
|
||||
|
||||
#define DEVICE_NAME "at_usb"
|
||||
#define MODULE_NAME "msm_usb_bridge"
|
||||
|
|
@ -99,6 +100,7 @@ struct f_cdev {
|
|||
struct list_head read_pool;
|
||||
struct list_head read_queued;
|
||||
struct list_head write_pool;
|
||||
struct list_head write_pending;
|
||||
|
||||
/* current active USB RX request */
|
||||
struct usb_request *current_rx_req;
|
||||
|
|
@ -127,6 +129,8 @@ struct f_cdev {
|
|||
struct workqueue_struct *fcdev_wq;
|
||||
bool is_connected;
|
||||
bool port_open;
|
||||
bool is_suspended;
|
||||
bool pending_state_notify;
|
||||
|
||||
unsigned long nbytes_from_host;
|
||||
unsigned long nbytes_to_host;
|
||||
|
|
@ -536,19 +540,56 @@ static int usb_cser_set_alt(struct usb_function *f, unsigned int intf,
|
|||
return rc;
|
||||
}
|
||||
|
||||
static int port_notify_serial_state(struct cserial *cser);
|
||||
|
||||
static void usb_cser_resume(struct usb_function *f)
|
||||
{
|
||||
struct f_cdev *port = func_to_port(f);
|
||||
struct usb_composite_dev *cdev = f->config->cdev;
|
||||
unsigned long flags;
|
||||
int ret;
|
||||
|
||||
if (cdev->gadget->speed >= USB_SPEED_SUPER && port->func_is_suspended) {
|
||||
if (port->func_wakeup_pending) {
|
||||
dev_dbg(&cdev->gadget->dev,
|
||||
"func_wakeup for port:%s\n", port->name);
|
||||
usb_func_wakeup(&port->port_usb.func);
|
||||
port->func_wakeup_pending = false;
|
||||
struct usb_request *req, *t;
|
||||
struct usb_ep *in;
|
||||
|
||||
pr_debug("%s\n", __func__);
|
||||
port->is_suspended = false;
|
||||
|
||||
/* process pending state notifications */
|
||||
if (port->pending_state_notify)
|
||||
port_notify_serial_state(&port->port_usb);
|
||||
|
||||
spin_lock_irqsave(&port->port_lock, flags);
|
||||
in = port->port_usb.in;
|
||||
/* process any pending requests */
|
||||
list_for_each_entry_safe(req, t, &port->write_pending, list) {
|
||||
list_del_init(&req->list);
|
||||
if (!port->is_connected) {
|
||||
pr_err("%s: cable is disconnected.\n", __func__);
|
||||
list_add(&req->list, &port->write_pool);
|
||||
spin_unlock_irqrestore(&port->port_lock, flags);
|
||||
return;
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&port->port_lock, flags);
|
||||
ret = usb_ep_queue(in, req, GFP_KERNEL);
|
||||
spin_lock_irqsave(&port->port_lock, flags);
|
||||
if (ret) {
|
||||
pr_err("EP QUEUE failed:%d\n", ret);
|
||||
list_add(&req->list, &port->write_pool);
|
||||
} else {
|
||||
port->nbytes_from_port_bridge += req->length;
|
||||
}
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&port->port_lock, flags);
|
||||
}
|
||||
|
||||
static void usb_cser_suspend(struct usb_function *f)
|
||||
{
|
||||
struct f_cdev *port = func_to_port(f);
|
||||
|
||||
pr_debug("%s\n", __func__);
|
||||
port->is_suspended = true;
|
||||
}
|
||||
|
||||
static int usb_cser_func_suspend(struct usb_function *f, u8 options)
|
||||
|
|
@ -557,11 +598,31 @@ static int usb_cser_func_suspend(struct usb_function *f, u8 options)
|
|||
|
||||
port->func_wakeup_allowed =
|
||||
!!(options & (USB_INTRF_FUNC_SUSPEND_RW >> 8));
|
||||
port->func_is_suspended = options & (USB_INTRF_FUNC_SUSPEND_LP >> 8);
|
||||
|
||||
if (options & (USB_INTRF_FUNC_SUSPEND_LP >> 8)) {
|
||||
if (!port->func_is_suspended) {
|
||||
usb_cser_suspend(f);
|
||||
port->func_is_suspended = true;
|
||||
} else {
|
||||
if (port->func_is_suspended) {
|
||||
port->func_is_suspended = false;
|
||||
usb_cser_resume(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int usb_cser_get_remote_wakeup_capable(struct usb_function *f,
|
||||
struct usb_gadget *g)
|
||||
{
|
||||
struct f_cdev *port = func_to_port(f);
|
||||
|
||||
return ((g->speed >= USB_SPEED_SUPER && port->func_wakeup_allowed) ||
|
||||
(g->speed < USB_SPEED_SUPER && usb_get_remote_wakeup_status(g)));
|
||||
|
||||
}
|
||||
|
||||
static int usb_cser_get_status(struct usb_function *f)
|
||||
{
|
||||
#ifdef CONFIG_USB_FUNC_WAKEUP_SUPPORTED
|
||||
|
|
@ -643,7 +704,16 @@ static int port_notify_serial_state(struct cserial *cser)
|
|||
unsigned long flags;
|
||||
struct usb_composite_dev *cdev = port->port_usb.func.config->cdev;
|
||||
|
||||
if (port->is_suspended) {
|
||||
port->pending_state_notify = true;
|
||||
pr_debug("%s: port is suspended\n", __func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&port->port_lock, flags);
|
||||
if (port->pending_state_notify)
|
||||
port->pending_state_notify = false;
|
||||
|
||||
if (!port->port_usb.pending) {
|
||||
port->port_usb.pending = true;
|
||||
spin_unlock_irqrestore(&port->port_lock, flags);
|
||||
|
|
@ -1133,6 +1203,7 @@ static void usb_cser_stop_io(struct f_cdev *port)
|
|||
usb_cser_free_requests(out, &port->read_queued);
|
||||
usb_cser_free_requests(out, &port->read_pool);
|
||||
usb_cser_free_requests(in, &port->write_pool);
|
||||
usb_cser_free_requests(in, &port->write_pending);
|
||||
spin_unlock_irqrestore(&port->port_lock, flags);
|
||||
}
|
||||
|
||||
|
|
@ -1298,6 +1369,9 @@ ssize_t f_cdev_write(struct file *file,
|
|||
struct list_head *pool;
|
||||
unsigned int xfer_size;
|
||||
struct usb_ep *in;
|
||||
struct cserial *cser;
|
||||
struct usb_function *func;
|
||||
struct usb_gadget *gadget;
|
||||
|
||||
port = file->private_data;
|
||||
if (!port) {
|
||||
|
|
@ -1305,6 +1379,9 @@ ssize_t f_cdev_write(struct file *file,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
cser = &port->port_usb;
|
||||
func = &cser->func;
|
||||
|
||||
spin_lock_irqsave(&port->port_lock, flags);
|
||||
pr_debug("write on port(%s)(%pK)\n", port->name, port);
|
||||
|
||||
|
|
@ -1336,9 +1413,40 @@ ssize_t f_cdev_write(struct file *file,
|
|||
if (ret) {
|
||||
pr_err("copy_from_user failed: err %d\n", ret);
|
||||
ret = -EFAULT;
|
||||
goto err_exit;
|
||||
}
|
||||
|
||||
req->length = xfer_size;
|
||||
req->zero = 1;
|
||||
if (port->is_suspended) {
|
||||
gadget = cser->func.config->cdev->gadget;
|
||||
if (!usb_cser_get_remote_wakeup_capable(func, gadget)) {
|
||||
pr_debug("%s remote-wakeup not capable\n",
|
||||
__func__);
|
||||
ret = -EOPNOTSUPP;
|
||||
goto err_exit;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&port->port_lock, flags);
|
||||
list_add(&req->list, &port->write_pending);
|
||||
spin_unlock_irqrestore(&port->port_lock, flags);
|
||||
|
||||
if (gadget->speed >= USB_SPEED_SUPER
|
||||
&& port->func_is_suspended)
|
||||
ret = usb_func_wakeup(func);
|
||||
else
|
||||
ret = usb_gadget_wakeup(gadget);
|
||||
|
||||
if (ret < 0 && ret != -EACCES && ret != -EAGAIN) {
|
||||
pr_err("Remote wakeup failed:%d\n", ret);
|
||||
spin_lock_irqsave(&port->port_lock, flags);
|
||||
req = list_first_entry(&port->write_pending,
|
||||
struct usb_request, list);
|
||||
list_del(&req->list);
|
||||
spin_unlock_irqrestore(&port->port_lock, flags);
|
||||
goto err_exit;
|
||||
}
|
||||
} else {
|
||||
req->length = xfer_size;
|
||||
req->zero = 1;
|
||||
ret = usb_ep_queue(in, req, GFP_KERNEL);
|
||||
if (ret) {
|
||||
pr_err("EP QUEUE failed:%d\n", ret);
|
||||
|
|
@ -1350,19 +1458,18 @@ ssize_t f_cdev_write(struct file *file,
|
|||
spin_unlock_irqrestore(&port->port_lock, flags);
|
||||
}
|
||||
|
||||
err_exit:
|
||||
if (ret) {
|
||||
spin_lock_irqsave(&port->port_lock, flags);
|
||||
/* USB cable is connected, add it back otherwise free request */
|
||||
if (port->is_connected)
|
||||
list_add(&req->list, &port->write_pool);
|
||||
else
|
||||
usb_cser_free_req(in, req);
|
||||
spin_unlock_irqrestore(&port->port_lock, flags);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return xfer_size;
|
||||
|
||||
err_exit:
|
||||
spin_lock_irqsave(&port->port_lock, flags);
|
||||
/* USB cable is connected, add it back otherwise free request */
|
||||
if (port->is_connected)
|
||||
list_add(&req->list, &port->write_pool);
|
||||
else
|
||||
usb_cser_free_req(in, req);
|
||||
spin_unlock_irqrestore(&port->port_lock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static unsigned int f_cdev_poll(struct file *file, poll_table *wait)
|
||||
|
|
@ -1598,6 +1705,8 @@ int usb_cser_connect(struct f_cdev *port)
|
|||
cser->pending = false;
|
||||
cser->q_again = false;
|
||||
port->is_connected = true;
|
||||
port->pending_state_notify = false;
|
||||
port->is_suspended = false;
|
||||
spin_unlock_irqrestore(&port->port_lock, flags);
|
||||
|
||||
usb_cser_start_io(port);
|
||||
|
|
@ -1799,6 +1908,7 @@ static struct f_cdev *f_cdev_alloc(char *func_name, int portno)
|
|||
INIT_LIST_HEAD(&port->read_pool);
|
||||
INIT_LIST_HEAD(&port->read_queued);
|
||||
INIT_LIST_HEAD(&port->write_pool);
|
||||
INIT_LIST_HEAD(&port->write_pending);
|
||||
|
||||
port->fcdev_wq = create_singlethread_workqueue(port->name);
|
||||
if (!port->fcdev_wq) {
|
||||
|
|
@ -2089,6 +2199,7 @@ static struct usb_function *cser_alloc(struct usb_function_instance *fi)
|
|||
port->port_usb.func.setup = usb_cser_setup;
|
||||
port->port_usb.func.func_suspend = usb_cser_func_suspend;
|
||||
port->port_usb.func.resume = usb_cser_resume;
|
||||
port->port_usb.func.suspend = usb_cser_suspend;
|
||||
port->port_usb.func.get_status = usb_cser_get_status;
|
||||
port->port_usb.func.free_func = usb_cser_free_func;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue