1bdecb33aSHeikki Krogerus // SPDX-License-Identifier: GPL-2.0 2bdecb33aSHeikki Krogerus /** 3bdecb33aSHeikki Krogerus * USB Type-C Multiplexer/DeMultiplexer Switch support 4bdecb33aSHeikki Krogerus * 5bdecb33aSHeikki Krogerus * Copyright (C) 2018 Intel Corporation 6bdecb33aSHeikki Krogerus * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com> 7bdecb33aSHeikki Krogerus * Hans de Goede <hdegoede@redhat.com> 8bdecb33aSHeikki Krogerus */ 9bdecb33aSHeikki Krogerus 10bdecb33aSHeikki Krogerus #include <linux/device.h> 11bdecb33aSHeikki Krogerus #include <linux/list.h> 123e3b8196SHeikki Krogerus #include <linux/module.h> 13bdecb33aSHeikki Krogerus #include <linux/mutex.h> 1496a6d031SHeikki Krogerus #include <linux/property.h> 1596a6d031SHeikki Krogerus #include <linux/slab.h> 16bdecb33aSHeikki Krogerus #include <linux/usb/typec_mux.h> 17bdecb33aSHeikki Krogerus 18bdecb33aSHeikki Krogerus static DEFINE_MUTEX(switch_lock); 19bdecb33aSHeikki Krogerus static DEFINE_MUTEX(mux_lock); 20bdecb33aSHeikki Krogerus static LIST_HEAD(switch_list); 21bdecb33aSHeikki Krogerus static LIST_HEAD(mux_list); 22bdecb33aSHeikki Krogerus 23bdecb33aSHeikki Krogerus static void *typec_switch_match(struct device_connection *con, int ep, 24bdecb33aSHeikki Krogerus void *data) 25bdecb33aSHeikki Krogerus { 26bdecb33aSHeikki Krogerus struct typec_switch *sw; 27bdecb33aSHeikki Krogerus 2896a6d031SHeikki Krogerus if (!con->fwnode) { 29bdecb33aSHeikki Krogerus list_for_each_entry(sw, &switch_list, entry) 30bdecb33aSHeikki Krogerus if (!strcmp(con->endpoint[ep], dev_name(sw->dev))) 31bdecb33aSHeikki Krogerus return sw; 3296a6d031SHeikki Krogerus return ERR_PTR(-EPROBE_DEFER); 3396a6d031SHeikki Krogerus } 34bdecb33aSHeikki Krogerus 35bdecb33aSHeikki Krogerus /* 3696a6d031SHeikki Krogerus * With OF graph the mux node must have a boolean device property named 3796a6d031SHeikki Krogerus * "orientation-switch". 38bdecb33aSHeikki Krogerus */ 3996a6d031SHeikki Krogerus if (con->id && !fwnode_property_present(con->fwnode, con->id)) 4096a6d031SHeikki Krogerus return NULL; 4196a6d031SHeikki Krogerus 4296a6d031SHeikki Krogerus list_for_each_entry(sw, &switch_list, entry) 4396a6d031SHeikki Krogerus if (dev_fwnode(sw->dev) == con->fwnode) 4496a6d031SHeikki Krogerus return sw; 4596a6d031SHeikki Krogerus 4696a6d031SHeikki Krogerus return con->id ? ERR_PTR(-EPROBE_DEFER) : NULL; 47bdecb33aSHeikki Krogerus } 48bdecb33aSHeikki Krogerus 49bdecb33aSHeikki Krogerus /** 50bdecb33aSHeikki Krogerus * typec_switch_get - Find USB Type-C orientation switch 51bdecb33aSHeikki Krogerus * @dev: The caller device 52bdecb33aSHeikki Krogerus * 53bdecb33aSHeikki Krogerus * Finds a switch linked with @dev. Returns a reference to the switch on 54bdecb33aSHeikki Krogerus * success, NULL if no matching connection was found, or 55bdecb33aSHeikki Krogerus * ERR_PTR(-EPROBE_DEFER) when a connection was found but the switch 56bdecb33aSHeikki Krogerus * has not been enumerated yet. 57bdecb33aSHeikki Krogerus */ 58bdecb33aSHeikki Krogerus struct typec_switch *typec_switch_get(struct device *dev) 59bdecb33aSHeikki Krogerus { 60bdecb33aSHeikki Krogerus struct typec_switch *sw; 61bdecb33aSHeikki Krogerus 62bdecb33aSHeikki Krogerus mutex_lock(&switch_lock); 63540bfab7SHeikki Krogerus sw = device_connection_find_match(dev, "orientation-switch", NULL, 64bdecb33aSHeikki Krogerus typec_switch_match); 653e3b8196SHeikki Krogerus if (!IS_ERR_OR_NULL(sw)) { 663e3b8196SHeikki Krogerus WARN_ON(!try_module_get(sw->dev->driver->owner)); 67bdecb33aSHeikki Krogerus get_device(sw->dev); 683e3b8196SHeikki Krogerus } 69bdecb33aSHeikki Krogerus mutex_unlock(&switch_lock); 70bdecb33aSHeikki Krogerus 71bdecb33aSHeikki Krogerus return sw; 72bdecb33aSHeikki Krogerus } 73bdecb33aSHeikki Krogerus EXPORT_SYMBOL_GPL(typec_switch_get); 74bdecb33aSHeikki Krogerus 75bdecb33aSHeikki Krogerus /** 76bdecb33aSHeikki Krogerus * typec_put_switch - Release USB Type-C orientation switch 77bdecb33aSHeikki Krogerus * @sw: USB Type-C orientation switch 78bdecb33aSHeikki Krogerus * 79bdecb33aSHeikki Krogerus * Decrement reference count for @sw. 80bdecb33aSHeikki Krogerus */ 81bdecb33aSHeikki Krogerus void typec_switch_put(struct typec_switch *sw) 82bdecb33aSHeikki Krogerus { 833e3b8196SHeikki Krogerus if (!IS_ERR_OR_NULL(sw)) { 843e3b8196SHeikki Krogerus module_put(sw->dev->driver->owner); 85bdecb33aSHeikki Krogerus put_device(sw->dev); 86bdecb33aSHeikki Krogerus } 873e3b8196SHeikki Krogerus } 88bdecb33aSHeikki Krogerus EXPORT_SYMBOL_GPL(typec_switch_put); 89bdecb33aSHeikki Krogerus 90bdecb33aSHeikki Krogerus /** 91bdecb33aSHeikki Krogerus * typec_switch_register - Register USB Type-C orientation switch 92bdecb33aSHeikki Krogerus * @sw: USB Type-C orientation switch 93bdecb33aSHeikki Krogerus * 94bdecb33aSHeikki Krogerus * This function registers a switch that can be used for routing the correct 95bdecb33aSHeikki Krogerus * data pairs depending on the cable plug orientation from the USB Type-C 96bdecb33aSHeikki Krogerus * connector to the USB controllers. USB Type-C plugs can be inserted 97bdecb33aSHeikki Krogerus * right-side-up or upside-down. 98bdecb33aSHeikki Krogerus */ 99bdecb33aSHeikki Krogerus int typec_switch_register(struct typec_switch *sw) 100bdecb33aSHeikki Krogerus { 101bdecb33aSHeikki Krogerus mutex_lock(&switch_lock); 102bdecb33aSHeikki Krogerus list_add_tail(&sw->entry, &switch_list); 103bdecb33aSHeikki Krogerus mutex_unlock(&switch_lock); 104bdecb33aSHeikki Krogerus 105bdecb33aSHeikki Krogerus return 0; 106bdecb33aSHeikki Krogerus } 107bdecb33aSHeikki Krogerus EXPORT_SYMBOL_GPL(typec_switch_register); 108bdecb33aSHeikki Krogerus 109bdecb33aSHeikki Krogerus /** 110bdecb33aSHeikki Krogerus * typec_switch_unregister - Unregister USB Type-C orientation switch 111bdecb33aSHeikki Krogerus * @sw: USB Type-C orientation switch 112bdecb33aSHeikki Krogerus * 113bdecb33aSHeikki Krogerus * Unregister switch that was registered with typec_switch_register(). 114bdecb33aSHeikki Krogerus */ 115bdecb33aSHeikki Krogerus void typec_switch_unregister(struct typec_switch *sw) 116bdecb33aSHeikki Krogerus { 117bdecb33aSHeikki Krogerus mutex_lock(&switch_lock); 118bdecb33aSHeikki Krogerus list_del(&sw->entry); 119bdecb33aSHeikki Krogerus mutex_unlock(&switch_lock); 120bdecb33aSHeikki Krogerus } 121bdecb33aSHeikki Krogerus EXPORT_SYMBOL_GPL(typec_switch_unregister); 122bdecb33aSHeikki Krogerus 123bdecb33aSHeikki Krogerus /* ------------------------------------------------------------------------- */ 124bdecb33aSHeikki Krogerus 125bdecb33aSHeikki Krogerus static void *typec_mux_match(struct device_connection *con, int ep, void *data) 126bdecb33aSHeikki Krogerus { 12796a6d031SHeikki Krogerus const struct typec_altmode_desc *desc = data; 128bdecb33aSHeikki Krogerus struct typec_mux *mux; 12996a6d031SHeikki Krogerus size_t nval; 13096a6d031SHeikki Krogerus bool match; 13196a6d031SHeikki Krogerus u16 *val; 13296a6d031SHeikki Krogerus int i; 133bdecb33aSHeikki Krogerus 13496a6d031SHeikki Krogerus if (!con->fwnode) { 135bdecb33aSHeikki Krogerus list_for_each_entry(mux, &mux_list, entry) 136bdecb33aSHeikki Krogerus if (!strcmp(con->endpoint[ep], dev_name(mux->dev))) 137bdecb33aSHeikki Krogerus return mux; 13896a6d031SHeikki Krogerus return ERR_PTR(-EPROBE_DEFER); 13996a6d031SHeikki Krogerus } 140bdecb33aSHeikki Krogerus 141bdecb33aSHeikki Krogerus /* 14296a6d031SHeikki Krogerus * Check has the identifier already been "consumed". If it 14396a6d031SHeikki Krogerus * has, no need to do any extra connection identification. 144bdecb33aSHeikki Krogerus */ 14596a6d031SHeikki Krogerus match = !con->id; 14696a6d031SHeikki Krogerus if (match) 14796a6d031SHeikki Krogerus goto find_mux; 14896a6d031SHeikki Krogerus 14996a6d031SHeikki Krogerus /* Accessory Mode muxes */ 15096a6d031SHeikki Krogerus if (!desc) { 15196a6d031SHeikki Krogerus match = fwnode_property_present(con->fwnode, "accessory"); 15296a6d031SHeikki Krogerus if (match) 15396a6d031SHeikki Krogerus goto find_mux; 15496a6d031SHeikki Krogerus return NULL; 15596a6d031SHeikki Krogerus } 15696a6d031SHeikki Krogerus 15796a6d031SHeikki Krogerus /* Alternate Mode muxes */ 15896a6d031SHeikki Krogerus nval = fwnode_property_read_u16_array(con->fwnode, "svid", NULL, 0); 15996a6d031SHeikki Krogerus if (nval <= 0) 16096a6d031SHeikki Krogerus return NULL; 16196a6d031SHeikki Krogerus 16296a6d031SHeikki Krogerus val = kcalloc(nval, sizeof(*val), GFP_KERNEL); 16396a6d031SHeikki Krogerus if (!val) 16496a6d031SHeikki Krogerus return ERR_PTR(-ENOMEM); 16596a6d031SHeikki Krogerus 16696a6d031SHeikki Krogerus nval = fwnode_property_read_u16_array(con->fwnode, "svid", val, nval); 16796a6d031SHeikki Krogerus if (nval < 0) { 16896a6d031SHeikki Krogerus kfree(val); 16996a6d031SHeikki Krogerus return ERR_PTR(nval); 17096a6d031SHeikki Krogerus } 17196a6d031SHeikki Krogerus 17296a6d031SHeikki Krogerus for (i = 0; i < nval; i++) { 17396a6d031SHeikki Krogerus match = val[i] == desc->svid; 17496a6d031SHeikki Krogerus if (match) { 17596a6d031SHeikki Krogerus kfree(val); 17696a6d031SHeikki Krogerus goto find_mux; 17796a6d031SHeikki Krogerus } 17896a6d031SHeikki Krogerus } 17996a6d031SHeikki Krogerus kfree(val); 18096a6d031SHeikki Krogerus return NULL; 18196a6d031SHeikki Krogerus 18296a6d031SHeikki Krogerus find_mux: 18396a6d031SHeikki Krogerus list_for_each_entry(mux, &mux_list, entry) 18496a6d031SHeikki Krogerus if (dev_fwnode(mux->dev) == con->fwnode) 18596a6d031SHeikki Krogerus return mux; 18696a6d031SHeikki Krogerus 18796a6d031SHeikki Krogerus return match ? ERR_PTR(-EPROBE_DEFER) : NULL; 188bdecb33aSHeikki Krogerus } 189bdecb33aSHeikki Krogerus 190bdecb33aSHeikki Krogerus /** 191bdecb33aSHeikki Krogerus * typec_mux_get - Find USB Type-C Multiplexer 192bdecb33aSHeikki Krogerus * @dev: The caller device 193540bfab7SHeikki Krogerus * @desc: Alt Mode description 194bdecb33aSHeikki Krogerus * 195bdecb33aSHeikki Krogerus * Finds a mux linked to the caller. This function is primarily meant for the 196bdecb33aSHeikki Krogerus * Type-C drivers. Returns a reference to the mux on success, NULL if no 197bdecb33aSHeikki Krogerus * matching connection was found, or ERR_PTR(-EPROBE_DEFER) when a connection 198bdecb33aSHeikki Krogerus * was found but the mux has not been enumerated yet. 199bdecb33aSHeikki Krogerus */ 200540bfab7SHeikki Krogerus struct typec_mux *typec_mux_get(struct device *dev, 201540bfab7SHeikki Krogerus const struct typec_altmode_desc *desc) 202bdecb33aSHeikki Krogerus { 203bdecb33aSHeikki Krogerus struct typec_mux *mux; 204bdecb33aSHeikki Krogerus 205bdecb33aSHeikki Krogerus mutex_lock(&mux_lock); 206540bfab7SHeikki Krogerus mux = device_connection_find_match(dev, "mode-switch", (void *)desc, 207540bfab7SHeikki Krogerus typec_mux_match); 2083e3b8196SHeikki Krogerus if (!IS_ERR_OR_NULL(mux)) { 2093e3b8196SHeikki Krogerus WARN_ON(!try_module_get(mux->dev->driver->owner)); 210bdecb33aSHeikki Krogerus get_device(mux->dev); 2113e3b8196SHeikki Krogerus } 212bdecb33aSHeikki Krogerus mutex_unlock(&mux_lock); 213bdecb33aSHeikki Krogerus 214bdecb33aSHeikki Krogerus return mux; 215bdecb33aSHeikki Krogerus } 216bdecb33aSHeikki Krogerus EXPORT_SYMBOL_GPL(typec_mux_get); 217bdecb33aSHeikki Krogerus 218bdecb33aSHeikki Krogerus /** 219bdecb33aSHeikki Krogerus * typec_mux_put - Release handle to a Multiplexer 220bdecb33aSHeikki Krogerus * @mux: USB Type-C Connector Multiplexer/DeMultiplexer 221bdecb33aSHeikki Krogerus * 222bdecb33aSHeikki Krogerus * Decrements reference count for @mux. 223bdecb33aSHeikki Krogerus */ 224bdecb33aSHeikki Krogerus void typec_mux_put(struct typec_mux *mux) 225bdecb33aSHeikki Krogerus { 2263e3b8196SHeikki Krogerus if (!IS_ERR_OR_NULL(mux)) { 2273e3b8196SHeikki Krogerus module_put(mux->dev->driver->owner); 228bdecb33aSHeikki Krogerus put_device(mux->dev); 229bdecb33aSHeikki Krogerus } 2303e3b8196SHeikki Krogerus } 231bdecb33aSHeikki Krogerus EXPORT_SYMBOL_GPL(typec_mux_put); 232bdecb33aSHeikki Krogerus 233bdecb33aSHeikki Krogerus /** 234bdecb33aSHeikki Krogerus * typec_mux_register - Register Multiplexer routing USB Type-C pins 235bdecb33aSHeikki Krogerus * @mux: USB Type-C Connector Multiplexer/DeMultiplexer 236bdecb33aSHeikki Krogerus * 237bdecb33aSHeikki Krogerus * USB Type-C connectors can be used for alternate modes of operation besides 238bdecb33aSHeikki Krogerus * USB when Accessory/Alternate Modes are supported. With some of those modes, 239bdecb33aSHeikki Krogerus * the pins on the connector need to be reconfigured. This function registers 240bdecb33aSHeikki Krogerus * multiplexer switches routing the pins on the connector. 241bdecb33aSHeikki Krogerus */ 242bdecb33aSHeikki Krogerus int typec_mux_register(struct typec_mux *mux) 243bdecb33aSHeikki Krogerus { 244bdecb33aSHeikki Krogerus mutex_lock(&mux_lock); 245bdecb33aSHeikki Krogerus list_add_tail(&mux->entry, &mux_list); 246bdecb33aSHeikki Krogerus mutex_unlock(&mux_lock); 247bdecb33aSHeikki Krogerus 248bdecb33aSHeikki Krogerus return 0; 249bdecb33aSHeikki Krogerus } 250bdecb33aSHeikki Krogerus EXPORT_SYMBOL_GPL(typec_mux_register); 251bdecb33aSHeikki Krogerus 252bdecb33aSHeikki Krogerus /** 253bdecb33aSHeikki Krogerus * typec_mux_unregister - Unregister Multiplexer Switch 25474789aedSHeikki Krogerus * @mux: USB Type-C Connector Multiplexer/DeMultiplexer 255bdecb33aSHeikki Krogerus * 256bdecb33aSHeikki Krogerus * Unregister mux that was registered with typec_mux_register(). 257bdecb33aSHeikki Krogerus */ 258bdecb33aSHeikki Krogerus void typec_mux_unregister(struct typec_mux *mux) 259bdecb33aSHeikki Krogerus { 260bdecb33aSHeikki Krogerus mutex_lock(&mux_lock); 261bdecb33aSHeikki Krogerus list_del(&mux->entry); 262bdecb33aSHeikki Krogerus mutex_unlock(&mux_lock); 263bdecb33aSHeikki Krogerus } 264bdecb33aSHeikki Krogerus EXPORT_SYMBOL_GPL(typec_mux_unregister); 265