Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
at master 495 lines 12 kB view raw
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 20#define TYPEC_MUX_MAX_DEVS 3 21 22struct typec_switch { 23 struct typec_switch_dev *sw_devs[TYPEC_MUX_MAX_DEVS]; 24 unsigned int num_sw_devs; 25}; 26 27static int switch_fwnode_match(struct device *dev, const void *fwnode) 28{ 29 if (!is_typec_switch_dev(dev)) 30 return 0; 31 32 return device_match_fwnode(dev, fwnode); 33} 34 35static void *typec_switch_match(const struct fwnode_handle *fwnode, 36 const char *id, void *data) 37{ 38 struct typec_switch_dev **sw_devs = data; 39 struct device *dev; 40 int i; 41 42 /* 43 * Device graph (OF graph) does not give any means to identify the 44 * device type or the device class of the remote port parent that @fwnode 45 * represents, so in order to identify the type or the class of @fwnode 46 * an additional device property is needed. With typec switches the 47 * property is named "orientation-switch" (@id). The value of the device 48 * property is ignored. 49 */ 50 if (id && !fwnode_property_present(fwnode, id)) 51 return NULL; 52 53 /* 54 * At this point we are sure that @fwnode is a typec switch in all 55 * cases. If the switch hasn't yet been registered for some reason, the 56 * function "defers probe" for now. 57 */ 58 dev = class_find_device(&typec_mux_class, NULL, fwnode, 59 switch_fwnode_match); 60 61 /* Skip duplicates */ 62 for (i = 0; i < TYPEC_MUX_MAX_DEVS; i++) 63 if (to_typec_switch_dev(dev) == sw_devs[i]) { 64 put_device(dev); 65 return NULL; 66 } 67 68 return dev ? to_typec_switch_dev(dev) : ERR_PTR(-EPROBE_DEFER); 69} 70 71/** 72 * fwnode_typec_switch_get - Find USB Type-C orientation switch 73 * @fwnode: The caller device node 74 * 75 * Finds a switch linked with @dev. Returns a reference to the switch on 76 * success, NULL if no matching connection was found, or 77 * ERR_PTR(-EPROBE_DEFER) when a connection was found but the switch 78 * has not been enumerated yet. 79 */ 80struct typec_switch *fwnode_typec_switch_get(struct fwnode_handle *fwnode) 81{ 82 struct typec_switch_dev *sw_devs[TYPEC_MUX_MAX_DEVS]; 83 struct typec_switch *sw; 84 int count; 85 int err; 86 int i; 87 88 sw = kzalloc_obj(*sw); 89 if (!sw) 90 return ERR_PTR(-ENOMEM); 91 92 count = fwnode_connection_find_matches(fwnode, "orientation-switch", 93 (void **)sw_devs, 94 typec_switch_match, 95 (void **)sw_devs, 96 ARRAY_SIZE(sw_devs)); 97 if (count <= 0) { 98 kfree(sw); 99 return NULL; 100 } 101 102 for (i = 0; i < count; i++) { 103 if (IS_ERR(sw_devs[i])) { 104 err = PTR_ERR(sw_devs[i]); 105 goto put_sw_devs; 106 } 107 } 108 109 for (i = 0; i < count; i++) { 110 WARN_ON(!try_module_get(sw_devs[i]->dev.parent->driver->owner)); 111 sw->sw_devs[i] = sw_devs[i]; 112 } 113 114 sw->num_sw_devs = count; 115 116 return sw; 117 118put_sw_devs: 119 for (i = 0; i < count; i++) { 120 if (!IS_ERR(sw_devs[i])) 121 put_device(&sw_devs[i]->dev); 122 } 123 124 kfree(sw); 125 126 return ERR_PTR(err); 127} 128EXPORT_SYMBOL_GPL(fwnode_typec_switch_get); 129 130/** 131 * typec_switch_put - Release USB Type-C orientation switch 132 * @sw: USB Type-C orientation switch 133 * 134 * Decrement reference count for @sw. 135 */ 136void typec_switch_put(struct typec_switch *sw) 137{ 138 struct typec_switch_dev *sw_dev; 139 unsigned int i; 140 141 if (IS_ERR_OR_NULL(sw)) 142 return; 143 144 for (i = 0; i < sw->num_sw_devs; i++) { 145 sw_dev = sw->sw_devs[i]; 146 147 module_put(sw_dev->dev.parent->driver->owner); 148 put_device(&sw_dev->dev); 149 } 150 kfree(sw); 151} 152EXPORT_SYMBOL_GPL(typec_switch_put); 153 154static void typec_switch_release(struct device *dev) 155{ 156 kfree(to_typec_switch_dev(dev)); 157} 158 159const struct device_type typec_switch_dev_type = { 160 .name = "orientation_switch", 161 .release = typec_switch_release, 162}; 163 164/** 165 * typec_switch_register - Register USB Type-C orientation switch 166 * @parent: Parent device 167 * @desc: Orientation switch description 168 * 169 * This function registers a switch that can be used for routing the correct 170 * data pairs depending on the cable plug orientation from the USB Type-C 171 * connector to the USB controllers. USB Type-C plugs can be inserted 172 * right-side-up or upside-down. 173 */ 174struct typec_switch_dev * 175typec_switch_register(struct device *parent, 176 const struct typec_switch_desc *desc) 177{ 178 struct typec_switch_dev *sw_dev; 179 int ret; 180 181 if (!desc || !desc->set) 182 return ERR_PTR(-EINVAL); 183 184 sw_dev = kzalloc_obj(*sw_dev); 185 if (!sw_dev) 186 return ERR_PTR(-ENOMEM); 187 188 sw_dev->set = desc->set; 189 190 device_initialize(&sw_dev->dev); 191 sw_dev->dev.parent = parent; 192 sw_dev->dev.fwnode = desc->fwnode; 193 sw_dev->dev.class = &typec_mux_class; 194 sw_dev->dev.type = &typec_switch_dev_type; 195 sw_dev->dev.driver_data = desc->drvdata; 196 ret = dev_set_name(&sw_dev->dev, "%s-switch", desc->name ? desc->name : dev_name(parent)); 197 if (ret) { 198 put_device(&sw_dev->dev); 199 return ERR_PTR(ret); 200 } 201 202 ret = device_add(&sw_dev->dev); 203 if (ret) { 204 dev_err(parent, "failed to register switch (%d)\n", ret); 205 put_device(&sw_dev->dev); 206 return ERR_PTR(ret); 207 } 208 209 return sw_dev; 210} 211EXPORT_SYMBOL_GPL(typec_switch_register); 212 213int typec_switch_set(struct typec_switch *sw, 214 enum typec_orientation orientation) 215{ 216 struct typec_switch_dev *sw_dev; 217 unsigned int i; 218 int ret; 219 220 if (IS_ERR_OR_NULL(sw)) 221 return 0; 222 223 for (i = 0; i < sw->num_sw_devs; i++) { 224 sw_dev = sw->sw_devs[i]; 225 226 ret = sw_dev->set(sw_dev, orientation); 227 if (ret && ret != -EOPNOTSUPP) 228 return ret; 229 } 230 231 return 0; 232} 233EXPORT_SYMBOL_GPL(typec_switch_set); 234 235/** 236 * typec_switch_unregister - Unregister USB Type-C orientation switch 237 * @sw_dev: USB Type-C orientation switch 238 * 239 * Unregister switch that was registered with typec_switch_register(). 240 */ 241void typec_switch_unregister(struct typec_switch_dev *sw_dev) 242{ 243 if (!IS_ERR_OR_NULL(sw_dev)) 244 device_unregister(&sw_dev->dev); 245} 246EXPORT_SYMBOL_GPL(typec_switch_unregister); 247 248void typec_switch_set_drvdata(struct typec_switch_dev *sw_dev, void *data) 249{ 250 dev_set_drvdata(&sw_dev->dev, data); 251} 252EXPORT_SYMBOL_GPL(typec_switch_set_drvdata); 253 254void *typec_switch_get_drvdata(struct typec_switch_dev *sw_dev) 255{ 256 return dev_get_drvdata(&sw_dev->dev); 257} 258EXPORT_SYMBOL_GPL(typec_switch_get_drvdata); 259 260/* ------------------------------------------------------------------------- */ 261 262struct typec_mux { 263 struct typec_mux_dev *mux_devs[TYPEC_MUX_MAX_DEVS]; 264 unsigned int num_mux_devs; 265}; 266 267static int mux_fwnode_match(struct device *dev, const void *fwnode) 268{ 269 if (!is_typec_mux_dev(dev)) 270 return 0; 271 272 return device_match_fwnode(dev, fwnode); 273} 274 275static void *typec_mux_match(const struct fwnode_handle *fwnode, 276 const char *id, void *data) 277{ 278 struct typec_mux_dev **mux_devs = data; 279 struct device *dev; 280 int i; 281 282 /* 283 * Device graph (OF graph) does not give any means to identify the 284 * device type or the device class of the remote port parent that @fwnode 285 * represents, so in order to identify the type or the class of @fwnode 286 * an additional device property is needed. With typec muxes the 287 * property is named "mode-switch" (@id). The value of the device 288 * property is ignored. 289 */ 290 if (id && !fwnode_property_present(fwnode, id)) 291 return NULL; 292 293 dev = class_find_device(&typec_mux_class, NULL, fwnode, 294 mux_fwnode_match); 295 296 /* Skip duplicates */ 297 for (i = 0; i < TYPEC_MUX_MAX_DEVS; i++) 298 if (to_typec_mux_dev(dev) == mux_devs[i]) { 299 put_device(dev); 300 return NULL; 301 } 302 303 304 return dev ? to_typec_mux_dev(dev) : ERR_PTR(-EPROBE_DEFER); 305} 306 307/** 308 * fwnode_typec_mux_get - Find USB Type-C Multiplexer 309 * @fwnode: The caller device node 310 * 311 * Finds a mux linked to the caller. This function is primarily meant for the 312 * Type-C drivers. Returns a reference to the mux on success, NULL if no 313 * matching connection was found, or ERR_PTR(-EPROBE_DEFER) when a connection 314 * was found but the mux has not been enumerated yet. 315 */ 316struct typec_mux *fwnode_typec_mux_get(struct fwnode_handle *fwnode) 317{ 318 struct typec_mux_dev *mux_devs[TYPEC_MUX_MAX_DEVS]; 319 struct typec_mux *mux; 320 int count; 321 int err; 322 int i; 323 324 mux = kzalloc_obj(*mux); 325 if (!mux) 326 return ERR_PTR(-ENOMEM); 327 328 count = fwnode_connection_find_matches(fwnode, "mode-switch", 329 (void **)mux_devs, 330 typec_mux_match, 331 (void **)mux_devs, 332 ARRAY_SIZE(mux_devs)); 333 if (count <= 0) { 334 kfree(mux); 335 return NULL; 336 } 337 338 for (i = 0; i < count; i++) { 339 if (IS_ERR(mux_devs[i])) { 340 err = PTR_ERR(mux_devs[i]); 341 goto put_mux_devs; 342 } 343 } 344 345 for (i = 0; i < count; i++) { 346 WARN_ON(!try_module_get(mux_devs[i]->dev.parent->driver->owner)); 347 mux->mux_devs[i] = mux_devs[i]; 348 } 349 350 mux->num_mux_devs = count; 351 352 return mux; 353 354put_mux_devs: 355 for (i = 0; i < count; i++) { 356 if (!IS_ERR(mux_devs[i])) 357 put_device(&mux_devs[i]->dev); 358 } 359 360 kfree(mux); 361 362 return ERR_PTR(err); 363} 364EXPORT_SYMBOL_GPL(fwnode_typec_mux_get); 365 366/** 367 * typec_mux_put - Release handle to a Multiplexer 368 * @mux: USB Type-C Connector Multiplexer/DeMultiplexer 369 * 370 * Decrements reference count for @mux. 371 */ 372void typec_mux_put(struct typec_mux *mux) 373{ 374 struct typec_mux_dev *mux_dev; 375 unsigned int i; 376 377 if (IS_ERR_OR_NULL(mux)) 378 return; 379 380 for (i = 0; i < mux->num_mux_devs; i++) { 381 mux_dev = mux->mux_devs[i]; 382 module_put(mux_dev->dev.parent->driver->owner); 383 put_device(&mux_dev->dev); 384 } 385 kfree(mux); 386} 387EXPORT_SYMBOL_GPL(typec_mux_put); 388 389int typec_mux_set(struct typec_mux *mux, struct typec_mux_state *state) 390{ 391 struct typec_mux_dev *mux_dev; 392 unsigned int i; 393 int ret; 394 395 if (IS_ERR_OR_NULL(mux)) 396 return 0; 397 398 for (i = 0; i < mux->num_mux_devs; i++) { 399 mux_dev = mux->mux_devs[i]; 400 401 ret = mux_dev->set(mux_dev, state); 402 if (ret && ret != -EOPNOTSUPP) 403 return ret; 404 } 405 406 return 0; 407} 408EXPORT_SYMBOL_GPL(typec_mux_set); 409 410static void typec_mux_release(struct device *dev) 411{ 412 kfree(to_typec_mux_dev(dev)); 413} 414 415const struct device_type typec_mux_dev_type = { 416 .name = "mode_switch", 417 .release = typec_mux_release, 418}; 419 420/** 421 * typec_mux_register - Register Multiplexer routing USB Type-C pins 422 * @parent: Parent device 423 * @desc: Multiplexer description 424 * 425 * USB Type-C connectors can be used for alternate modes of operation besides 426 * USB when Accessory/Alternate Modes are supported. With some of those modes, 427 * the pins on the connector need to be reconfigured. This function registers 428 * multiplexer switches routing the pins on the connector. 429 */ 430struct typec_mux_dev * 431typec_mux_register(struct device *parent, const struct typec_mux_desc *desc) 432{ 433 struct typec_mux_dev *mux_dev; 434 int ret; 435 436 if (!desc || !desc->set) 437 return ERR_PTR(-EINVAL); 438 439 mux_dev = kzalloc_obj(*mux_dev); 440 if (!mux_dev) 441 return ERR_PTR(-ENOMEM); 442 443 mux_dev->set = desc->set; 444 445 device_initialize(&mux_dev->dev); 446 mux_dev->dev.parent = parent; 447 mux_dev->dev.fwnode = desc->fwnode; 448 mux_dev->dev.class = &typec_mux_class; 449 mux_dev->dev.type = &typec_mux_dev_type; 450 mux_dev->dev.driver_data = desc->drvdata; 451 ret = dev_set_name(&mux_dev->dev, "%s-mux", desc->name ? desc->name : dev_name(parent)); 452 if (ret) { 453 put_device(&mux_dev->dev); 454 return ERR_PTR(ret); 455 } 456 457 ret = device_add(&mux_dev->dev); 458 if (ret) { 459 dev_err(parent, "failed to register mux (%d)\n", ret); 460 put_device(&mux_dev->dev); 461 return ERR_PTR(ret); 462 } 463 464 return mux_dev; 465} 466EXPORT_SYMBOL_GPL(typec_mux_register); 467 468/** 469 * typec_mux_unregister - Unregister Multiplexer Switch 470 * @mux_dev: USB Type-C Connector Multiplexer/DeMultiplexer 471 * 472 * Unregister mux that was registered with typec_mux_register(). 473 */ 474void typec_mux_unregister(struct typec_mux_dev *mux_dev) 475{ 476 if (!IS_ERR_OR_NULL(mux_dev)) 477 device_unregister(&mux_dev->dev); 478} 479EXPORT_SYMBOL_GPL(typec_mux_unregister); 480 481void typec_mux_set_drvdata(struct typec_mux_dev *mux_dev, void *data) 482{ 483 dev_set_drvdata(&mux_dev->dev, data); 484} 485EXPORT_SYMBOL_GPL(typec_mux_set_drvdata); 486 487void *typec_mux_get_drvdata(struct typec_mux_dev *mux_dev) 488{ 489 return dev_get_drvdata(&mux_dev->dev); 490} 491EXPORT_SYMBOL_GPL(typec_mux_get_drvdata); 492 493const struct class typec_mux_class = { 494 .name = "typec_mux", 495};