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 static int switch_fwnode_match(struct device *dev, const void *fwnode) 21 { 22 if (!is_typec_switch(dev)) 23 return 0; 24 25 return dev_fwnode(dev) == fwnode; 26 } 27 28 static void *typec_switch_match(struct fwnode_handle *fwnode, const char *id, 29 void *data) 30 { 31 struct device *dev; 32 33 /* 34 * Device graph (OF graph) does not give any means to identify the 35 * device type or the device class of the remote port parent that @fwnode 36 * represents, so in order to identify the type or the class of @fwnode 37 * an additional device property is needed. With typec switches the 38 * property is named "orientation-switch" (@id). The value of the device 39 * property is ignored. 40 */ 41 if (id && !fwnode_property_present(fwnode, id)) 42 return NULL; 43 44 /* 45 * At this point we are sure that @fwnode is a typec switch in all 46 * cases. If the switch hasn't yet been registered for some reason, the 47 * function "defers probe" for now. 48 */ 49 dev = class_find_device(&typec_mux_class, NULL, fwnode, 50 switch_fwnode_match); 51 52 return dev ? to_typec_switch(dev) : ERR_PTR(-EPROBE_DEFER); 53 } 54 55 /** 56 * fwnode_typec_switch_get - Find USB Type-C orientation switch 57 * @fwnode: The caller device node 58 * 59 * Finds a switch linked with @dev. Returns a reference to the switch on 60 * success, NULL if no matching connection was found, or 61 * ERR_PTR(-EPROBE_DEFER) when a connection was found but the switch 62 * has not been enumerated yet. 63 */ 64 struct typec_switch *fwnode_typec_switch_get(struct fwnode_handle *fwnode) 65 { 66 struct typec_switch *sw; 67 68 sw = fwnode_connection_find_match(fwnode, "orientation-switch", NULL, 69 typec_switch_match); 70 if (!IS_ERR_OR_NULL(sw)) 71 WARN_ON(!try_module_get(sw->dev.parent->driver->owner)); 72 73 return sw; 74 } 75 EXPORT_SYMBOL_GPL(fwnode_typec_switch_get); 76 77 /** 78 * typec_switch_put - Release USB Type-C orientation switch 79 * @sw: USB Type-C orientation switch 80 * 81 * Decrement reference count for @sw. 82 */ 83 void typec_switch_put(struct typec_switch *sw) 84 { 85 if (!IS_ERR_OR_NULL(sw)) { 86 module_put(sw->dev.parent->driver->owner); 87 put_device(&sw->dev); 88 } 89 } 90 EXPORT_SYMBOL_GPL(typec_switch_put); 91 92 static void typec_switch_release(struct device *dev) 93 { 94 kfree(to_typec_switch(dev)); 95 } 96 97 const struct device_type typec_switch_dev_type = { 98 .name = "orientation_switch", 99 .release = typec_switch_release, 100 }; 101 102 /** 103 * typec_switch_register - Register USB Type-C orientation switch 104 * @parent: Parent device 105 * @desc: Orientation switch description 106 * 107 * This function registers a switch that can be used for routing the correct 108 * data pairs depending on the cable plug orientation from the USB Type-C 109 * connector to the USB controllers. USB Type-C plugs can be inserted 110 * right-side-up or upside-down. 111 */ 112 struct typec_switch * 113 typec_switch_register(struct device *parent, 114 const struct typec_switch_desc *desc) 115 { 116 struct typec_switch *sw; 117 int ret; 118 119 if (!desc || !desc->set) 120 return ERR_PTR(-EINVAL); 121 122 sw = kzalloc(sizeof(*sw), GFP_KERNEL); 123 if (!sw) 124 return ERR_PTR(-ENOMEM); 125 126 sw->set = desc->set; 127 128 device_initialize(&sw->dev); 129 sw->dev.parent = parent; 130 sw->dev.fwnode = desc->fwnode; 131 sw->dev.class = &typec_mux_class; 132 sw->dev.type = &typec_switch_dev_type; 133 sw->dev.driver_data = desc->drvdata; 134 dev_set_name(&sw->dev, "%s-switch", 135 desc->name ? desc->name : dev_name(parent)); 136 137 ret = device_add(&sw->dev); 138 if (ret) { 139 dev_err(parent, "failed to register switch (%d)\n", ret); 140 put_device(&sw->dev); 141 return ERR_PTR(ret); 142 } 143 144 return sw; 145 } 146 EXPORT_SYMBOL_GPL(typec_switch_register); 147 148 int typec_switch_set(struct typec_switch *sw, 149 enum typec_orientation orientation) 150 { 151 if (IS_ERR_OR_NULL(sw)) 152 return 0; 153 154 return sw->set(sw, orientation); 155 } 156 EXPORT_SYMBOL_GPL(typec_switch_set); 157 158 /** 159 * typec_switch_unregister - Unregister USB Type-C orientation switch 160 * @sw: USB Type-C orientation switch 161 * 162 * Unregister switch that was registered with typec_switch_register(). 163 */ 164 void typec_switch_unregister(struct typec_switch *sw) 165 { 166 if (!IS_ERR_OR_NULL(sw)) 167 device_unregister(&sw->dev); 168 } 169 EXPORT_SYMBOL_GPL(typec_switch_unregister); 170 171 void typec_switch_set_drvdata(struct typec_switch *sw, void *data) 172 { 173 dev_set_drvdata(&sw->dev, data); 174 } 175 EXPORT_SYMBOL_GPL(typec_switch_set_drvdata); 176 177 void *typec_switch_get_drvdata(struct typec_switch *sw) 178 { 179 return dev_get_drvdata(&sw->dev); 180 } 181 EXPORT_SYMBOL_GPL(typec_switch_get_drvdata); 182 183 /* ------------------------------------------------------------------------- */ 184 185 static int mux_fwnode_match(struct device *dev, const void *fwnode) 186 { 187 if (!is_typec_mux(dev)) 188 return 0; 189 190 return dev_fwnode(dev) == fwnode; 191 } 192 193 static void *typec_mux_match(struct fwnode_handle *fwnode, const char *id, 194 void *data) 195 { 196 const struct typec_altmode_desc *desc = data; 197 struct device *dev; 198 bool match; 199 int nval; 200 u16 *val; 201 int ret; 202 int i; 203 204 /* 205 * Check has the identifier already been "consumed". If it 206 * has, no need to do any extra connection identification. 207 */ 208 match = !id; 209 if (match) 210 goto find_mux; 211 212 /* Accessory Mode muxes */ 213 if (!desc) { 214 match = fwnode_property_present(fwnode, "accessory"); 215 if (match) 216 goto find_mux; 217 return NULL; 218 } 219 220 /* Alternate Mode muxes */ 221 nval = fwnode_property_count_u16(fwnode, "svid"); 222 if (nval <= 0) 223 return NULL; 224 225 val = kcalloc(nval, sizeof(*val), GFP_KERNEL); 226 if (!val) 227 return ERR_PTR(-ENOMEM); 228 229 ret = fwnode_property_read_u16_array(fwnode, "svid", val, nval); 230 if (ret < 0) { 231 kfree(val); 232 return ERR_PTR(ret); 233 } 234 235 for (i = 0; i < nval; i++) { 236 match = val[i] == desc->svid; 237 if (match) { 238 kfree(val); 239 goto find_mux; 240 } 241 } 242 kfree(val); 243 return NULL; 244 245 find_mux: 246 dev = class_find_device(&typec_mux_class, NULL, fwnode, 247 mux_fwnode_match); 248 249 return dev ? to_typec_mux(dev) : ERR_PTR(-EPROBE_DEFER); 250 } 251 252 /** 253 * fwnode_typec_mux_get - Find USB Type-C Multiplexer 254 * @fwnode: The caller device node 255 * @desc: Alt Mode description 256 * 257 * Finds a mux linked to the caller. This function is primarily meant for the 258 * Type-C drivers. Returns a reference to the mux on success, NULL if no 259 * matching connection was found, or ERR_PTR(-EPROBE_DEFER) when a connection 260 * was found but the mux has not been enumerated yet. 261 */ 262 struct typec_mux *fwnode_typec_mux_get(struct fwnode_handle *fwnode, 263 const struct typec_altmode_desc *desc) 264 { 265 struct typec_mux *mux; 266 267 mux = fwnode_connection_find_match(fwnode, "mode-switch", (void *)desc, 268 typec_mux_match); 269 if (!IS_ERR_OR_NULL(mux)) 270 WARN_ON(!try_module_get(mux->dev.parent->driver->owner)); 271 272 return mux; 273 } 274 EXPORT_SYMBOL_GPL(fwnode_typec_mux_get); 275 276 /** 277 * typec_mux_put - Release handle to a Multiplexer 278 * @mux: USB Type-C Connector Multiplexer/DeMultiplexer 279 * 280 * Decrements reference count for @mux. 281 */ 282 void typec_mux_put(struct typec_mux *mux) 283 { 284 if (!IS_ERR_OR_NULL(mux)) { 285 module_put(mux->dev.parent->driver->owner); 286 put_device(&mux->dev); 287 } 288 } 289 EXPORT_SYMBOL_GPL(typec_mux_put); 290 291 int typec_mux_set(struct typec_mux *mux, struct typec_mux_state *state) 292 { 293 if (IS_ERR_OR_NULL(mux)) 294 return 0; 295 296 return mux->set(mux, state); 297 } 298 EXPORT_SYMBOL_GPL(typec_mux_set); 299 300 static void typec_mux_release(struct device *dev) 301 { 302 kfree(to_typec_mux(dev)); 303 } 304 305 const struct device_type typec_mux_dev_type = { 306 .name = "mode_switch", 307 .release = typec_mux_release, 308 }; 309 310 /** 311 * typec_mux_register - Register Multiplexer routing USB Type-C pins 312 * @parent: Parent device 313 * @desc: Multiplexer description 314 * 315 * USB Type-C connectors can be used for alternate modes of operation besides 316 * USB when Accessory/Alternate Modes are supported. With some of those modes, 317 * the pins on the connector need to be reconfigured. This function registers 318 * multiplexer switches routing the pins on the connector. 319 */ 320 struct typec_mux * 321 typec_mux_register(struct device *parent, const struct typec_mux_desc *desc) 322 { 323 struct typec_mux *mux; 324 int ret; 325 326 if (!desc || !desc->set) 327 return ERR_PTR(-EINVAL); 328 329 mux = kzalloc(sizeof(*mux), GFP_KERNEL); 330 if (!mux) 331 return ERR_PTR(-ENOMEM); 332 333 mux->set = desc->set; 334 335 device_initialize(&mux->dev); 336 mux->dev.parent = parent; 337 mux->dev.fwnode = desc->fwnode; 338 mux->dev.class = &typec_mux_class; 339 mux->dev.type = &typec_mux_dev_type; 340 mux->dev.driver_data = desc->drvdata; 341 dev_set_name(&mux->dev, "%s-mux", 342 desc->name ? desc->name : dev_name(parent)); 343 344 ret = device_add(&mux->dev); 345 if (ret) { 346 dev_err(parent, "failed to register mux (%d)\n", ret); 347 put_device(&mux->dev); 348 return ERR_PTR(ret); 349 } 350 351 return mux; 352 } 353 EXPORT_SYMBOL_GPL(typec_mux_register); 354 355 /** 356 * typec_mux_unregister - Unregister Multiplexer Switch 357 * @mux: USB Type-C Connector Multiplexer/DeMultiplexer 358 * 359 * Unregister mux that was registered with typec_mux_register(). 360 */ 361 void typec_mux_unregister(struct typec_mux *mux) 362 { 363 if (!IS_ERR_OR_NULL(mux)) 364 device_unregister(&mux->dev); 365 } 366 EXPORT_SYMBOL_GPL(typec_mux_unregister); 367 368 void typec_mux_set_drvdata(struct typec_mux *mux, void *data) 369 { 370 dev_set_drvdata(&mux->dev, data); 371 } 372 EXPORT_SYMBOL_GPL(typec_mux_set_drvdata); 373 374 void *typec_mux_get_drvdata(struct typec_mux *mux) 375 { 376 return dev_get_drvdata(&mux->dev); 377 } 378 EXPORT_SYMBOL_GPL(typec_mux_get_drvdata); 379 380 struct class typec_mux_class = { 381 .name = "typec_mux", 382 .owner = THIS_MODULE, 383 }; 384