ANDROID: 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: Badhri Jagan Sridharan <badhri@google.com> Bug: 21615151 Change-Id: Id1c4aaa97e898264d7006381a7badd029b5d9789
This commit is contained in:
committed by
Dmitry Shmidt
parent
171b8124fe
commit
7dfee9ca8f
71
Documentation/ABI/testing/sysfs-class-dual-role-usb
Normal file
71
Documentation/ABI/testing/sysfs-class-dual-role-usb
Normal file
@ -0,0 +1,71 @@
|
||||
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".
|
||||
@ -216,4 +216,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
|
||||
|
||||
@ -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
drivers/usb/phy/class-dual-role.c
Normal file
529
drivers/usb/phy/class-dual-role.c
Normal file
@ -0,0 +1,529 @@
|
||||
/*
|
||||
* 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
include/linux/usb/class-dual-role.h
Normal file
128
include/linux/usb/class-dual-role.h
Normal file
@ -0,0 +1,128 @@
|
||||
#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__ */
|
||||
Reference in New Issue
Block a user