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

Commit 78917d6a authored by Badhri Jagan Sridharan's avatar Badhri Jagan Sridharan Committed by Badhri Jagan Sridharan
Browse files

usb: phy: Dual role sysfs class definition

This CL adds a new class to monitor and change
dual role usb ports from userspace. The usb
phy drivers can register to the dual_role_usb
class and expose the capabilities of the ports.
The phy drivers can decide on whether a specific
attribute can be changed from userspace by
choosing to implement the appropriate callback.

Cherry-picked from
https://android-review.googlesource.com/#/c/167310/



Signed-off-by: default avatarBadhri Jagan Sridharan <badhri@google.com>
Bug: 21615151
Change-Id: Id1c4aaa97e898264d7006381a7badd029b5d9789
parent e02055c7
Loading
Loading
Loading
Loading
+71 −0
Original line number Diff line number Diff line
What:		/sys/class/dual_role_usb/.../
Date:		June 2015
Contact:	Badhri Jagan Sridharan<badhri@google.com>
Description:
		Provide a generic interface to monitor and change
		the state of dual role usb ports. The name here
		refers to the name mentioned in the
		dual_role_phy_desc that is passed while registering
		the dual_role_phy_intstance through
		devm_dual_role_instance_register.

What:           /sys/class/dual_role_usb/.../supported_modes
Date:           June 2015
Contact:        Badhri Jagan Sridharan<badhri@google.com>
Description:
		This is a static node, once initialized this
		is not expected to change during runtime. "dfp"
		refers to "downstream facing port" i.e. port can
		only act as host. "ufp" refers to "upstream
		facing port" i.e. port can only act as device.
		"dfp ufp" refers to "dual role port" i.e. the port
		can either be a host port or a device port.

What:		/sys/class/dual_role_usb/.../mode
Date:		June 2015
Contact:	Badhri Jagan Sridharan<badhri@google.com>
Description:
		The mode node refers to the current mode in which the
		port is operating. "dfp" for host ports. "ufp" for device
		ports and "none" when cable is not connected.

		On devices where the USB mode is software-controllable,
		userspace can change the mode by writing "dfp" or "ufp".
		On devices where the USB mode is fixed in hardware,
		this attribute is read-only.

What:		/sys/class/dual_role_usb/.../power_role
Date:		June 2015
Contact:	Badhri Jagan Sridharan<badhri@google.com>
Description:
		The power_role node mentions whether the port
		is "sink"ing or "source"ing power. "none" if
		they are not connected.

		On devices implementing USB Power Delivery,
		userspace can control the power role by writing "sink" or
		"source". On devices without USB-PD, this attribute is
		read-only.

What:		/sys/class/dual_role_usb/.../data_role
Date:		June 2015
Contact:	Badhri Jagan Sridharan<badhri@google.com>
Description:
		The data_role node mentions whether the port
		is acting as "host" or "device" for USB data connection.
		"none" if there is no active data link.

		On devices implementing USB Power Delivery, userspace
		can control the data role by writing "host" or "device".
		On devices without USB-PD, this attribute is read-only

What:		/sys/class/dual_role_usb/.../powers_vconn
Date:		June 2015
Contact:	Badhri Jagan Sridharan<badhri@google.com>
Description:
		The powers_vconn node mentions whether the port
		is supplying power for VCONN pin.

		On devices with software control of VCONN,
		userspace can disable the power supply to VCONN by writing "n",
		or enable the power supply by writing "y".
+9 −0
Original line number Diff line number Diff line
@@ -221,4 +221,13 @@ config USB_ULPI_VIEWPORT
	  Provides read/write operations to the ULPI phy register set for
	  controllers with a viewport register (e.g. Chipidea/ARC controllers).

config DUAL_ROLE_USB_INTF
	bool "Generic DUAL ROLE sysfs interface"
	depends on SYSFS && USB_PHY
	help
	  A generic sysfs interface to track and change the state of
	  dual role usb phys. The usb phy drivers can register to
	  this interface to expose it capabilities to the userspace
	  and thereby allowing userspace to change the port mode.

endmenu
+2 −0
Original line number Diff line number Diff line
@@ -4,6 +4,8 @@
obj-$(CONFIG_USB_PHY)			+= phy.o
obj-$(CONFIG_OF)			+= of.o
obj-$(CONFIG_USB_OTG_WAKELOCK)		+= otg-wakelock.o
obj-$(CONFIG_DUAL_ROLE_USB_INTF)	+= class-dual-role.o

# transceiver drivers, keep the list sorted

obj-$(CONFIG_AB8500_USB)		+= phy-ab8500-usb.o
+529 −0
Original line number Diff line number Diff line
/*
 * class-dual-role.c
 *
 * Copyright (C) 2015 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/ctype.h>
#include <linux/device.h>
#include <linux/usb/class-dual-role.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/stat.h>
#include <linux/types.h>

#define DUAL_ROLE_NOTIFICATION_TIMEOUT 2000

static ssize_t dual_role_store_property(struct device *dev,
					struct device_attribute *attr,
					const char *buf, size_t count);
static ssize_t dual_role_show_property(struct device *dev,
				       struct device_attribute *attr,
				       char *buf);

#define DUAL_ROLE_ATTR(_name)				\
{							\
	.attr = { .name = #_name },			\
	.show = dual_role_show_property,		\
	.store = dual_role_store_property,		\
}

static struct device_attribute dual_role_attrs[] = {
	DUAL_ROLE_ATTR(supported_modes),
	DUAL_ROLE_ATTR(mode),
	DUAL_ROLE_ATTR(power_role),
	DUAL_ROLE_ATTR(data_role),
	DUAL_ROLE_ATTR(powers_vconn),
};

struct class *dual_role_class;
EXPORT_SYMBOL_GPL(dual_role_class);

static struct device_type dual_role_dev_type;

static char *kstrdupcase(const char *str, gfp_t gfp, bool to_upper)
{
	char *ret, *ustr;

	ustr = ret = kmalloc(strlen(str) + 1, gfp);

	if (!ret)
		return NULL;

	while (*str)
		*ustr++ = to_upper ? toupper(*str++) : tolower(*str++);

	*ustr = 0;

	return ret;
}

static void dual_role_changed_work(struct work_struct *work)
{
	struct dual_role_phy_instance *dual_role =
	    container_of(work, struct dual_role_phy_instance,
			 changed_work);

	dev_dbg(&dual_role->dev, "%s\n", __func__);
	kobject_uevent(&dual_role->dev.kobj, KOBJ_CHANGE);
}

void dual_role_instance_changed(struct dual_role_phy_instance *dual_role)
{
	dev_dbg(&dual_role->dev, "%s\n", __func__);
	pm_wakeup_event(&dual_role->dev, DUAL_ROLE_NOTIFICATION_TIMEOUT);
	schedule_work(&dual_role->changed_work);
}
EXPORT_SYMBOL_GPL(dual_role_instance_changed)

int dual_role_get_property(struct dual_role_phy_instance *dual_role,
			   enum dual_role_property prop,
			   unsigned int *val)
{
	return dual_role->desc->get_property(dual_role, prop, val);
}
EXPORT_SYMBOL_GPL(dual_role_get_property);

int dual_role_set_property(struct dual_role_phy_instance *dual_role,
			   enum dual_role_property prop,
			   const unsigned int *val)
{
	if (!dual_role->desc->set_property)
		return -ENODEV;

	return dual_role->desc->set_property(dual_role, prop, val);
}
EXPORT_SYMBOL_GPL(dual_role_set_property);

int dual_role_property_is_writeable(struct dual_role_phy_instance *dual_role,
				    enum dual_role_property prop)
{
	if (!dual_role->desc->property_is_writeable)
		return -ENODEV;

	return dual_role->desc->property_is_writeable(dual_role, prop);
}
EXPORT_SYMBOL_GPL(dual_role_property_is_writeable);

static void dual_role_dev_release(struct device *dev)
{
	struct dual_role_phy_instance *dual_role =
	    container_of(dev, struct dual_role_phy_instance, dev);
	pr_debug("device: '%s': %s\n", dev_name(dev), __func__);
	kfree(dual_role);
}

static struct dual_role_phy_instance *__must_check
__dual_role_register(struct device *parent,
		     const struct dual_role_phy_desc *desc)
{
	struct device *dev;
	struct dual_role_phy_instance *dual_role;
	int rc;

	dual_role = kzalloc(sizeof(*dual_role), GFP_KERNEL);
	if (!dual_role)
		return ERR_PTR(-ENOMEM);

	dev = &dual_role->dev;

	device_initialize(dev);

	dev->class = dual_role_class;
	dev->type = &dual_role_dev_type;
	dev->parent = parent;
	dev->release = dual_role_dev_release;
	dev_set_drvdata(dev, dual_role);
	dual_role->desc = desc;

	rc = dev_set_name(dev, "%s", desc->name);
	if (rc)
		goto dev_set_name_failed;

	INIT_WORK(&dual_role->changed_work, dual_role_changed_work);

	rc = device_init_wakeup(dev, true);
	if (rc)
		goto wakeup_init_failed;

	rc = device_add(dev);
	if (rc)
		goto device_add_failed;

	dual_role_instance_changed(dual_role);

	return dual_role;

device_add_failed:
	device_init_wakeup(dev, false);
wakeup_init_failed:
dev_set_name_failed:
	put_device(dev);
	kfree(dual_role);

	return ERR_PTR(rc);
}

static void dual_role_instance_unregister(struct dual_role_phy_instance
					  *dual_role)
{
	cancel_work_sync(&dual_role->changed_work);
	device_init_wakeup(&dual_role->dev, false);
	device_unregister(&dual_role->dev);
}

static void devm_dual_role_release(struct device *dev, void *res)
{
	struct dual_role_phy_instance **dual_role = res;

	dual_role_instance_unregister(*dual_role);
}

struct dual_role_phy_instance *__must_check
devm_dual_role_instance_register(struct device *parent,
				 const struct dual_role_phy_desc *desc)
{
	struct dual_role_phy_instance **ptr, *dual_role;

	ptr = devres_alloc(devm_dual_role_release, sizeof(*ptr), GFP_KERNEL);

	if (!ptr)
		return ERR_PTR(-ENOMEM);
	dual_role = __dual_role_register(parent, desc);
	if (IS_ERR(dual_role)) {
		devres_free(ptr);
	} else {
		*ptr = dual_role;
		devres_add(parent, ptr);
	}
	return dual_role;
}
EXPORT_SYMBOL_GPL(devm_dual_role_instance_register);

static int devm_dual_role_match(struct device *dev, void *res, void *data)
{
	struct dual_role_phy_instance **r = res;

	if (WARN_ON(!r || !*r))
		return 0;

	return *r == data;
}

void devm_dual_role_instance_unregister(struct device *dev,
					struct dual_role_phy_instance
					*dual_role)
{
	int rc;

	rc = devres_release(dev, devm_dual_role_release,
			    devm_dual_role_match, dual_role);
	WARN_ON(rc);
}
EXPORT_SYMBOL_GPL(devm_dual_role_instance_unregister);

void *dual_role_get_drvdata(struct dual_role_phy_instance *dual_role)
{
	return dual_role->drv_data;
}
EXPORT_SYMBOL_GPL(dual_role_get_drvdata);

/***************** Device attribute functions **************************/

/* port type */
static char *supported_modes_text[] = {
	"ufp dfp", "dfp", "ufp"
};

/* current mode */
static char *mode_text[] = {
	"ufp", "dfp", "none"
};

/* Power role */
static char *pr_text[] = {
	"source", "sink", "none"
};

/* Data role */
static char *dr_text[] = {
	"host", "device", "none"
};

/* Vconn supply */
static char *vconn_supply_text[] = {
	"n", "y"
};

static ssize_t dual_role_show_property(struct device *dev,
				       struct device_attribute *attr, char *buf)
{
	ssize_t ret = 0;
	struct dual_role_phy_instance *dual_role = dev_get_drvdata(dev);
	const ptrdiff_t off = attr - dual_role_attrs;
	unsigned int value;

	if (off == DUAL_ROLE_PROP_SUPPORTED_MODES) {
		value = dual_role->desc->supported_modes;
	} else {
		ret = dual_role_get_property(dual_role, off, &value);

		if (ret < 0) {
			if (ret == -ENODATA)
				dev_dbg(dev,
					"driver has no data for `%s' property\n",
					attr->attr.name);
			else if (ret != -ENODEV)
				dev_err(dev,
					"driver failed to report `%s' property: %zd\n",
					attr->attr.name, ret);
			return ret;
		}
	}

	if (off == DUAL_ROLE_PROP_SUPPORTED_MODES) {
		BUILD_BUG_ON(DUAL_ROLE_PROP_SUPPORTED_MODES_TOTAL !=
			ARRAY_SIZE(supported_modes_text));
		if (value < DUAL_ROLE_PROP_SUPPORTED_MODES_TOTAL)
			return snprintf(buf, PAGE_SIZE, "%s\n",
					supported_modes_text[value]);
		else
			return -EIO;
	} else if (off == DUAL_ROLE_PROP_MODE) {
		BUILD_BUG_ON(DUAL_ROLE_PROP_MODE_TOTAL !=
			ARRAY_SIZE(mode_text));
		if (value < DUAL_ROLE_PROP_MODE_TOTAL)
			return snprintf(buf, PAGE_SIZE, "%s\n",
					mode_text[value]);
		else
			return -EIO;
	} else if (off == DUAL_ROLE_PROP_PR) {
		BUILD_BUG_ON(DUAL_ROLE_PROP_PR_TOTAL != ARRAY_SIZE(pr_text));
		if (value < DUAL_ROLE_PROP_PR_TOTAL)
			return snprintf(buf, PAGE_SIZE, "%s\n",
					pr_text[value]);
		else
			return -EIO;
	} else if (off == DUAL_ROLE_PROP_DR) {
		BUILD_BUG_ON(DUAL_ROLE_PROP_DR_TOTAL != ARRAY_SIZE(dr_text));
		if (value < DUAL_ROLE_PROP_DR_TOTAL)
			return snprintf(buf, PAGE_SIZE, "%s\n",
					dr_text[value]);
		else
			return -EIO;
	} else if (off == DUAL_ROLE_PROP_VCONN_SUPPLY) {
		BUILD_BUG_ON(DUAL_ROLE_PROP_VCONN_SUPPLY_TOTAL !=
				ARRAY_SIZE(vconn_supply_text));
		if (value < DUAL_ROLE_PROP_VCONN_SUPPLY_TOTAL)
			return snprintf(buf, PAGE_SIZE, "%s\n",
					vconn_supply_text[value]);
		else
			return -EIO;
	} else
		return -EIO;
}

static ssize_t dual_role_store_property(struct device *dev,
					struct device_attribute *attr,
					const char *buf, size_t count)
{
	ssize_t ret;
	struct dual_role_phy_instance *dual_role = dev_get_drvdata(dev);
	const ptrdiff_t off = attr - dual_role_attrs;
	unsigned int value;
	int total, i;
	char *dup_buf, **text_array;
	bool result = false;

	dup_buf = kstrdupcase(buf, GFP_KERNEL, false);
	switch (off) {
	case DUAL_ROLE_PROP_MODE:
		total = DUAL_ROLE_PROP_MODE_TOTAL;
		text_array = mode_text;
		break;
	case DUAL_ROLE_PROP_PR:
		total = DUAL_ROLE_PROP_PR_TOTAL;
		text_array = pr_text;
		break;
	case DUAL_ROLE_PROP_DR:
		total = DUAL_ROLE_PROP_DR_TOTAL;
		text_array = dr_text;
		break;
	case DUAL_ROLE_PROP_VCONN_SUPPLY:
		ret = strtobool(dup_buf, &result);
		value = result;
		if (!ret)
			goto setprop;
	default:
		ret = -EINVAL;
		goto error;
	}

	for (i = 0; i <= total; i++) {
		if (i == total) {
			ret = -ENOTSUPP;
			goto error;
		}
		if (!strncmp(*(text_array + i), dup_buf,
			     strlen(*(text_array + i)))) {
			value = i;
			break;
		}
	}

setprop:
	ret = dual_role->desc->set_property(dual_role, off, &value);

error:
	kfree(dup_buf);

	if (ret < 0)
		return ret;

	return count;
}

static umode_t dual_role_attr_is_visible(struct kobject *kobj,
					 struct attribute *attr, int attrno)
{
	struct device *dev = container_of(kobj, struct device, kobj);
	struct dual_role_phy_instance *dual_role = dev_get_drvdata(dev);
	umode_t mode = S_IRUSR | S_IRGRP | S_IROTH;
	int i;

	if (attrno == DUAL_ROLE_PROP_SUPPORTED_MODES)
		return mode;

	for (i = 0; i < dual_role->desc->num_properties; i++) {
		int property = dual_role->desc->properties[i];

		if (property == attrno) {
			if (dual_role->desc->property_is_writeable &&
			    dual_role_property_is_writeable(dual_role, property)
			    > 0)
				mode |= S_IWUSR;

			return mode;
		}
	}

	return 0;
}

static struct attribute *__dual_role_attrs[ARRAY_SIZE(dual_role_attrs) + 1];

static struct attribute_group dual_role_attr_group = {
	.attrs = __dual_role_attrs,
	.is_visible = dual_role_attr_is_visible,
};

static const struct attribute_group *dual_role_attr_groups[] = {
	&dual_role_attr_group,
	NULL,
};

void dual_role_init_attrs(struct device_type *dev_type)
{
	int i;

	dev_type->groups = dual_role_attr_groups;

	for (i = 0; i < ARRAY_SIZE(dual_role_attrs); i++)
		__dual_role_attrs[i] = &dual_role_attrs[i].attr;
}

int dual_role_uevent(struct device *dev, struct kobj_uevent_env *env)
{
	struct dual_role_phy_instance *dual_role = dev_get_drvdata(dev);
	int ret = 0, j;
	char *prop_buf;
	char *attrname;

	dev_dbg(dev, "uevent\n");

	if (!dual_role || !dual_role->desc) {
		dev_dbg(dev, "No dual_role phy yet\n");
		return ret;
	}

	dev_dbg(dev, "DUAL_ROLE_NAME=%s\n", dual_role->desc->name);

	ret = add_uevent_var(env, "DUAL_ROLE_NAME=%s", dual_role->desc->name);
	if (ret)
		return ret;

	prop_buf = (char *)get_zeroed_page(GFP_KERNEL);
	if (!prop_buf)
		return -ENOMEM;

	for (j = 0; j < dual_role->desc->num_properties; j++) {
		struct device_attribute *attr;
		char *line;

		attr = &dual_role_attrs[dual_role->desc->properties[j]];

		ret = dual_role_show_property(dev, attr, prop_buf);
		if (ret == -ENODEV || ret == -ENODATA) {
			ret = 0;
			continue;
		}

		if (ret < 0)
			goto out;
		line = strnchr(prop_buf, PAGE_SIZE, '\n');
		if (line)
			*line = 0;

		attrname = kstrdupcase(attr->attr.name, GFP_KERNEL, true);
		if (!attrname)
			ret = -ENOMEM;

		dev_dbg(dev, "prop %s=%s\n", attrname, prop_buf);

		ret = add_uevent_var(env, "DUAL_ROLE_%s=%s", attrname,
				     prop_buf);
		kfree(attrname);
		if (ret)
			goto out;
	}

out:
	free_page((unsigned long)prop_buf);

	return ret;
}

/******************* Module Init ***********************************/

static int __init dual_role_class_init(void)
{
	dual_role_class = class_create(THIS_MODULE, "dual_role_usb");

	if (IS_ERR(dual_role_class))
		return PTR_ERR(dual_role_class);

	dual_role_class->dev_uevent = dual_role_uevent;
	dual_role_init_attrs(&dual_role_dev_type);

	return 0;
}

static void __exit dual_role_class_exit(void)
{
	class_destroy(dual_role_class);
}

subsys_initcall(dual_role_class_init);
module_exit(dual_role_class_exit);
+128 −0
Original line number Diff line number Diff line
#ifndef __LINUX_CLASS_DUAL_ROLE_H__
#define __LINUX_CLASS_DUAL_ROLE_H__

#include <linux/workqueue.h>
#include <linux/errno.h>
#include <linux/types.h>

struct device;

enum dual_role_supported_modes {
	DUAL_ROLE_SUPPORTED_MODES_DFP_AND_UFP = 0,
	DUAL_ROLE_SUPPORTED_MODES_DFP,
	DUAL_ROLE_SUPPORTED_MODES_UFP,
/*The following should be the last element*/
	DUAL_ROLE_PROP_SUPPORTED_MODES_TOTAL,
};

enum {
	DUAL_ROLE_PROP_MODE_UFP = 0,
	DUAL_ROLE_PROP_MODE_DFP,
	DUAL_ROLE_PROP_MODE_NONE,
/*The following should be the last element*/
	DUAL_ROLE_PROP_MODE_TOTAL,
};

enum {
	DUAL_ROLE_PROP_PR_SRC = 0,
	DUAL_ROLE_PROP_PR_SNK,
	DUAL_ROLE_PROP_PR_NONE,
/*The following should be the last element*/
	DUAL_ROLE_PROP_PR_TOTAL,

};

enum {
	DUAL_ROLE_PROP_DR_HOST = 0,
	DUAL_ROLE_PROP_DR_DEVICE,
	DUAL_ROLE_PROP_DR_NONE,
/*The following should be the last element*/
	DUAL_ROLE_PROP_DR_TOTAL,
};

enum {
	DUAL_ROLE_PROP_VCONN_SUPPLY_NO = 0,
	DUAL_ROLE_PROP_VCONN_SUPPLY_YES,
/*The following should be the last element*/
	DUAL_ROLE_PROP_VCONN_SUPPLY_TOTAL,
};

enum dual_role_property {
	DUAL_ROLE_PROP_SUPPORTED_MODES = 0,
	DUAL_ROLE_PROP_MODE,
	DUAL_ROLE_PROP_PR,
	DUAL_ROLE_PROP_DR,
	DUAL_ROLE_PROP_VCONN_SUPPLY,
};

struct dual_role_phy_instance;

/* Description of typec port */
struct dual_role_phy_desc {
	/* /sys/class/dual_role_usb/<name>/ */
	const char *name;
	enum dual_role_supported_modes supported_modes;
	enum dual_role_property *properties;
	size_t num_properties;

	/* Callback for "cat /sys/class/dual_role_usb/<name>/<property>" */
	int (*get_property)(struct dual_role_phy_instance *dual_role,
			     enum dual_role_property prop,
			     unsigned int *val);
	/* Callback for "echo <value> >
	 *                      /sys/class/dual_role_usb/<name>/<property>" */
	int (*set_property)(struct dual_role_phy_instance *dual_role,
			     enum dual_role_property prop,
			     const unsigned int *val);
	/* Decides whether userspace can change a specific property */
	int (*property_is_writeable)(struct dual_role_phy_instance *dual_role,
				      enum dual_role_property prop);
};

struct dual_role_phy_instance {
	const struct dual_role_phy_desc *desc;

	/* Driver private data */
	void *drv_data;

	struct device dev;
	struct work_struct changed_work;
};

#if IS_ENABLED(CONFIG_DUAL_ROLE_USB_INTF)
extern void dual_role_instance_changed(struct dual_role_phy_instance
				       *dual_role);
extern struct dual_role_phy_instance *__must_check
devm_dual_role_instance_register(struct device *parent,
				 const struct dual_role_phy_desc *desc);
extern void devm_dual_role_instance_unregister(struct device *dev,
					       struct dual_role_phy_instance
					       *dual_role);
extern int dual_role_get_property(struct dual_role_phy_instance *dual_role,
				  enum dual_role_property prop,
				  unsigned int *val);
extern int dual_role_set_property(struct dual_role_phy_instance *dual_role,
				  enum dual_role_property prop,
				  const unsigned int *val);
extern int dual_role_property_is_writeable(struct dual_role_phy_instance
					   *dual_role,
					   enum dual_role_property prop);
extern void *dual_role_get_drvdata(struct dual_role_phy_instance *dual_role);
#else /* CONFIG_DUAL_ROLE_USB_INTF */
static void dual_role_instance_changed(struct dual_role_phy_instance
				       *dual_role){}
static struct dual_role_phy_instance *__must_check
devm_dual_role_instance_register(struct device *parent,
				 const struct dual_role_phy_desc *desc)
{
	return ERR_PTR(-ENOSYS);
}
static void devm_dual_role_instance_unregister(struct device *dev,
					       struct dual_role_phy_instance
					       *dual_role){}
static void *dual_role_get_drvdata(struct dual_role_phy_instance *dual_role)
{
	return ERR_PTR(-ENOSYS);
}
#endif /* CONFIG_DUAL_ROLE_USB_INTF */
#endif /* __LINUX_CLASS_DUAL_ROLE_H__ */