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/usb/typec_mux.h> 15 16 static DEFINE_MUTEX(switch_lock); 17 static DEFINE_MUTEX(mux_lock); 18 static LIST_HEAD(switch_list); 19 static LIST_HEAD(mux_list); 20 21 static void *typec_switch_match(struct device_connection *con, int ep, 22 void *data) 23 { 24 struct typec_switch *sw; 25 26 list_for_each_entry(sw, &switch_list, entry) 27 if (!strcmp(con->endpoint[ep], dev_name(sw->dev))) 28 return sw; 29 30 /* 31 * We only get called if a connection was found, tell the caller to 32 * wait for the switch to show up. 33 */ 34 return ERR_PTR(-EPROBE_DEFER); 35 } 36 37 /** 38 * typec_switch_get - Find USB Type-C orientation switch 39 * @dev: The caller device 40 * 41 * Finds a switch linked with @dev. Returns a reference to the switch on 42 * success, NULL if no matching connection was found, or 43 * ERR_PTR(-EPROBE_DEFER) when a connection was found but the switch 44 * has not been enumerated yet. 45 */ 46 struct typec_switch *typec_switch_get(struct device *dev) 47 { 48 struct typec_switch *sw; 49 50 mutex_lock(&switch_lock); 51 sw = device_connection_find_match(dev, "orientation-switch", NULL, 52 typec_switch_match); 53 if (!IS_ERR_OR_NULL(sw)) { 54 WARN_ON(!try_module_get(sw->dev->driver->owner)); 55 get_device(sw->dev); 56 } 57 mutex_unlock(&switch_lock); 58 59 return sw; 60 } 61 EXPORT_SYMBOL_GPL(typec_switch_get); 62 63 /** 64 * typec_put_switch - Release USB Type-C orientation switch 65 * @sw: USB Type-C orientation switch 66 * 67 * Decrement reference count for @sw. 68 */ 69 void typec_switch_put(struct typec_switch *sw) 70 { 71 if (!IS_ERR_OR_NULL(sw)) { 72 module_put(sw->dev->driver->owner); 73 put_device(sw->dev); 74 } 75 } 76 EXPORT_SYMBOL_GPL(typec_switch_put); 77 78 /** 79 * typec_switch_register - Register USB Type-C orientation switch 80 * @sw: USB Type-C orientation switch 81 * 82 * This function registers a switch that can be used for routing the correct 83 * data pairs depending on the cable plug orientation from the USB Type-C 84 * connector to the USB controllers. USB Type-C plugs can be inserted 85 * right-side-up or upside-down. 86 */ 87 int typec_switch_register(struct typec_switch *sw) 88 { 89 mutex_lock(&switch_lock); 90 list_add_tail(&sw->entry, &switch_list); 91 mutex_unlock(&switch_lock); 92 93 return 0; 94 } 95 EXPORT_SYMBOL_GPL(typec_switch_register); 96 97 /** 98 * typec_switch_unregister - Unregister USB Type-C orientation switch 99 * @sw: USB Type-C orientation switch 100 * 101 * Unregister switch that was registered with typec_switch_register(). 102 */ 103 void typec_switch_unregister(struct typec_switch *sw) 104 { 105 mutex_lock(&switch_lock); 106 list_del(&sw->entry); 107 mutex_unlock(&switch_lock); 108 } 109 EXPORT_SYMBOL_GPL(typec_switch_unregister); 110 111 /* ------------------------------------------------------------------------- */ 112 113 static void *typec_mux_match(struct device_connection *con, int ep, void *data) 114 { 115 struct typec_mux *mux; 116 117 list_for_each_entry(mux, &mux_list, entry) 118 if (!strcmp(con->endpoint[ep], dev_name(mux->dev))) 119 return mux; 120 121 /* 122 * We only get called if a connection was found, tell the caller to 123 * wait for the switch to show up. 124 */ 125 return ERR_PTR(-EPROBE_DEFER); 126 } 127 128 /** 129 * typec_mux_get - Find USB Type-C Multiplexer 130 * @dev: The caller device 131 * @desc: Alt Mode description 132 * 133 * Finds a mux linked to the caller. This function is primarily meant for the 134 * Type-C drivers. Returns a reference to the mux on success, NULL if no 135 * matching connection was found, or ERR_PTR(-EPROBE_DEFER) when a connection 136 * was found but the mux has not been enumerated yet. 137 */ 138 struct typec_mux *typec_mux_get(struct device *dev, 139 const struct typec_altmode_desc *desc) 140 { 141 struct typec_mux *mux; 142 143 mutex_lock(&mux_lock); 144 mux = device_connection_find_match(dev, "mode-switch", (void *)desc, 145 typec_mux_match); 146 if (!IS_ERR_OR_NULL(mux)) { 147 WARN_ON(!try_module_get(mux->dev->driver->owner)); 148 get_device(mux->dev); 149 } 150 mutex_unlock(&mux_lock); 151 152 return mux; 153 } 154 EXPORT_SYMBOL_GPL(typec_mux_get); 155 156 /** 157 * typec_mux_put - Release handle to a Multiplexer 158 * @mux: USB Type-C Connector Multiplexer/DeMultiplexer 159 * 160 * Decrements reference count for @mux. 161 */ 162 void typec_mux_put(struct typec_mux *mux) 163 { 164 if (!IS_ERR_OR_NULL(mux)) { 165 module_put(mux->dev->driver->owner); 166 put_device(mux->dev); 167 } 168 } 169 EXPORT_SYMBOL_GPL(typec_mux_put); 170 171 /** 172 * typec_mux_register - Register Multiplexer routing USB Type-C pins 173 * @mux: USB Type-C Connector Multiplexer/DeMultiplexer 174 * 175 * USB Type-C connectors can be used for alternate modes of operation besides 176 * USB when Accessory/Alternate Modes are supported. With some of those modes, 177 * the pins on the connector need to be reconfigured. This function registers 178 * multiplexer switches routing the pins on the connector. 179 */ 180 int typec_mux_register(struct typec_mux *mux) 181 { 182 mutex_lock(&mux_lock); 183 list_add_tail(&mux->entry, &mux_list); 184 mutex_unlock(&mux_lock); 185 186 return 0; 187 } 188 EXPORT_SYMBOL_GPL(typec_mux_register); 189 190 /** 191 * typec_mux_unregister - Unregister Multiplexer Switch 192 * @mux: USB Type-C Connector Multiplexer/DeMultiplexer 193 * 194 * Unregister mux that was registered with typec_mux_register(). 195 */ 196 void typec_mux_unregister(struct typec_mux *mux) 197 { 198 mutex_lock(&mux_lock); 199 list_del(&mux->entry); 200 mutex_unlock(&mux_lock); 201 } 202 EXPORT_SYMBOL_GPL(typec_mux_unregister); 203