Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d46b9e1d authored by Chandana Kishori Chiluveru's avatar Chandana Kishori Chiluveru Committed by Hemant Kumar
Browse files

usb: f_cdev: Add function suspend support for serial interfaces



USB super-speed mode requires device to advertise remote wakeup
capability at function driver level called as function
wakeup capability. Host queries this using get_status and
uses set_feature to enable function remote wakeup before
initiating function suspend. This is required to initiate
remote wakeup as well which is failed by udc otherwise in SS mode.

Also Add debugfs node to initiate function remote wakeup for serial
interfaces. In USBCV Function remote wakeup enable test requires to
initiate function wakeup notification from the device. Hence add a
debugfs node to initiate function wakeup notification from the device.

Use below commands to initiate function wakeup notification
for Modem:
echo 1 > /sys/kernel/debug/at_usb0/remote_wakeup

for NMEA:
echo 1 > /sys/kernel/debug/at_usb1/remote_wakeup.

Change-Id: I1c4c02282a50dc96dd67b646788a5e08818c34c0
Signed-off-by: default avatarChandana Kishori Chiluveru <cchiluve@codeaurora.org>
parent 568e87ad
Loading
Loading
Loading
Loading
+155 −0
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/debugfs.h>
#include <linux/cdev.h>
#include <linux/spinlock.h>
#include <linux/usb/gadget.h>
@@ -126,6 +127,9 @@ struct f_cdev {
	unsigned long		nbytes_to_host;
	unsigned long           nbytes_to_port_bridge;
	unsigned long		nbytes_from_port_bridge;

	/* To test remote wakeup using debugfs */
	u8 debugfs_rw_enable;
};

struct f_cdev_opts {
@@ -135,6 +139,12 @@ struct f_cdev_opts {
	u8 port_num;
};

struct usb_cser_debugfs {
	struct dentry *debugfs_root;
};

static struct usb_cser_debugfs debugfs;

static int major, minors;
struct class *fcdev_classp;
static DEFINE_IDA(chardev_ida);
@@ -147,6 +157,7 @@ static int usb_cser_connect(struct f_cdev *port);
static void usb_cser_disconnect(struct f_cdev *port);
static struct f_cdev *f_cdev_alloc(char *func_name, int portno);
static void usb_cser_free_req(struct usb_ep *ep, struct usb_request *req);
static void usb_cser_debugfs_exit(void);

static struct usb_interface_descriptor cser_interface_desc = {
	.bLength =		USB_DT_INTERFACE_SIZE,
@@ -505,6 +516,32 @@ static int usb_cser_set_alt(struct usb_function *f, unsigned int intf,
	return rc;
}

static int usb_cser_func_suspend(struct usb_function *f, u8 options)
{
	bool func_wakeup_allowed;

	func_wakeup_allowed =
		((options & FUNC_SUSPEND_OPT_RW_EN_MASK) != 0);

	f->func_wakeup_allowed = func_wakeup_allowed;
	if (options & FUNC_SUSPEND_OPT_SUSP_MASK) {
		if (!f->func_is_suspended)
			f->func_is_suspended = true;
	} else {
		if (f->func_is_suspended)
			f->func_is_suspended = false;
	}
	return 0;
}

static int usb_cser_get_status(struct usb_function *f)
{
	bool remote_wakeup_en_status = f->func_wakeup_allowed ? 1 : 0;

	return (remote_wakeup_en_status << FUNC_WAKEUP_ENABLE_SHIFT) |
		(1 << FUNC_WAKEUP_CAPABLE_SHIFT);
}

static void usb_cser_disable(struct usb_function *f)
{
	struct f_cdev	*port = func_to_port(f);
@@ -823,6 +860,7 @@ static void cser_free_inst(struct usb_function_instance *fi)
		cdev_del(&opts->port->fcdev_cdev);
	}
	usb_cser_chardev_deinit();
	usb_cser_debugfs_exit();
	kfree(opts->func_name);
	kfree(opts->port);
	kfree(opts);
@@ -1500,6 +1538,119 @@ static const struct file_operations f_cdev_fops = {
	.compat_ioctl = f_cdev_ioctl,
};

static ssize_t cser_rw_write(struct file *file, const char __user *ubuf,
				size_t count, loff_t *ppos)
{
	struct seq_file *s = file->private_data;
	struct f_cdev *port = s->private;
	u8 input;
	struct cserial *cser;
	struct usb_function *func;
	struct usb_gadget *gadget;
	int ret;

	cser = &port->port_usb;
	if (!cser) {
		pr_err("cser is NULL\n");
		return -EINVAL;
	}

	if (!port->is_connected) {
		pr_debug("port disconnected\n");
		return -ENODEV;
	}

	func = &cser->func;
	if (!func) {
		pr_err("func is NULL\n");
		return -EINVAL;
	}

	if (ubuf == NULL) {
		pr_debug("buffer is Null.\n");
		goto err;
	}

	ret = kstrtou8_from_user(ubuf, count, 0, &input);
	if (ret) {
		pr_err("Invalid value. err:%d\n", ret);
		goto err;
	}

	if (port->debugfs_rw_enable == !!input) {
		if (!!input)
			pr_debug("RW already enabled\n");
		else
			pr_debug("RW already disabled\n");
		goto err;
	}

	port->debugfs_rw_enable = !!input;
	if (port->debugfs_rw_enable) {
		gadget = cser->func.config->cdev->gadget;
		if (gadget->speed == USB_SPEED_SUPER &&
			func->func_is_suspended) {
			pr_debug("Calling usb_func_wakeup\n");
			ret = usb_func_wakeup(func);
		} else {
			pr_debug("Calling usb_gadget_wakeup\n");
			ret = usb_gadget_wakeup(gadget);
		}

		if ((ret == -EBUSY) || (ret == -EAGAIN))
			pr_debug("RW delayed due to LPM exit.\n");
		else if (ret)
			pr_err("wakeup failed. ret=%d.\n", ret);
	} else {
		pr_debug("RW disabled.\n");
	}
err:
	return count;
}

static int usb_cser_rw_show(struct seq_file *s, void *unused)
{
	struct f_cdev *port = s->private;

	if (!port) {
		pr_err("port is null\n");
		return 0;
	}

	seq_printf(s, "%d\n", port->debugfs_rw_enable);

	return 0;
}

static int debug_cdev_rw_open(struct inode *inode, struct file *f)
{
	return single_open(f, usb_cser_rw_show, inode->i_private);
}

static const struct file_operations cser_rem_wakeup_fops = {
	.open = debug_cdev_rw_open,
	.read = seq_read,
	.write = cser_rw_write,
	.owner = THIS_MODULE,
	.llseek = seq_lseek,
	.release = seq_release,
};

static void usb_cser_debugfs_init(struct f_cdev *port)
{
	debugfs.debugfs_root = debugfs_create_dir(port->name, NULL);
	if (IS_ERR(debugfs.debugfs_root))
		return;

	debugfs_create_file("remote_wakeup", 0600,
			debugfs.debugfs_root, port, &cser_rem_wakeup_fops);
}

static void usb_cser_debugfs_exit(void)
{
	debugfs_remove_recursive(debugfs.debugfs_root);
}

static struct f_cdev *f_cdev_alloc(char *func_name, int portno)
{
	int ret;
@@ -1567,6 +1718,8 @@ static struct f_cdev *f_cdev_alloc(char *func_name, int portno)
		goto err_create_dev;
	}

	usb_cser_debugfs_init(port);

	pr_info("port_name:%s (%pK) portno:(%d)\n",
			port->name, port, port->port_num);
	return port;
@@ -1829,6 +1982,8 @@ static struct usb_function *cser_alloc(struct usb_function_instance *fi)
	port->port_usb.func.set_alt = usb_cser_set_alt;
	port->port_usb.func.disable = usb_cser_disable;
	port->port_usb.func.setup = usb_cser_setup;
	port->port_usb.func.func_suspend = usb_cser_func_suspend;
	port->port_usb.func.get_status = usb_cser_get_status;
	port->port_usb.func.free_func = usb_cser_free_func;

	return &port->port_usb.func;