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

Commit 3c5cddfb authored by qctecmdr Service's avatar qctecmdr Service Committed by Gerrit - the friendly Code Review server
Browse files

Merge "usb: f_cdev: Add function suspend support for serial interfaces"

parents fc7983ab d46b9e1d
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;