root/drivers/usb/typec/mux.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. name_match
  2. dev_name_ends_with
  3. switch_fwnode_match
  4. typec_switch_match
  5. typec_switch_get
  6. typec_switch_put
  7. typec_switch_release
  8. typec_switch_register
  9. typec_switch_unregister
  10. typec_switch_set_drvdata
  11. typec_switch_get_drvdata
  12. mux_fwnode_match
  13. typec_mux_match
  14. typec_mux_get
  15. typec_mux_put
  16. typec_mux_release
  17. typec_mux_register
  18. typec_mux_unregister
  19. typec_mux_set_drvdata
  20. typec_mux_get_drvdata

   1 // SPDX-License-Identifier: GPL-2.0
   2 /**
   3  * USB Type-C Multiplexer/DeMultiplexer Switch support
   4  *
   5  * Copyright (C) 2018 Intel Corporation
   6  * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
   7  *         Hans de Goede <hdegoede@redhat.com>
   8  */
   9 
  10 #include <linux/device.h>
  11 #include <linux/list.h>
  12 #include <linux/module.h>
  13 #include <linux/mutex.h>
  14 #include <linux/property.h>
  15 #include <linux/slab.h>
  16 #include <linux/usb/typec_mux.h>
  17 
  18 #include "bus.h"
  19 
  20 static int name_match(struct device *dev, const void *name)
  21 {
  22         return !strcmp((const char *)name, dev_name(dev));
  23 }
  24 
  25 static bool dev_name_ends_with(struct device *dev, const char *suffix)
  26 {
  27         const char *name = dev_name(dev);
  28         const int name_len = strlen(name);
  29         const int suffix_len = strlen(suffix);
  30 
  31         if (suffix_len > name_len)
  32                 return false;
  33 
  34         return strcmp(name + (name_len - suffix_len), suffix) == 0;
  35 }
  36 
  37 static int switch_fwnode_match(struct device *dev, const void *fwnode)
  38 {
  39         return dev_fwnode(dev) == fwnode && dev_name_ends_with(dev, "-switch");
  40 }
  41 
  42 static void *typec_switch_match(struct device_connection *con, int ep,
  43                                 void *data)
  44 {
  45         struct device *dev;
  46 
  47         if (con->fwnode) {
  48                 if (con->id && !fwnode_property_present(con->fwnode, con->id))
  49                         return NULL;
  50 
  51                 dev = class_find_device(&typec_mux_class, NULL, con->fwnode,
  52                                         switch_fwnode_match);
  53         } else {
  54                 dev = class_find_device(&typec_mux_class, NULL,
  55                                         con->endpoint[ep], name_match);
  56         }
  57 
  58         return dev ? to_typec_switch(dev) : ERR_PTR(-EPROBE_DEFER);
  59 }
  60 
  61 /**
  62  * typec_switch_get - Find USB Type-C orientation switch
  63  * @dev: The caller device
  64  *
  65  * Finds a switch linked with @dev. Returns a reference to the switch on
  66  * success, NULL if no matching connection was found, or
  67  * ERR_PTR(-EPROBE_DEFER) when a connection was found but the switch
  68  * has not been enumerated yet.
  69  */
  70 struct typec_switch *typec_switch_get(struct device *dev)
  71 {
  72         struct typec_switch *sw;
  73 
  74         sw = device_connection_find_match(dev, "orientation-switch", NULL,
  75                                           typec_switch_match);
  76         if (!IS_ERR_OR_NULL(sw))
  77                 WARN_ON(!try_module_get(sw->dev.parent->driver->owner));
  78 
  79         return sw;
  80 }
  81 EXPORT_SYMBOL_GPL(typec_switch_get);
  82 
  83 /**
  84  * typec_put_switch - Release USB Type-C orientation switch
  85  * @sw: USB Type-C orientation switch
  86  *
  87  * Decrement reference count for @sw.
  88  */
  89 void typec_switch_put(struct typec_switch *sw)
  90 {
  91         if (!IS_ERR_OR_NULL(sw)) {
  92                 module_put(sw->dev.parent->driver->owner);
  93                 put_device(&sw->dev);
  94         }
  95 }
  96 EXPORT_SYMBOL_GPL(typec_switch_put);
  97 
  98 static void typec_switch_release(struct device *dev)
  99 {
 100         kfree(to_typec_switch(dev));
 101 }
 102 
 103 static const struct device_type typec_switch_dev_type = {
 104         .name = "orientation_switch",
 105         .release = typec_switch_release,
 106 };
 107 
 108 /**
 109  * typec_switch_register - Register USB Type-C orientation switch
 110  * @parent: Parent device
 111  * @desc: Orientation switch description
 112  *
 113  * This function registers a switch that can be used for routing the correct
 114  * data pairs depending on the cable plug orientation from the USB Type-C
 115  * connector to the USB controllers. USB Type-C plugs can be inserted
 116  * right-side-up or upside-down.
 117  */
 118 struct typec_switch *
 119 typec_switch_register(struct device *parent,
 120                       const struct typec_switch_desc *desc)
 121 {
 122         struct typec_switch *sw;
 123         int ret;
 124 
 125         if (!desc || !desc->set)
 126                 return ERR_PTR(-EINVAL);
 127 
 128         sw = kzalloc(sizeof(*sw), GFP_KERNEL);
 129         if (!sw)
 130                 return ERR_PTR(-ENOMEM);
 131 
 132         sw->set = desc->set;
 133 
 134         device_initialize(&sw->dev);
 135         sw->dev.parent = parent;
 136         sw->dev.fwnode = desc->fwnode;
 137         sw->dev.class = &typec_mux_class;
 138         sw->dev.type = &typec_switch_dev_type;
 139         sw->dev.driver_data = desc->drvdata;
 140         dev_set_name(&sw->dev, "%s-switch", dev_name(parent));
 141 
 142         ret = device_add(&sw->dev);
 143         if (ret) {
 144                 dev_err(parent, "failed to register switch (%d)\n", ret);
 145                 put_device(&sw->dev);
 146                 return ERR_PTR(ret);
 147         }
 148 
 149         return sw;
 150 }
 151 EXPORT_SYMBOL_GPL(typec_switch_register);
 152 
 153 /**
 154  * typec_switch_unregister - Unregister USB Type-C orientation switch
 155  * @sw: USB Type-C orientation switch
 156  *
 157  * Unregister switch that was registered with typec_switch_register().
 158  */
 159 void typec_switch_unregister(struct typec_switch *sw)
 160 {
 161         if (!IS_ERR_OR_NULL(sw))
 162                 device_unregister(&sw->dev);
 163 }
 164 EXPORT_SYMBOL_GPL(typec_switch_unregister);
 165 
 166 void typec_switch_set_drvdata(struct typec_switch *sw, void *data)
 167 {
 168         dev_set_drvdata(&sw->dev, data);
 169 }
 170 EXPORT_SYMBOL_GPL(typec_switch_set_drvdata);
 171 
 172 void *typec_switch_get_drvdata(struct typec_switch *sw)
 173 {
 174         return dev_get_drvdata(&sw->dev);
 175 }
 176 EXPORT_SYMBOL_GPL(typec_switch_get_drvdata);
 177 
 178 /* ------------------------------------------------------------------------- */
 179 
 180 static int mux_fwnode_match(struct device *dev, const void *fwnode)
 181 {
 182         return dev_fwnode(dev) == fwnode && dev_name_ends_with(dev, "-mux");
 183 }
 184 
 185 static void *typec_mux_match(struct device_connection *con, int ep, void *data)
 186 {
 187         const struct typec_altmode_desc *desc = data;
 188         struct device *dev;
 189         bool match;
 190         int nval;
 191         u16 *val;
 192         int i;
 193 
 194         if (!con->fwnode) {
 195                 dev = class_find_device(&typec_mux_class, NULL,
 196                                         con->endpoint[ep], name_match);
 197 
 198                 return dev ? to_typec_switch(dev) : ERR_PTR(-EPROBE_DEFER);
 199         }
 200 
 201         /*
 202          * Check has the identifier already been "consumed". If it
 203          * has, no need to do any extra connection identification.
 204          */
 205         match = !con->id;
 206         if (match)
 207                 goto find_mux;
 208 
 209         /* Accessory Mode muxes */
 210         if (!desc) {
 211                 match = fwnode_property_present(con->fwnode, "accessory");
 212                 if (match)
 213                         goto find_mux;
 214                 return NULL;
 215         }
 216 
 217         /* Alternate Mode muxes */
 218         nval = fwnode_property_count_u16(con->fwnode, "svid");
 219         if (nval <= 0)
 220                 return NULL;
 221 
 222         val = kcalloc(nval, sizeof(*val), GFP_KERNEL);
 223         if (!val)
 224                 return ERR_PTR(-ENOMEM);
 225 
 226         nval = fwnode_property_read_u16_array(con->fwnode, "svid", val, nval);
 227         if (nval < 0) {
 228                 kfree(val);
 229                 return ERR_PTR(nval);
 230         }
 231 
 232         for (i = 0; i < nval; i++) {
 233                 match = val[i] == desc->svid;
 234                 if (match) {
 235                         kfree(val);
 236                         goto find_mux;
 237                 }
 238         }
 239         kfree(val);
 240         return NULL;
 241 
 242 find_mux:
 243         dev = class_find_device(&typec_mux_class, NULL, con->fwnode,
 244                                 mux_fwnode_match);
 245 
 246         return dev ? to_typec_switch(dev) : ERR_PTR(-EPROBE_DEFER);
 247 }
 248 
 249 /**
 250  * typec_mux_get - Find USB Type-C Multiplexer
 251  * @dev: The caller device
 252  * @desc: Alt Mode description
 253  *
 254  * Finds a mux linked to the caller. This function is primarily meant for the
 255  * Type-C drivers. Returns a reference to the mux on success, NULL if no
 256  * matching connection was found, or ERR_PTR(-EPROBE_DEFER) when a connection
 257  * was found but the mux has not been enumerated yet.
 258  */
 259 struct typec_mux *typec_mux_get(struct device *dev,
 260                                 const struct typec_altmode_desc *desc)
 261 {
 262         struct typec_mux *mux;
 263 
 264         mux = device_connection_find_match(dev, "mode-switch", (void *)desc,
 265                                            typec_mux_match);
 266         if (!IS_ERR_OR_NULL(mux))
 267                 WARN_ON(!try_module_get(mux->dev.parent->driver->owner));
 268 
 269         return mux;
 270 }
 271 EXPORT_SYMBOL_GPL(typec_mux_get);
 272 
 273 /**
 274  * typec_mux_put - Release handle to a Multiplexer
 275  * @mux: USB Type-C Connector Multiplexer/DeMultiplexer
 276  *
 277  * Decrements reference count for @mux.
 278  */
 279 void typec_mux_put(struct typec_mux *mux)
 280 {
 281         if (!IS_ERR_OR_NULL(mux)) {
 282                 module_put(mux->dev.parent->driver->owner);
 283                 put_device(&mux->dev);
 284         }
 285 }
 286 EXPORT_SYMBOL_GPL(typec_mux_put);
 287 
 288 static void typec_mux_release(struct device *dev)
 289 {
 290         kfree(to_typec_mux(dev));
 291 }
 292 
 293 static const struct device_type typec_mux_dev_type = {
 294         .name = "mode_switch",
 295         .release = typec_mux_release,
 296 };
 297 
 298 /**
 299  * typec_mux_register - Register Multiplexer routing USB Type-C pins
 300  * @parent: Parent device
 301  * @desc: Multiplexer description
 302  *
 303  * USB Type-C connectors can be used for alternate modes of operation besides
 304  * USB when Accessory/Alternate Modes are supported. With some of those modes,
 305  * the pins on the connector need to be reconfigured. This function registers
 306  * multiplexer switches routing the pins on the connector.
 307  */
 308 struct typec_mux *
 309 typec_mux_register(struct device *parent, const struct typec_mux_desc *desc)
 310 {
 311         struct typec_mux *mux;
 312         int ret;
 313 
 314         if (!desc || !desc->set)
 315                 return ERR_PTR(-EINVAL);
 316 
 317         mux = kzalloc(sizeof(*mux), GFP_KERNEL);
 318         if (!mux)
 319                 return ERR_PTR(-ENOMEM);
 320 
 321         mux->set = desc->set;
 322 
 323         device_initialize(&mux->dev);
 324         mux->dev.parent = parent;
 325         mux->dev.fwnode = desc->fwnode;
 326         mux->dev.class = &typec_mux_class;
 327         mux->dev.type = &typec_mux_dev_type;
 328         mux->dev.driver_data = desc->drvdata;
 329         dev_set_name(&mux->dev, "%s-mux", dev_name(parent));
 330 
 331         ret = device_add(&mux->dev);
 332         if (ret) {
 333                 dev_err(parent, "failed to register mux (%d)\n", ret);
 334                 put_device(&mux->dev);
 335                 return ERR_PTR(ret);
 336         }
 337 
 338         return mux;
 339 }
 340 EXPORT_SYMBOL_GPL(typec_mux_register);
 341 
 342 /**
 343  * typec_mux_unregister - Unregister Multiplexer Switch
 344  * @mux: USB Type-C Connector Multiplexer/DeMultiplexer
 345  *
 346  * Unregister mux that was registered with typec_mux_register().
 347  */
 348 void typec_mux_unregister(struct typec_mux *mux)
 349 {
 350         if (!IS_ERR_OR_NULL(mux))
 351                 device_unregister(&mux->dev);
 352 }
 353 EXPORT_SYMBOL_GPL(typec_mux_unregister);
 354 
 355 void typec_mux_set_drvdata(struct typec_mux *mux, void *data)
 356 {
 357         dev_set_drvdata(&mux->dev, data);
 358 }
 359 EXPORT_SYMBOL_GPL(typec_mux_set_drvdata);
 360 
 361 void *typec_mux_get_drvdata(struct typec_mux *mux)
 362 {
 363         return dev_get_drvdata(&mux->dev);
 364 }
 365 EXPORT_SYMBOL_GPL(typec_mux_get_drvdata);
 366 
 367 struct class typec_mux_class = {
 368         .name = "typec_mux",
 369         .owner = THIS_MODULE,
 370 };

/* [<][>][^][v][top][bottom][index][help] */