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 }; 371