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
  18static DEFINE_MUTEX(switch_lock);
  19static DEFINE_MUTEX(mux_lock);
  20static LIST_HEAD(switch_list);
  21static LIST_HEAD(mux_list);
  22
  23static void *typec_switch_match(struct device_connection *con, int ep,
  24                                void *data)
  25{
  26        struct typec_switch *sw;
  27
  28        if (!con->fwnode) {
  29                list_for_each_entry(sw, &switch_list, entry)
  30                        if (!strcmp(con->endpoint[ep], dev_name(sw->dev)))
  31                                return sw;
  32                return ERR_PTR(-EPROBE_DEFER);
  33        }
  34
  35        /*
  36         * With OF graph the mux node must have a boolean device property named
  37         * "orientation-switch".
  38         */
  39        if (con->id && !fwnode_property_present(con->fwnode, con->id))
  40                return NULL;
  41
  42        list_for_each_entry(sw, &switch_list, entry)
  43                if (dev_fwnode(sw->dev) == con->fwnode)
  44                        return sw;
  45
  46        return con->id ? ERR_PTR(-EPROBE_DEFER) : NULL;
  47}
  48
  49/**
  50 * typec_switch_get - Find USB Type-C orientation switch
  51 * @dev: The caller device
  52 *
  53 * Finds a switch linked with @dev. Returns a reference to the switch on
  54 * success, NULL if no matching connection was found, or
  55 * ERR_PTR(-EPROBE_DEFER) when a connection was found but the switch
  56 * has not been enumerated yet.
  57 */
  58struct typec_switch *typec_switch_get(struct device *dev)
  59{
  60        struct typec_switch *sw;
  61
  62        mutex_lock(&switch_lock);
  63        sw = device_connection_find_match(dev, "orientation-switch", NULL,
  64                                          typec_switch_match);
  65        if (!IS_ERR_OR_NULL(sw)) {
  66                WARN_ON(!try_module_get(sw->dev->driver->owner));
  67                get_device(sw->dev);
  68        }
  69        mutex_unlock(&switch_lock);
  70
  71        return sw;
  72}
  73EXPORT_SYMBOL_GPL(typec_switch_get);
  74
  75/**
  76 * typec_put_switch - Release USB Type-C orientation switch
  77 * @sw: USB Type-C orientation switch
  78 *
  79 * Decrement reference count for @sw.
  80 */
  81void typec_switch_put(struct typec_switch *sw)
  82{
  83        if (!IS_ERR_OR_NULL(sw)) {
  84                module_put(sw->dev->driver->owner);
  85                put_device(sw->dev);
  86        }
  87}
  88EXPORT_SYMBOL_GPL(typec_switch_put);
  89
  90/**
  91 * typec_switch_register - Register USB Type-C orientation switch
  92 * @sw: USB Type-C orientation switch
  93 *
  94 * This function registers a switch that can be used for routing the correct
  95 * data pairs depending on the cable plug orientation from the USB Type-C
  96 * connector to the USB controllers. USB Type-C plugs can be inserted
  97 * right-side-up or upside-down.
  98 */
  99int typec_switch_register(struct typec_switch *sw)
 100{
 101        mutex_lock(&switch_lock);
 102        list_add_tail(&sw->entry, &switch_list);
 103        mutex_unlock(&switch_lock);
 104
 105        return 0;
 106}
 107EXPORT_SYMBOL_GPL(typec_switch_register);
 108
 109/**
 110 * typec_switch_unregister - Unregister USB Type-C orientation switch
 111 * @sw: USB Type-C orientation switch
 112 *
 113 * Unregister switch that was registered with typec_switch_register().
 114 */
 115void typec_switch_unregister(struct typec_switch *sw)
 116{
 117        mutex_lock(&switch_lock);
 118        list_del(&sw->entry);
 119        mutex_unlock(&switch_lock);
 120}
 121EXPORT_SYMBOL_GPL(typec_switch_unregister);
 122
 123/* ------------------------------------------------------------------------- */
 124
 125static void *typec_mux_match(struct device_connection *con, int ep, void *data)
 126{
 127        const struct typec_altmode_desc *desc = data;
 128        struct typec_mux *mux;
 129        int nval;
 130        bool match;
 131        u16 *val;
 132        int i;
 133
 134        if (!con->fwnode) {
 135                list_for_each_entry(mux, &mux_list, entry)
 136                        if (!strcmp(con->endpoint[ep], dev_name(mux->dev)))
 137                                return mux;
 138                return ERR_PTR(-EPROBE_DEFER);
 139        }
 140
 141        /*
 142         * Check has the identifier already been "consumed". If it
 143         * has, no need to do any extra connection identification.
 144         */
 145        match = !con->id;
 146        if (match)
 147                goto find_mux;
 148
 149        /* Accessory Mode muxes */
 150        if (!desc) {
 151                match = fwnode_property_present(con->fwnode, "accessory");
 152                if (match)
 153                        goto find_mux;
 154                return NULL;
 155        }
 156
 157        /* Alternate Mode muxes */
 158        nval = fwnode_property_read_u16_array(con->fwnode, "svid", NULL, 0);
 159        if (nval <= 0)
 160                return NULL;
 161
 162        val = kcalloc(nval, sizeof(*val), GFP_KERNEL);
 163        if (!val)
 164                return ERR_PTR(-ENOMEM);
 165
 166        nval = fwnode_property_read_u16_array(con->fwnode, "svid", val, nval);
 167        if (nval < 0) {
 168                kfree(val);
 169                return ERR_PTR(nval);
 170        }
 171
 172        for (i = 0; i < nval; i++) {
 173                match = val[i] == desc->svid;
 174                if (match) {
 175                        kfree(val);
 176                        goto find_mux;
 177                }
 178        }
 179        kfree(val);
 180        return NULL;
 181
 182find_mux:
 183        list_for_each_entry(mux, &mux_list, entry)
 184                if (dev_fwnode(mux->dev) == con->fwnode)
 185                        return mux;
 186
 187        return ERR_PTR(-EPROBE_DEFER);
 188}
 189
 190/**
 191 * typec_mux_get - Find USB Type-C Multiplexer
 192 * @dev: The caller device
 193 * @desc: Alt Mode description
 194 *
 195 * Finds a mux linked to the caller. This function is primarily meant for the
 196 * Type-C drivers. Returns a reference to the mux on success, NULL if no
 197 * matching connection was found, or ERR_PTR(-EPROBE_DEFER) when a connection
 198 * was found but the mux has not been enumerated yet.
 199 */
 200struct typec_mux *typec_mux_get(struct device *dev,
 201                                const struct typec_altmode_desc *desc)
 202{
 203        struct typec_mux *mux;
 204
 205        mutex_lock(&mux_lock);
 206        mux = device_connection_find_match(dev, "mode-switch", (void *)desc,
 207                                           typec_mux_match);
 208        if (!IS_ERR_OR_NULL(mux)) {
 209                WARN_ON(!try_module_get(mux->dev->driver->owner));
 210                get_device(mux->dev);
 211        }
 212        mutex_unlock(&mux_lock);
 213
 214        return mux;
 215}
 216EXPORT_SYMBOL_GPL(typec_mux_get);
 217
 218/**
 219 * typec_mux_put - Release handle to a Multiplexer
 220 * @mux: USB Type-C Connector Multiplexer/DeMultiplexer
 221 *
 222 * Decrements reference count for @mux.
 223 */
 224void typec_mux_put(struct typec_mux *mux)
 225{
 226        if (!IS_ERR_OR_NULL(mux)) {
 227                module_put(mux->dev->driver->owner);
 228                put_device(mux->dev);
 229        }
 230}
 231EXPORT_SYMBOL_GPL(typec_mux_put);
 232
 233/**
 234 * typec_mux_register - Register Multiplexer routing USB Type-C pins
 235 * @mux: USB Type-C Connector Multiplexer/DeMultiplexer
 236 *
 237 * USB Type-C connectors can be used for alternate modes of operation besides
 238 * USB when Accessory/Alternate Modes are supported. With some of those modes,
 239 * the pins on the connector need to be reconfigured. This function registers
 240 * multiplexer switches routing the pins on the connector.
 241 */
 242int typec_mux_register(struct typec_mux *mux)
 243{
 244        mutex_lock(&mux_lock);
 245        list_add_tail(&mux->entry, &mux_list);
 246        mutex_unlock(&mux_lock);
 247
 248        return 0;
 249}
 250EXPORT_SYMBOL_GPL(typec_mux_register);
 251
 252/**
 253 * typec_mux_unregister - Unregister Multiplexer Switch
 254 * @mux: USB Type-C Connector Multiplexer/DeMultiplexer
 255 *
 256 * Unregister mux that was registered with typec_mux_register().
 257 */
 258void typec_mux_unregister(struct typec_mux *mux)
 259{
 260        mutex_lock(&mux_lock);
 261        list_del(&mux->entry);
 262        mutex_unlock(&mux_lock);
 263}
 264EXPORT_SYMBOL_GPL(typec_mux_unregister);
 265