// 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/device.h>#include <linux/module.h>#include <linux/mutex.h>#include <linux/property.h>#include <linux/slab.h> #include "bus.h" struct typec_plug { struct device dev; enum typec_plug_index index; struct ida mode_ids;}; struct typec_cable { struct device dev; enum typec_plug_type type; struct usb_pd_identity *identity; unsigned int active:1;}; struct typec_partner { struct device dev; unsigned int usb_pd:1; struct usb_pd_identity *identity; enum typec_accessory accessory; struct ida mode_ids;}; struct typec_port { unsigned int id; struct device dev; struct ida mode_ids; int prefer_role; enum typec_data_role data_role; enum typec_role pwr_role; enum typec_role vconn_role; enum typec_pwr_opmode pwr_opmode; enum typec_port_type port_type; struct mutex port_type_lock; enum typec_orientation orientation; struct typec_switch *sw; struct typec_mux *mux; const struct typec_capability *cap;}; #define to_typec_port(_dev_) container_of(_dev_, struct typec_port, dev)#define to_typec_plug(_dev_) container_of(_dev_, struct typec_plug, dev)#define to_typec_cable(_dev_) container_of(_dev_, struct typec_cable, dev)#define to_typec_partner(_dev_) container_of(_dev_, struct typec_partner, dev) static const struct device_type typec_partner_dev_type;static const struct device_type typec_cable_dev_type;static const struct device_type typec_plug_dev_type; #define is_typec_partner(_dev_) (_dev_->type == &typec_partner_dev_type)#define is_typec_cable(_dev_) (_dev_->type == &typec_cable_dev_type)#define is_typec_plug(_dev_) (_dev_->type == &typec_plug_dev_type) static DEFINE_IDA(typec_index_ida);static struct class *typec_class; /* ------------------------------------------------------------------------- *//* Common attributes */ static const char * const typec_accessory_modes[] = { [TYPEC_ACCESSORY_NONE] = "none", [TYPEC_ACCESSORY_AUDIO] = "analog_audio", [TYPEC_ACCESSORY_DEBUG] = "debug",}; 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 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 struct attribute *usb_pd_id_attrs[] = { &dev_attr_id_header.attr, &dev_attr_cert_stat.attr, &dev_attr_product.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_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");} /* ------------------------------------------------------------------------- *//* Alternate Modes */ static int altmode_match(struct device *dev, void *data){ struct typec_altmode *adev = to_typec_altmode(dev); struct typec_device_id *id = data; if (!is_typec_altmode(dev)) return 0; return ((adev->svid == id->svid) && (adev->mode == id->mode));} static void typec_altmode_set_partner(struct altmode *altmode){ struct typec_altmode *adev = &altmode->adev; struct typec_device_id id = { adev->svid, adev->mode, }; 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; if (!partner) return; 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(&adev->dev);} static int typec_port_fwnode_match(struct device *dev, const void *fwnode){ return dev_fwnode(dev) == fwnode;} static int typec_port_name_match(struct device *dev, const void *name){ return !strcmp((const char *)name, dev_name(dev));} static void *typec_port_match(struct device_connection *con, int ep, void *data){ struct device *dev; /* * FIXME: Check does the fwnode supports the requested SVID. If it does * we need to return ERR_PTR(-PROBE_DEFER) when there is no device. */ if (con->fwnode) return class_find_device(typec_class, NULL, con->fwnode, typec_port_fwnode_match); dev = class_find_device(typec_class, NULL, con->endpoint[ep], typec_port_name_match); return dev ? dev : ERR_PTR(-EPROBE_DEFER);} struct typec_altmode *typec_altmode_register_notifier(struct device *dev, u16 svid, u8 mode, struct notifier_block *nb){ struct typec_device_id id = { svid, mode, }; struct device *altmode_dev; struct device *port_dev; struct altmode *altmode; int ret; /* Find the port linked to the caller */ port_dev = device_connection_find_match(dev, NULL, NULL, typec_port_match); if (IS_ERR_OR_NULL(port_dev)) return port_dev ? ERR_CAST(port_dev) : ERR_PTR(-ENODEV); /* Find the altmode with matching svid */ altmode_dev = device_find_child(port_dev, &id, altmode_match); put_device(port_dev); if (!altmode_dev) return ERR_PTR(-ENODEV); altmode = to_altmode(to_typec_altmode(altmode_dev)); /* Register notifier */ ret = blocking_notifier_chain_register(&altmode->nh, nb); if (ret) { put_device(altmode_dev); return ERR_PTR(ret); } return &altmode->adev;}EXPORT_SYMBOL_GPL(typec_altmode_register_notifier); void typec_altmode_unregister_notifier(struct typec_altmode *adev, struct notifier_block *nb){ struct altmode *altmode = to_altmode(adev); blocking_notifier_chain_unregister(&altmode->nh, nb); put_device(&adev->dev);}EXPORT_SYMBOL_GPL(typec_altmode_unregister_notifier); /** * 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", alt->active ? "yes" : "no");} 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 struct attribute *typec_altmode_attrs[] = { &dev_attr_active.attr, &dev_attr_mode.attr, &dev_attr_svid.attr, &dev_attr_vdo.attr, NULL};ATTRIBUTE_GROUPS(typec_altmode); static int altmode_id_get(struct device *dev){ struct ida *ids; if (is_typec_partner(dev)) ids = &to_typec_partner(dev)->mode_ids; else if (is_typec_plug(dev)) ids = &to_typec_plug(dev)->mode_ids; else ids = &to_typec_port(dev)->mode_ids; return ida_simple_get(ids, 0, 0, GFP_KERNEL);} static void altmode_id_remove(struct device *dev, int id){ struct ida *ids; if (is_typec_partner(dev)) ids = &to_typec_partner(dev)->mode_ids; else if (is_typec_plug(dev)) ids = &to_typec_plug(dev)->mode_ids; else ids = &to_typec_port(dev)->mode_ids; ida_simple_remove(ids, id);} static void typec_altmode_release(struct device *dev){ struct altmode *alt = to_altmode(to_typec_altmode(dev)); typec_altmode_put_partner(alt); altmode_id_remove(alt->adev.dev.parent, alt->id); kfree(alt);} const struct device_type typec_altmode_dev_type = { .name = "typec_alternate_mode", .groups = typec_altmode_groups, .release = typec_altmode_release,}; static struct typec_altmode *typec_register_altmode(struct device *parent, const struct typec_altmode_desc *desc){ unsigned int id = altmode_id_get(parent); bool is_port = is_typec_port(parent); struct altmode *alt; int ret; Lines 1–500 of 1669. 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/device.h> #include <linux/module.h> #include <linux/mutex.h> #include <linux/property.h> #include <linux/slab.h> #include "bus.h" struct typec_plug { struct device dev; enum typec_plug_index index; struct ida mode_ids; }; struct typec_cable { struct device dev; enum typec_plug_type type; struct usb_pd_identity *identity; unsigned int active:1; }; struct typec_partner { struct device dev; unsigned int usb_pd:1; struct usb_pd_identity *identity; enum typec_accessory accessory; struct ida mode_ids; }; struct typec_port { unsigned int id; struct device dev; struct ida mode_ids; int prefer_role; enum typec_data_role data_role; enum typec_role pwr_role; enum typec_role vconn_role; enum typec_pwr_opmode pwr_opmode; enum typec_port_type port_type; struct mutex port_type_lock; enum typec_orientation orientation; struct typec_switch *sw; struct typec_mux *mux; const struct typec_capability *cap; }; #define to_typec_port(_dev_) container_of(_dev_, struct typec_port, dev) #define to_typec_plug(_dev_) container_of(_dev_, struct typec_plug, dev) #define to_typec_cable(_dev_) container_of(_dev_, struct typec_cable, dev) #define to_typec_partner(_dev_) container_of(_dev_, struct typec_partner, dev) static const struct device_type typec_partner_dev_type; static const struct device_type typec_cable_dev_type; static const struct device_type typec_plug_dev_type; #define is_typec_partner(_dev_) (_dev_->type == &typec_partner_dev_type) #define is_typec_cable(_dev_) (_dev_->type == &typec_cable_dev_type) #define is_typec_plug(_dev_) (_dev_->type == &typec_plug_dev_type) static DEFINE_IDA(typec_index_ida); static struct class *typec_class; /* ------------------------------------------------------------------------- */ /* Common attributes */ static const char * const typec_accessory_modes[] = { [TYPEC_ACCESSORY_NONE] = "none", [TYPEC_ACCESSORY_AUDIO] = "analog_audio", [TYPEC_ACCESSORY_DEBUG] = "debug", }; 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 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 struct attribute *usb_pd_id_attrs[] = { &dev_attr_id_header.attr, &dev_attr_cert_stat.attr, &dev_attr_product.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_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"); } /* ------------------------------------------------------------------------- */ /* Alternate Modes */ static int altmode_match(struct device *dev, void *data) { struct typec_altmode *adev = to_typec_altmode(dev); struct typec_device_id *id = data; if (!is_typec_altmode(dev)) return 0; return ((adev->svid == id->svid) && (adev->mode == id->mode)); } static void typec_altmode_set_partner(struct altmode *altmode) { struct typec_altmode *adev = &altmode->adev; struct typec_device_id id = { adev->svid, adev->mode, }; 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; if (!partner) return; 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(&adev->dev); } static int typec_port_fwnode_match(struct device *dev, const void *fwnode) { return dev_fwnode(dev) == fwnode; } static int typec_port_name_match(struct device *dev, const void *name) { return !strcmp((const char *)name, dev_name(dev)); } static void *typec_port_match(struct device_connection *con, int ep, void *data) { struct device *dev; /* * FIXME: Check does the fwnode supports the requested SVID. If it does * we need to return ERR_PTR(-PROBE_DEFER) when there is no device. */ if (con->fwnode) return class_find_device(typec_class, NULL, con->fwnode, typec_port_fwnode_match); dev = class_find_device(typec_class, NULL, con->endpoint[ep], typec_port_name_match); return dev ? dev : ERR_PTR(-EPROBE_DEFER); } struct typec_altmode * typec_altmode_register_notifier(struct device *dev, u16 svid, u8 mode, struct notifier_block *nb) { struct typec_device_id id = { svid, mode, }; struct device *altmode_dev; struct device *port_dev; struct altmode *altmode; int ret; /* Find the port linked to the caller */ port_dev = device_connection_find_match(dev, NULL, NULL, typec_port_match); if (IS_ERR_OR_NULL(port_dev)) return port_dev ? ERR_CAST(port_dev) : ERR_PTR(-ENODEV); /* Find the altmode with matching svid */ altmode_dev = device_find_child(port_dev, &id, altmode_match); put_device(port_dev); if (!altmode_dev) return ERR_PTR(-ENODEV); altmode = to_altmode(to_typec_altmode(altmode_dev)); /* Register notifier */ ret = blocking_notifier_chain_register(&altmode->nh, nb); if (ret) { put_device(altmode_dev); return ERR_PTR(ret); } return &altmode->adev; } EXPORT_SYMBOL_GPL(typec_altmode_register_notifier); void typec_altmode_unregister_notifier(struct typec_altmode *adev, struct notifier_block *nb) { struct altmode *altmode = to_altmode(adev); blocking_notifier_chain_unregister(&altmode->nh, nb); put_device(&adev->dev); } EXPORT_SYMBOL_GPL(typec_altmode_unregister_notifier); /** * 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", alt->active ? "yes" : "no"); } 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 struct attribute *typec_altmode_attrs[] = { &dev_attr_active.attr, &dev_attr_mode.attr, &dev_attr_svid.attr, &dev_attr_vdo.attr, NULL }; ATTRIBUTE_GROUPS(typec_altmode); static int altmode_id_get(struct device *dev) { struct ida *ids; if (is_typec_partner(dev)) ids = &to_typec_partner(dev)->mode_ids; else if (is_typec_plug(dev)) ids = &to_typec_plug(dev)->mode_ids; else ids = &to_typec_port(dev)->mode_ids; return ida_simple_get(ids, 0, 0, GFP_KERNEL); } static void altmode_id_remove(struct device *dev, int id) { struct ida *ids; if (is_typec_partner(dev)) ids = &to_typec_partner(dev)->mode_ids; else if (is_typec_plug(dev)) ids = &to_typec_plug(dev)->mode_ids; else ids = &to_typec_port(dev)->mode_ids; ida_simple_remove(ids, id); } static void typec_altmode_release(struct device *dev) { struct altmode *alt = to_altmode(to_typec_altmode(dev)); typec_altmode_put_partner(alt); altmode_id_remove(alt->adev.dev.parent, alt->id); kfree(alt); } const struct device_type typec_altmode_dev_type = { .name = "typec_alternate_mode", .groups = typec_altmode_groups, .release = typec_altmode_release, }; static struct typec_altmode * typec_register_altmode(struct device *parent, const struct typec_altmode_desc *desc) { unsigned int id = altmode_id_get(parent); bool is_port = is_typec_port(parent); struct altmode *alt; int ret;