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