diff options
Diffstat (limited to 'drivers/usb/typec/class.c')
-rw-r--r-- | drivers/usb/typec/class.c | 298 |
1 files changed, 281 insertions, 17 deletions
diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c index 35eec707cb51..ebfd3113a9a8 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" @@ -18,6 +19,7 @@ struct typec_plug { struct device dev; enum typec_plug_index index; struct ida mode_ids; + int num_altmodes; }; struct typec_cable { @@ -33,6 +35,7 @@ struct typec_partner { struct usb_pd_identity *identity; enum typec_accessory accessory; struct ida mode_ids; + int num_altmodes; }; struct typec_port { @@ -81,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)) { @@ -95,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) { @@ -122,10 +174,40 @@ static ssize_t product_show(struct device *dev, struct device_attribute *attr, } static DEVICE_ATTR_RO(product); +static ssize_t product_type_vdo1_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_pd_identity *id = get_pd_identity(dev); + + return sysfs_emit(buf, "0x%08x\n", id->vdo[0]); +} +static DEVICE_ATTR_RO(product_type_vdo1); + +static ssize_t product_type_vdo2_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_pd_identity *id = get_pd_identity(dev); + + return sysfs_emit(buf, "0x%08x\n", id->vdo[1]); +} +static DEVICE_ATTR_RO(product_type_vdo2); + +static ssize_t product_type_vdo3_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_pd_identity *id = get_pd_identity(dev); + + return sysfs_emit(buf, "0x%08x\n", id->vdo[2]); +} +static DEVICE_ATTR_RO(product_type_vdo3); + static struct attribute *usb_pd_id_attrs[] = { &dev_attr_id_header.attr, &dev_attr_cert_stat.attr, &dev_attr_product.attr, + &dev_attr_product_type_vdo1.attr, + &dev_attr_product_type_vdo2.attr, + &dev_attr_product_type_vdo3.attr, NULL }; @@ -139,12 +221,48 @@ 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"); sysfs_notify(&dev->kobj, "identity", "cert_stat"); sysfs_notify(&dev->kobj, "identity", "product"); + 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 */ @@ -382,7 +500,7 @@ static umode_t typec_altmode_attr_is_visible(struct kobject *kobj, return attr->mode; } -static struct attribute_group typec_altmode_group = { +static const struct attribute_group typec_altmode_group = { .is_visible = typec_altmode_attr_is_visible, .attrs = typec_altmode_attrs, }; @@ -482,6 +600,10 @@ typec_register_altmode(struct device *parent, if (is_typec_partner(parent)) alt->adev.dev.bus = &typec_bus; + /* Plug alt modes need a class to generate udev events. */ + if (is_typec_plug(parent)) + alt->adev.dev.class = typec_class; + ret = device_register(&alt->adev.dev); if (ret) { dev_err(parent, "failed to register alternate mode (%d)\n", @@ -532,12 +654,60 @@ static ssize_t supports_usb_power_delivery_show(struct device *dev, } static DEVICE_ATTR_RO(supports_usb_power_delivery); +static ssize_t number_of_alternate_modes_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct typec_partner *partner; + struct typec_plug *plug; + int num_altmodes; + + if (is_typec_partner(dev)) { + partner = to_typec_partner(dev); + num_altmodes = partner->num_altmodes; + } else if (is_typec_plug(dev)) { + plug = to_typec_plug(dev); + num_altmodes = plug->num_altmodes; + } else { + return 0; + } + + return sysfs_emit(buf, "%d\n", num_altmodes); +} +static DEVICE_ATTR_RO(number_of_alternate_modes); + 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 +}; + +static umode_t typec_partner_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) +{ + struct typec_partner *partner = to_typec_partner(kobj_to_dev(kobj)); + + if (attr == &dev_attr_number_of_alternate_modes.attr) { + if (partner->num_altmodes < 0) + return 0; + } + + if (attr == &dev_attr_type.attr) + if (!get_pd_product_type(kobj_to_dev(kobj))) + return 0; + + return attr->mode; +} + +static const struct attribute_group typec_partner_group = { + .is_visible = typec_partner_attr_is_visible, + .attrs = typec_partner_attrs +}; + +static const struct attribute_group *typec_partner_groups[] = { + &typec_partner_group, NULL }; -ATTRIBUTE_GROUPS(typec_partner); static void typec_partner_release(struct device *dev) { @@ -571,6 +741,37 @@ int typec_partner_set_identity(struct typec_partner *partner) EXPORT_SYMBOL_GPL(typec_partner_set_identity); /** + * typec_partner_set_num_altmodes - Set the number of available partner altmodes + * @partner: The partner to be updated. + * @num_altmodes: The number of altmodes we want to specify as available. + * + * This routine is used to report the number of alternate modes supported by the + * partner. This value is *not* enforced in alternate mode registration routines. + * + * @partner.num_altmodes is set to -1 on partner registration, denoting that + * a valid value has not been set for it yet. + * + * Returns 0 on success or negative error number on failure. + */ +int typec_partner_set_num_altmodes(struct typec_partner *partner, int num_altmodes) +{ + int ret; + + if (num_altmodes < 0) + return -EINVAL; + + partner->num_altmodes = num_altmodes; + ret = sysfs_update_group(&partner->dev.kobj, &typec_partner_group); + if (ret < 0) + return ret; + + sysfs_notify(&partner->dev.kobj, NULL, "number_of_alternate_modes"); + + return 0; +} +EXPORT_SYMBOL_GPL(typec_partner_set_num_altmodes); + +/** * typec_partner_register_altmode - Register USB Type-C Partner Alternate Mode * @partner: USB Type-C Partner that supports the alternate mode * @desc: Description of the alternate mode @@ -612,6 +813,7 @@ struct typec_partner *typec_register_partner(struct typec_port *port, ida_init(&partner->mode_ids); partner->usb_pd = desc->usb_pd; partner->accessory = desc->accessory; + partner->num_altmodes = -1; if (desc->identity) { /* @@ -662,12 +864,71 @@ static void typec_plug_release(struct device *dev) kfree(plug); } +static struct attribute *typec_plug_attrs[] = { + &dev_attr_number_of_alternate_modes.attr, + NULL +}; + +static umode_t typec_plug_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) +{ + struct typec_plug *plug = to_typec_plug(kobj_to_dev(kobj)); + + if (attr == &dev_attr_number_of_alternate_modes.attr) { + if (plug->num_altmodes < 0) + return 0; + } + + return attr->mode; +} + +static const struct attribute_group typec_plug_group = { + .is_visible = typec_plug_attr_is_visible, + .attrs = typec_plug_attrs +}; + +static const struct attribute_group *typec_plug_groups[] = { + &typec_plug_group, + NULL +}; + static const struct device_type typec_plug_dev_type = { .name = "typec_plug", + .groups = typec_plug_groups, .release = typec_plug_release, }; /** + * typec_plug_set_num_altmodes - Set the number of available plug altmodes + * @plug: The plug to be updated. + * @num_altmodes: The number of altmodes we want to specify as available. + * + * This routine is used to report the number of alternate modes supported by the + * plug. This value is *not* enforced in alternate mode registration routines. + * + * @plug.num_altmodes is set to -1 on plug registration, denoting that + * a valid value has not been set for it yet. + * + * Returns 0 on success or negative error number on failure. + */ +int typec_plug_set_num_altmodes(struct typec_plug *plug, int num_altmodes) +{ + int ret; + + if (num_altmodes < 0) + return -EINVAL; + + plug->num_altmodes = num_altmodes; + ret = sysfs_update_group(&plug->dev.kobj, &typec_plug_group); + if (ret < 0) + return ret; + + sysfs_notify(&plug->dev.kobj, NULL, "number_of_alternate_modes"); + + return 0; +} +EXPORT_SYMBOL_GPL(typec_plug_set_num_altmodes); + +/** * typec_plug_register_altmode - Register USB Type-C Cable Plug Alternate Mode * @plug: USB Type-C Cable Plug that supports the alternate mode * @desc: Description of the alternate mode @@ -712,6 +973,7 @@ struct typec_plug *typec_register_plug(struct typec_cable *cable, sprintf(name, "plug%d", desc->index); ida_init(&plug->mode_ids); + plug->num_altmodes = -1; plug->index = desc->index; plug->dev.class = typec_class; plug->dev.parent = &cable->dev; @@ -744,15 +1006,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", @@ -1309,7 +1562,7 @@ static umode_t typec_attr_is_visible(struct kobject *kobj, return attr->mode; } -static struct attribute_group typec_group = { +static const struct attribute_group typec_group = { .is_visible = typec_attr_is_visible, .attrs = typec_attrs, }; @@ -1352,6 +1605,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 @@ -1361,12 +1619,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); @@ -1407,11 +1676,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 |