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