Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
at v5.5-rc5 370 lines 9.2 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#include <linux/usb/typec_mux.h> 17 18#include "bus.h" 19 20static int name_match(struct device *dev, const void *name) 21{ 22 return !strcmp((const char *)name, dev_name(dev)); 23} 24 25static 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 37static 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 42static 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 */ 70struct 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} 81EXPORT_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 */ 89void 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} 96EXPORT_SYMBOL_GPL(typec_switch_put); 97 98static void typec_switch_release(struct device *dev) 99{ 100 kfree(to_typec_switch(dev)); 101} 102 103static 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 */ 118struct typec_switch * 119typec_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} 151EXPORT_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 */ 159void typec_switch_unregister(struct typec_switch *sw) 160{ 161 if (!IS_ERR_OR_NULL(sw)) 162 device_unregister(&sw->dev); 163} 164EXPORT_SYMBOL_GPL(typec_switch_unregister); 165 166void typec_switch_set_drvdata(struct typec_switch *sw, void *data) 167{ 168 dev_set_drvdata(&sw->dev, data); 169} 170EXPORT_SYMBOL_GPL(typec_switch_set_drvdata); 171 172void *typec_switch_get_drvdata(struct typec_switch *sw) 173{ 174 return dev_get_drvdata(&sw->dev); 175} 176EXPORT_SYMBOL_GPL(typec_switch_get_drvdata); 177 178/* ------------------------------------------------------------------------- */ 179 180static 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 185static 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 242find_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 */ 259struct 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} 271EXPORT_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 */ 279void 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} 286EXPORT_SYMBOL_GPL(typec_mux_put); 287 288static void typec_mux_release(struct device *dev) 289{ 290 kfree(to_typec_mux(dev)); 291} 292 293static 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 */ 308struct typec_mux * 309typec_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} 340EXPORT_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 */ 348void typec_mux_unregister(struct typec_mux *mux) 349{ 350 if (!IS_ERR_OR_NULL(mux)) 351 device_unregister(&mux->dev); 352} 353EXPORT_SYMBOL_GPL(typec_mux_unregister); 354 355void typec_mux_set_drvdata(struct typec_mux *mux, void *data) 356{ 357 dev_set_drvdata(&mux->dev, data); 358} 359EXPORT_SYMBOL_GPL(typec_mux_set_drvdata); 360 361void *typec_mux_get_drvdata(struct typec_mux *mux) 362{ 363 return dev_get_drvdata(&mux->dev); 364} 365EXPORT_SYMBOL_GPL(typec_mux_get_drvdata); 366 367struct class typec_mux_class = { 368 .name = "typec_mux", 369 .owner = THIS_MODULE, 370};