// SPDX-License-Identifier: GPL-2.0/* * USB Type-C Connector Class * * Copyright (C) 2017, Intel Corporation * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com> */ #include <linux/module.h>#include <linux/mutex.h>#include <linux/property.h>#include <linux/slab.h>#include <linux/string_choices.h>#include <linux/usb/pd_vdo.h>#include <linux/usb/typec_mux.h>#include <linux/usb/typec_retimer.h>#include <linux/usb.h> #include "bus.h"#include "class.h"#include "pd.h" static DEFINE_IDA(typec_index_ida); const struct class typec_class = { .name = "typec",}; /* ------------------------------------------------------------------------- *//* Common attributes */ static const char * const typec_accessory_modes[] = { [TYPEC_ACCESSORY_NONE] = "none", [TYPEC_ACCESSORY_AUDIO] = "analog_audio", [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_NOT_UFP] = "not_ufp", [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_NOT_DFP] = "not_dfp", [IDH_PTYPE_DFP_HUB] = "hub", [IDH_PTYPE_DFP_HOST] = "host", [IDH_PTYPE_DFP_PB] = "power_brick",}; static const char * const product_type_cable[8] = { [IDH_PTYPE_NOT_CABLE] = "not_cable", [IDH_PTYPE_PCABLE] = "passive", [IDH_PTYPE_ACABLE] = "active", [IDH_PTYPE_VPD] = "vpd",}; static struct usb_pd_identity *get_pd_identity(struct device *dev){ if (is_typec_partner(dev)) { struct typec_partner *partner = to_typec_partner(dev); return partner->identity; } else if (is_typec_cable(dev)) { struct typec_cable *cable = to_typec_cable(dev); return cable->identity; } 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){ struct usb_pd_identity *id = get_pd_identity(dev); return sprintf(buf, "0x%08x\n", id->id_header);}static DEVICE_ATTR_RO(id_header); static ssize_t cert_stat_show(struct device *dev, struct device_attribute *attr, char *buf){ struct usb_pd_identity *id = get_pd_identity(dev); return sprintf(buf, "0x%08x\n", id->cert_stat);}static DEVICE_ATTR_RO(cert_stat); static ssize_t product_show(struct device *dev, struct device_attribute *attr, char *buf){ struct usb_pd_identity *id = get_pd_identity(dev); return sprintf(buf, "0x%08x\n", id->product);}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}; static const struct attribute_group usb_pd_id_group = { .name = "identity", .attrs = usb_pd_id_attrs,}; static const struct attribute_group *usb_pd_id_groups[] = { &usb_pd_id_group, 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_ttype_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); static ssize_t usb_power_delivery_revision_show(struct device *dev, struct device_attribute *attr, char *buf);static DEVICE_ATTR_RO(usb_power_delivery_revision); static const char * const usb_modes[] = { [USB_MODE_NONE] = "none", [USB_MODE_USB2] = "usb2", [USB_MODE_USB3] = "usb3", [USB_MODE_USB4] = "usb4"}; /* ------------------------------------------------------------------------- *//* Alternate Modes */ static int altmode_match(struct device *dev, const void *data){ struct typec_altmode *adev = to_typec_altmode(dev); const struct typec_device_id *id = data; if (!is_typec_port_altmode(dev)) return 0; return (adev->svid == id->svid);} static void typec_altmode_set_partner(struct altmode *altmode){ struct typec_altmode *adev = &altmode->adev; struct typec_device_id id = { adev->svid }; struct typec_port *port = typec_altmode2port(adev); struct altmode *partner; struct device *dev; dev = device_find_child(&port->dev, &id, altmode_match); if (!dev) return; /* Bind the port alt mode to the partner/plug alt mode. */ partner = to_altmode(to_typec_altmode(dev)); altmode->partner = partner; /* Bind the partner/plug alt mode to the port alt mode. */ if (is_typec_plug(adev->dev.parent)) { struct typec_plug *plug = to_typec_plug(adev->dev.parent); partner->plug[plug->index] = altmode; } else { partner->partner = altmode; }} static void typec_altmode_put_partner(struct altmode *altmode){ struct altmode *partner = altmode->partner; struct typec_altmode *adev; struct typec_altmode *partner_adev; if (!partner) return; adev = &altmode->adev; partner_adev = &partner->adev; if (is_typec_plug(adev->dev.parent)) { struct typec_plug *plug = to_typec_plug(adev->dev.parent); partner->plug[plug->index] = NULL; } else { partner->partner = NULL; } put_device(&partner_adev->dev);} /** * typec_altmode_update_active - Report Enter/Exit mode * @adev: Handle to the alternate mode * @active: True when the mode has been entered * * If a partner or cable plug executes Enter/Exit Mode command successfully, the * drivers use this routine to report the updated state of the mode. */void typec_altmode_update_active(struct typec_altmode *adev, bool active){ char dir[6]; if (adev->active == active) return; if (!is_typec_port(adev->dev.parent) && adev->dev.driver) { if (!active) module_put(adev->dev.driver->owner); else WARN_ON(!try_module_get(adev->dev.driver->owner)); } adev->active = active; snprintf(dir, sizeof(dir), "mode%d", adev->mode); sysfs_notify(&adev->dev.kobj, dir, "active"); sysfs_notify(&adev->dev.kobj, NULL, "active"); kobject_uevent(&adev->dev.kobj, KOBJ_CHANGE);}EXPORT_SYMBOL_GPL(typec_altmode_update_active); /** * typec_altmode2port - Alternate Mode to USB Type-C port * @alt: The Alternate Mode * * Returns handle to the port that a cable plug or partner with @alt is * connected to. */struct typec_port *typec_altmode2port(struct typec_altmode *alt){ if (is_typec_plug(alt->dev.parent)) return to_typec_port(alt->dev.parent->parent->parent); if (is_typec_partner(alt->dev.parent)) return to_typec_port(alt->dev.parent->parent); if (is_typec_port(alt->dev.parent)) return to_typec_port(alt->dev.parent); return NULL;}EXPORT_SYMBOL_GPL(typec_altmode2port); static ssize_tvdo_show(struct device *dev, struct device_attribute *attr, char *buf){ struct typec_altmode *alt = to_typec_altmode(dev); return sprintf(buf, "0x%08x\n", alt->vdo);}static DEVICE_ATTR_RO(vdo); static ssize_tdescription_show(struct device *dev, struct device_attribute *attr, char *buf){ struct typec_altmode *alt = to_typec_altmode(dev); return sprintf(buf, "%s\n", alt->desc ? alt->desc : "");}static DEVICE_ATTR_RO(description); static ssize_tactive_show(struct device *dev, struct device_attribute *attr, char *buf){ struct typec_altmode *alt = to_typec_altmode(dev); return sprintf(buf, "%s\n", str_yes_no(alt->active));} static ssize_t active_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size){ struct typec_altmode *adev = to_typec_altmode(dev); struct altmode *altmode = to_altmode(adev); bool enter; int ret; ret = kstrtobool(buf, &enter); if (ret) return ret; if (adev->active == enter) return size; if (is_typec_port(adev->dev.parent)) { typec_altmode_update_active(adev, enter); /* Make sure that the partner exits the mode before disabling */ if (altmode->partner && !enter && altmode->partner->adev.active) typec_altmode_exit(&altmode->partner->adev); } else if (altmode->partner) { if (enter && !altmode->partner->adev.active) { dev_warn(dev, "port has the mode disabled\n"); return -EPERM; } } /* Note: If there is no driver, the mode will not be entered */ if (adev->ops && adev->ops->activate) { ret = adev->ops->activate(adev, enter); if (ret) return ret; } return size;}static DEVICE_ATTR_RW(active); static ssize_tsupported_roles_show(struct device *dev, struct device_attribute *attr, char *buf){ struct altmode *alt = to_altmode(to_typec_altmode(dev)); ssize_t ret; switch (alt->roles) { case TYPEC_PORT_SRC: ret = sprintf(buf, "source\n"); break; case TYPEC_PORT_SNK: ret = sprintf(buf, "sink\n"); break; case TYPEC_PORT_DRP: default: ret = sprintf(buf, "source sink\n"); break; } return ret;}static DEVICE_ATTR_RO(supported_roles); static ssize_tmode_show(struct device *dev, struct device_attribute *attr, char *buf){ struct typec_altmode *adev = to_typec_altmode(dev); return sprintf(buf, "%u\n", adev->mode);}static DEVICE_ATTR_RO(mode); static ssize_tsvid_show(struct device *dev, struct device_attribute *attr, char *buf){ struct typec_altmode *adev = to_typec_altmode(dev); return sprintf(buf, "%04x\n", adev->svid);}static DEVICE_ATTR_RO(svid); static int increment_duplicated_priority(struct device *dev, void *data){ if (is_typec_port_altmode(dev)) { struct typec_altmode **alt_target = (struct typec_altmode **)data; struct typec_altmode *alt = to_typec_altmode(dev); if (alt != *alt_target && alt->priority == (*alt_target)->priority) { alt->priority++; *alt_target = alt; return 1; } } return 0;} static int find_duplicated_priority(struct device *dev, void *data){ if (is_typec_port_altmode(dev)) { struct typec_altmode **alt_target = (struct typec_altmode **)data; struct typec_altmode *alt = to_typec_altmode(dev); if (alt != *alt_target && alt->priority == (*alt_target)->priority) return 1; } return 0;} static int typec_mode_set_priority(struct typec_altmode *alt, const u8 priority){ struct typec_port *port = to_typec_port(alt->dev.parent); const u8 old_priority = alt->priority; int res = 1; alt->priority = priority; while (res) { res = device_for_each_child(&port->dev, &alt, find_duplicated_priority); if (res) { alt->priority++; if (alt->priority == 0) { alt->priority = old_priority; return -EOVERFLOW; } } } res = 1; alt->priority = priority; while (res) res = device_for_each_child(&port->dev, &alt, increment_duplicated_priority); return 0;}Lines 1–500 of 2965. Load more
// SPDX-License-Identifier: GPL-2.0 /* * USB Type-C Connector Class * * Copyright (C) 2017, Intel Corporation * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com> */ #include <linux/module.h> #include <linux/mutex.h> #include <linux/property.h> #include <linux/slab.h> #include <linux/string_choices.h> #include <linux/usb/pd_vdo.h> #include <linux/usb/typec_mux.h> #include <linux/usb/typec_retimer.h> #include <linux/usb.h> #include "bus.h" #include "class.h" #include "pd.h" static DEFINE_IDA(typec_index_ida); const struct class typec_class = { .name = "typec", }; /* ------------------------------------------------------------------------- */ /* Common attributes */ static const char * const typec_accessory_modes[] = { [TYPEC_ACCESSORY_NONE] = "none", [TYPEC_ACCESSORY_AUDIO] = "analog_audio", [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_NOT_UFP] = "not_ufp", [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_NOT_DFP] = "not_dfp", [IDH_PTYPE_DFP_HUB] = "hub", [IDH_PTYPE_DFP_HOST] = "host", [IDH_PTYPE_DFP_PB] = "power_brick", }; static const char * const product_type_cable[8] = { [IDH_PTYPE_NOT_CABLE] = "not_cable", [IDH_PTYPE_PCABLE] = "passive", [IDH_PTYPE_ACABLE] = "active", [IDH_PTYPE_VPD] = "vpd", }; static struct usb_pd_identity *get_pd_identity(struct device *dev) { if (is_typec_partner(dev)) { struct typec_partner *partner = to_typec_partner(dev); return partner->identity; } else if (is_typec_cable(dev)) { struct typec_cable *cable = to_typec_cable(dev); return cable->identity; } 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) { struct usb_pd_identity *id = get_pd_identity(dev); return sprintf(buf, "0x%08x\n", id->id_header); } static DEVICE_ATTR_RO(id_header); static ssize_t cert_stat_show(struct device *dev, struct device_attribute *attr, char *buf) { struct usb_pd_identity *id = get_pd_identity(dev); return sprintf(buf, "0x%08x\n", id->cert_stat); } static DEVICE_ATTR_RO(cert_stat); static ssize_t product_show(struct device *dev, struct device_attribute *attr, char *buf) { struct usb_pd_identity *id = get_pd_identity(dev); return sprintf(buf, "0x%08x\n", id->product); } 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 }; static const struct attribute_group usb_pd_id_group = { .name = "identity", .attrs = usb_pd_id_attrs, }; static const struct attribute_group *usb_pd_id_groups[] = { &usb_pd_id_group, 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); static ssize_t usb_power_delivery_revision_show(struct device *dev, struct device_attribute *attr, char *buf); static DEVICE_ATTR_RO(usb_power_delivery_revision); static const char * const usb_modes[] = { [USB_MODE_NONE] = "none", [USB_MODE_USB2] = "usb2", [USB_MODE_USB3] = "usb3", [USB_MODE_USB4] = "usb4" }; /* ------------------------------------------------------------------------- */ /* Alternate Modes */ static int altmode_match(struct device *dev, const void *data) { struct typec_altmode *adev = to_typec_altmode(dev); const struct typec_device_id *id = data; if (!is_typec_port_altmode(dev)) return 0; return (adev->svid == id->svid); } static void typec_altmode_set_partner(struct altmode *altmode) { struct typec_altmode *adev = &altmode->adev; struct typec_device_id id = { adev->svid }; struct typec_port *port = typec_altmode2port(adev); struct altmode *partner; struct device *dev; dev = device_find_child(&port->dev, &id, altmode_match); if (!dev) return; /* Bind the port alt mode to the partner/plug alt mode. */ partner = to_altmode(to_typec_altmode(dev)); altmode->partner = partner; /* Bind the partner/plug alt mode to the port alt mode. */ if (is_typec_plug(adev->dev.parent)) { struct typec_plug *plug = to_typec_plug(adev->dev.parent); partner->plug[plug->index] = altmode; } else { partner->partner = altmode; } } static void typec_altmode_put_partner(struct altmode *altmode) { struct altmode *partner = altmode->partner; struct typec_altmode *adev; struct typec_altmode *partner_adev; if (!partner) return; adev = &altmode->adev; partner_adev = &partner->adev; if (is_typec_plug(adev->dev.parent)) { struct typec_plug *plug = to_typec_plug(adev->dev.parent); partner->plug[plug->index] = NULL; } else { partner->partner = NULL; } put_device(&partner_adev->dev); } /** * typec_altmode_update_active - Report Enter/Exit mode * @adev: Handle to the alternate mode * @active: True when the mode has been entered * * If a partner or cable plug executes Enter/Exit Mode command successfully, the * drivers use this routine to report the updated state of the mode. */ void typec_altmode_update_active(struct typec_altmode *adev, bool active) { char dir[6]; if (adev->active == active) return; if (!is_typec_port(adev->dev.parent) && adev->dev.driver) { if (!active) module_put(adev->dev.driver->owner); else WARN_ON(!try_module_get(adev->dev.driver->owner)); } adev->active = active; snprintf(dir, sizeof(dir), "mode%d", adev->mode); sysfs_notify(&adev->dev.kobj, dir, "active"); sysfs_notify(&adev->dev.kobj, NULL, "active"); kobject_uevent(&adev->dev.kobj, KOBJ_CHANGE); } EXPORT_SYMBOL_GPL(typec_altmode_update_active); /** * typec_altmode2port - Alternate Mode to USB Type-C port * @alt: The Alternate Mode * * Returns handle to the port that a cable plug or partner with @alt is * connected to. */ struct typec_port *typec_altmode2port(struct typec_altmode *alt) { if (is_typec_plug(alt->dev.parent)) return to_typec_port(alt->dev.parent->parent->parent); if (is_typec_partner(alt->dev.parent)) return to_typec_port(alt->dev.parent->parent); if (is_typec_port(alt->dev.parent)) return to_typec_port(alt->dev.parent); return NULL; } EXPORT_SYMBOL_GPL(typec_altmode2port); static ssize_t vdo_show(struct device *dev, struct device_attribute *attr, char *buf) { struct typec_altmode *alt = to_typec_altmode(dev); return sprintf(buf, "0x%08x\n", alt->vdo); } static DEVICE_ATTR_RO(vdo); static ssize_t description_show(struct device *dev, struct device_attribute *attr, char *buf) { struct typec_altmode *alt = to_typec_altmode(dev); return sprintf(buf, "%s\n", alt->desc ? alt->desc : ""); } static DEVICE_ATTR_RO(description); static ssize_t active_show(struct device *dev, struct device_attribute *attr, char *buf) { struct typec_altmode *alt = to_typec_altmode(dev); return sprintf(buf, "%s\n", str_yes_no(alt->active)); } static ssize_t active_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct typec_altmode *adev = to_typec_altmode(dev); struct altmode *altmode = to_altmode(adev); bool enter; int ret; ret = kstrtobool(buf, &enter); if (ret) return ret; if (adev->active == enter) return size; if (is_typec_port(adev->dev.parent)) { typec_altmode_update_active(adev, enter); /* Make sure that the partner exits the mode before disabling */ if (altmode->partner && !enter && altmode->partner->adev.active) typec_altmode_exit(&altmode->partner->adev); } else if (altmode->partner) { if (enter && !altmode->partner->adev.active) { dev_warn(dev, "port has the mode disabled\n"); return -EPERM; } } /* Note: If there is no driver, the mode will not be entered */ if (adev->ops && adev->ops->activate) { ret = adev->ops->activate(adev, enter); if (ret) return ret; } return size; } static DEVICE_ATTR_RW(active); static ssize_t supported_roles_show(struct device *dev, struct device_attribute *attr, char *buf) { struct altmode *alt = to_altmode(to_typec_altmode(dev)); ssize_t ret; switch (alt->roles) { case TYPEC_PORT_SRC: ret = sprintf(buf, "source\n"); break; case TYPEC_PORT_SNK: ret = sprintf(buf, "sink\n"); break; case TYPEC_PORT_DRP: default: ret = sprintf(buf, "source sink\n"); break; } return ret; } static DEVICE_ATTR_RO(supported_roles); static ssize_t mode_show(struct device *dev, struct device_attribute *attr, char *buf) { struct typec_altmode *adev = to_typec_altmode(dev); return sprintf(buf, "%u\n", adev->mode); } static DEVICE_ATTR_RO(mode); static ssize_t svid_show(struct device *dev, struct device_attribute *attr, char *buf) { struct typec_altmode *adev = to_typec_altmode(dev); return sprintf(buf, "%04x\n", adev->svid); } static DEVICE_ATTR_RO(svid); static int increment_duplicated_priority(struct device *dev, void *data) { if (is_typec_port_altmode(dev)) { struct typec_altmode **alt_target = (struct typec_altmode **)data; struct typec_altmode *alt = to_typec_altmode(dev); if (alt != *alt_target && alt->priority == (*alt_target)->priority) { alt->priority++; *alt_target = alt; return 1; } } return 0; } static int find_duplicated_priority(struct device *dev, void *data) { if (is_typec_port_altmode(dev)) { struct typec_altmode **alt_target = (struct typec_altmode **)data; struct typec_altmode *alt = to_typec_altmode(dev); if (alt != *alt_target && alt->priority == (*alt_target)->priority) return 1; } return 0; } static int typec_mode_set_priority(struct typec_altmode *alt, const u8 priority) { struct typec_port *port = to_typec_port(alt->dev.parent); const u8 old_priority = alt->priority; int res = 1; alt->priority = priority; while (res) { res = device_for_each_child(&port->dev, &alt, find_duplicated_priority); if (res) { alt->priority++; if (alt->priority == 0) { alt->priority = old_priority; return -EOVERFLOW; } } } res = 1; alt->priority = priority; while (res) res = device_for_each_child(&port->dev, &alt, increment_duplicated_priority); return 0; }