summaryrefslogtreecommitdiffstats
path: root/drivers/usb/typec
diff options
context:
space:
mode:
authorHeikki Krogerus <heikki.krogerus@linux.intel.com>2020-11-26 14:57:35 +0300
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2020-11-26 13:40:43 +0100
commitab37fa851c488be805f6568ecaabb67b13cd937c (patch)
tree13fb82d8a7bea098fb8c1c5685aa36375cceea0c /drivers/usb/typec
parent52170e937866b3bac1572ed48a97d370f42e52fa (diff)
usb: typec: Add type sysfs attribute file for partners
USB Power Delivery Specification defines a set of product types for partners and cables. The product type can be read from the ID Header VDO which is the first object in the response to the Discover Identity command. This attribute will display the product type of the partner. The cables already have the attribute. This sysfs attribute file is only created for the partners and cables if the product type is really known in the driver. Some interfaces do not give access to the Discover Identity response from the partner or cable, but they may still supply the product type separately in some cases. When the product type of the partner or cable is detected, uevent is also raised with PRODUCT_TYPE set to show the actual product type (for example PRODUCT_TYPE=host). Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com> Link: https://lore.kernel.org/r/20201126115735.50529-1-heikki.krogerus@linux.intel.com Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/usb/typec')
-rw-r--r--drivers/usb/typec/class.c118
1 files changed, 104 insertions, 14 deletions
diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c
index 8f40093c2044..4f6e58dfb81d 100644
--- a/drivers/usb/typec/class.c
+++ b/drivers/usb/typec/class.c
@@ -11,6 +11,7 @@
#include <linux/mutex.h>
#include <linux/property.h>
#include <linux/slab.h>
+#include <linux/usb/pd_vdo.h>
#include "bus.h"
@@ -83,6 +84,29 @@ static const char * const typec_accessory_modes[] = {
[TYPEC_ACCESSORY_DEBUG] = "debug",
};
+/* Product types defined in USB PD Specification R3.0 V2.0 */
+static const char * const product_type_ufp[8] = {
+ [IDH_PTYPE_UNDEF] = "undefined",
+ [IDH_PTYPE_HUB] = "hub",
+ [IDH_PTYPE_PERIPH] = "peripheral",
+ [IDH_PTYPE_PSD] = "psd",
+ [IDH_PTYPE_AMA] = "ama",
+};
+
+static const char * const product_type_dfp[8] = {
+ [IDH_PTYPE_DFP_UNDEF] = "undefined",
+ [IDH_PTYPE_DFP_HUB] = "hub",
+ [IDH_PTYPE_DFP_HOST] = "host",
+ [IDH_PTYPE_DFP_PB] = "power_brick",
+ [IDH_PTYPE_DFP_AMC] = "amc",
+};
+
+static const char * const product_type_cable[8] = {
+ [IDH_PTYPE_UNDEF] = "undefined",
+ [IDH_PTYPE_PCABLE] = "passive",
+ [IDH_PTYPE_ACABLE] = "active",
+};
+
static struct usb_pd_identity *get_pd_identity(struct device *dev)
{
if (is_typec_partner(dev)) {
@@ -97,6 +121,32 @@ static struct usb_pd_identity *get_pd_identity(struct device *dev)
return NULL;
}
+static const char *get_pd_product_type(struct device *dev)
+{
+ struct typec_port *port = to_typec_port(dev->parent);
+ struct usb_pd_identity *id = get_pd_identity(dev);
+ const char *ptype = NULL;
+
+ if (is_typec_partner(dev)) {
+ if (!id)
+ return NULL;
+
+ if (port->data_role == TYPEC_HOST)
+ ptype = product_type_ufp[PD_IDH_PTYPE(id->id_header)];
+ else
+ ptype = product_type_dfp[PD_IDH_DFP_PTYPE(id->id_header)];
+ } else if (is_typec_cable(dev)) {
+ if (id)
+ ptype = product_type_cable[PD_IDH_PTYPE(id->id_header)];
+ else
+ ptype = to_typec_cable(dev)->active ?
+ product_type_cable[IDH_PTYPE_ACABLE] :
+ product_type_cable[IDH_PTYPE_PCABLE];
+ }
+
+ return ptype;
+}
+
static ssize_t id_header_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
@@ -171,6 +221,25 @@ static const struct attribute_group *usb_pd_id_groups[] = {
NULL,
};
+static void typec_product_type_notify(struct device *dev)
+{
+ char *envp[2] = { };
+ const char *ptype;
+
+ ptype = get_pd_product_type(dev);
+ if (!ptype)
+ return;
+
+ sysfs_notify(&dev->kobj, NULL, "type");
+
+ envp[0] = kasprintf(GFP_KERNEL, "PRODUCT_TYPE=%s", ptype);
+ if (!envp[0])
+ return;
+
+ kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp);
+ kfree(envp[0]);
+}
+
static void typec_report_identity(struct device *dev)
{
sysfs_notify(&dev->kobj, "identity", "id_header");
@@ -179,7 +248,21 @@ static void typec_report_identity(struct device *dev)
sysfs_notify(&dev->kobj, "identity", "product_type_vdo1");
sysfs_notify(&dev->kobj, "identity", "product_type_vdo2");
sysfs_notify(&dev->kobj, "identity", "product_type_vdo3");
+ typec_product_type_notify(dev);
+}
+
+static ssize_t
+type_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ const char *ptype;
+
+ ptype = get_pd_product_type(dev);
+ if (!ptype)
+ return 0;
+
+ return sysfs_emit(buf, "%s\n", ptype);
}
+static DEVICE_ATTR_RO(type);
/* ------------------------------------------------------------------------- */
/* Alternate Modes */
@@ -592,6 +675,7 @@ static struct attribute *typec_partner_attrs[] = {
&dev_attr_accessory_mode.attr,
&dev_attr_supports_usb_power_delivery.attr,
&dev_attr_number_of_alternate_modes.attr,
+ &dev_attr_type.attr,
NULL
};
@@ -604,6 +688,10 @@ static umode_t typec_partner_attr_is_visible(struct kobject *kobj, struct attrib
return 0;
}
+ if (attr == &dev_attr_type.attr)
+ if (!get_pd_product_type(kobj_to_dev(kobj)))
+ return 0;
+
return attr->mode;
}
@@ -914,15 +1002,6 @@ EXPORT_SYMBOL_GPL(typec_unregister_plug);
/* Type-C Cables */
-static ssize_t
-type_show(struct device *dev, struct device_attribute *attr, char *buf)
-{
- struct typec_cable *cable = to_typec_cable(dev);
-
- return sprintf(buf, "%s\n", cable->active ? "active" : "passive");
-}
-static DEVICE_ATTR_RO(type);
-
static const char * const typec_plug_types[] = {
[USB_PLUG_NONE] = "unknown",
[USB_PLUG_TYPE_A] = "type-a",
@@ -1522,6 +1601,11 @@ const struct device_type typec_port_dev_type = {
/* --------------------------------------- */
/* Driver callbacks to report role updates */
+static int partner_match(struct device *dev, void *data)
+{
+ return is_typec_partner(dev);
+}
+
/**
* typec_set_data_role - Report data role change
* @port: The USB Type-C Port where the role was changed
@@ -1531,12 +1615,23 @@ const struct device_type typec_port_dev_type = {
*/
void typec_set_data_role(struct typec_port *port, enum typec_data_role role)
{
+ struct device *partner_dev;
+
if (port->data_role == role)
return;
port->data_role = role;
sysfs_notify(&port->dev.kobj, NULL, "data_role");
kobject_uevent(&port->dev.kobj, KOBJ_CHANGE);
+
+ partner_dev = device_find_child(&port->dev, NULL, partner_match);
+ if (!partner_dev)
+ return;
+
+ if (to_typec_partner(partner_dev)->identity)
+ typec_product_type_notify(partner_dev);
+
+ put_device(partner_dev);
}
EXPORT_SYMBOL_GPL(typec_set_data_role);
@@ -1577,11 +1672,6 @@ void typec_set_vconn_role(struct typec_port *port, enum typec_role role)
}
EXPORT_SYMBOL_GPL(typec_set_vconn_role);
-static int partner_match(struct device *dev, void *data)
-{
- return is_typec_partner(dev);
-}
-
/**
* typec_set_pwr_opmode - Report changed power operation mode
* @port: The USB Type-C Port where the mode was changed