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:
Badhri Jagan Sridharan
2015-08-31 21:36:07 -07:00
committed by Dmitry Shmidt
parent 171b8124fe
commit 7dfee9ca8f
5 changed files with 739 additions and 0 deletions

View 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".

View File

@ -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

View File

@ -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

View 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);

View 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__ */