Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

usb: typec: Bus type for alternate modes

Introducing a simple bus for the alternate modes. Bus allows
binding drivers to the discovered alternate modes the
partners support.

Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Tested-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Heikki Krogerus and committed by
Greg Kroah-Hartman
8a37d87d 4ab8c18d

+1174 -147
+48
Documentation/ABI/obsolete/sysfs-class-typec
··· 1 + These files are deprecated and will be removed. The same files are available 2 + under /sys/bus/typec (see Documentation/ABI/testing/sysfs-bus-typec). 3 + 4 + What: /sys/class/typec/<port|partner|cable>/<dev>/svid 5 + Date: April 2017 6 + Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> 7 + Description: 8 + The SVID (Standard or Vendor ID) assigned by USB-IF for this 9 + alternate mode. 10 + 11 + What: /sys/class/typec/<port|partner|cable>/<dev>/mode<index>/ 12 + Date: April 2017 13 + Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> 14 + Description: 15 + Every supported mode will have its own directory. The name of 16 + a mode will be "mode<index>" (for example mode1), where <index> 17 + is the actual index to the mode VDO returned by Discover Modes 18 + USB power delivery command. 19 + 20 + What: /sys/class/typec/<port|partner|cable>/<dev>/mode<index>/description 21 + Date: April 2017 22 + Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> 23 + Description: 24 + Shows description of the mode. The description is optional for 25 + the drivers, just like with the Billboard Devices. 26 + 27 + What: /sys/class/typec/<port|partner|cable>/<dev>/mode<index>/vdo 28 + Date: April 2017 29 + Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> 30 + Description: 31 + Shows the VDO in hexadecimal returned by Discover Modes command 32 + for this mode. 33 + 34 + What: /sys/class/typec/<port|partner|cable>/<dev>/mode<index>/active 35 + Date: April 2017 36 + Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> 37 + Description: 38 + Shows if the mode is active or not. The attribute can be used 39 + for entering/exiting the mode with partners and cable plugs, and 40 + with the port alternate modes it can be used for disabling 41 + support for specific alternate modes. Entering/exiting modes is 42 + supported as synchronous operation so write(2) to the attribute 43 + does not return until the enter/exit mode operation has 44 + finished. The attribute is notified when the mode is 45 + entered/exited so poll(2) on the attribute wakes up. 46 + Entering/exiting a mode will also generate uevent KOBJ_CHANGE. 47 + 48 + Valid values: yes, no
+51
Documentation/ABI/testing/sysfs-bus-typec
··· 1 + What: /sys/bus/typec/devices/.../active 2 + Date: July 2018 3 + Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> 4 + Description: 5 + Shows if the mode is active or not. The attribute can be used 6 + for entering/exiting the mode. Entering/exiting modes is 7 + supported as synchronous operation so write(2) to the attribute 8 + does not return until the enter/exit mode operation has 9 + finished. The attribute is notified when the mode is 10 + entered/exited so poll(2) on the attribute wakes up. 11 + Entering/exiting a mode will also generate uevent KOBJ_CHANGE. 12 + 13 + Valid values are boolean. 14 + 15 + What: /sys/bus/typec/devices/.../description 16 + Date: July 2018 17 + Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> 18 + Description: 19 + Shows description of the mode. The description is optional for 20 + the drivers, just like with the Billboard Devices. 21 + 22 + What: /sys/bus/typec/devices/.../mode 23 + Date: July 2018 24 + Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> 25 + Description: 26 + The index number of the mode returned by Discover Modes USB 27 + Power Delivery command. Depending on the alternate mode, the 28 + mode index may be significant. 29 + 30 + With some alternate modes (SVIDs), the mode index is assigned 31 + for specific functionality in the specification for that 32 + alternate mode. 33 + 34 + With other alternate modes, the mode index values are not 35 + assigned, and can not be therefore used for identification. When 36 + the mode index is not assigned, identifying the alternate mode 37 + must be done with either mode VDO or the description. 38 + 39 + What: /sys/bus/typec/devices/.../svid 40 + Date: July 2018 41 + Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> 42 + Description: 43 + The Standard or Vendor ID (SVID) assigned by USB-IF for this 44 + alternate mode. 45 + 46 + What: /sys/bus/typec/devices/.../vdo 47 + Date: July 2018 48 + Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> 49 + Description: 50 + Shows the VDO in hexadecimal returned by Discover Modes command 51 + for this mode.
+2 -60
Documentation/ABI/testing/sysfs-class-typec
··· 222 222 available. The value can be polled. 223 223 224 224 225 - Alternate Mode devices. 225 + USB Type-C port alternate mode devices. 226 226 227 - The alternate modes will have Standard or Vendor ID (SVID) assigned by USB-IF. 228 - The ports, partners and cable plugs can have alternate modes. A supported SVID 229 - will consist of a set of modes. Every SVID a port/partner/plug supports will 230 - have a device created for it, and every supported mode for a supported SVID will 231 - have its own directory under that device. Below <dev> refers to the device for 232 - the alternate mode. 233 - 234 - What: /sys/class/typec/<port|partner|cable>/<dev>/svid 235 - Date: April 2017 236 - Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> 237 - Description: 238 - The SVID (Standard or Vendor ID) assigned by USB-IF for this 239 - alternate mode. 240 - 241 - What: /sys/class/typec/<port|partner|cable>/<dev>/mode<index>/ 242 - Date: April 2017 243 - Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> 244 - Description: 245 - Every supported mode will have its own directory. The name of 246 - a mode will be "mode<index>" (for example mode1), where <index> 247 - is the actual index to the mode VDO returned by Discover Modes 248 - USB power delivery command. 249 - 250 - What: /sys/class/typec/<port|partner|cable>/<dev>/mode<index>/description 251 - Date: April 2017 252 - Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> 253 - Description: 254 - Shows description of the mode. The description is optional for 255 - the drivers, just like with the Billboard Devices. 256 - 257 - What: /sys/class/typec/<port|partner|cable>/<dev>/mode<index>/vdo 258 - Date: April 2017 259 - Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> 260 - Description: 261 - Shows the VDO in hexadecimal returned by Discover Modes command 262 - for this mode. 263 - 264 - What: /sys/class/typec/<port|partner|cable>/<dev>/mode<index>/active 265 - Date: April 2017 266 - Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> 267 - Description: 268 - Shows if the mode is active or not. The attribute can be used 269 - for entering/exiting the mode with partners and cable plugs, and 270 - with the port alternate modes it can be used for disabling 271 - support for specific alternate modes. Entering/exiting modes is 272 - supported as synchronous operation so write(2) to the attribute 273 - does not return until the enter/exit mode operation has 274 - finished. The attribute is notified when the mode is 275 - entered/exited so poll(2) on the attribute wakes up. 276 - Entering/exiting a mode will also generate uevent KOBJ_CHANGE. 277 - 278 - Valid values: yes, no 279 - 280 - What: /sys/class/typec/<port>/<dev>/mode<index>/supported_roles 227 + What: /sys/class/typec/<port>/<alt mode>/supported_roles 281 228 Date: April 2017 282 229 Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com> 283 230 Description: 284 231 Space separated list of the supported roles. 285 - 286 - This attribute is available for the devices describing the 287 - alternate modes a port supports, and it will not be exposed with 288 - the devices presenting the alternate modes the partners or cable 289 - plugs support. 290 232 291 233 Valid values: source, sink
+136
Documentation/driver-api/usb/typec_bus.rst
··· 1 + 2 + API for USB Type-C Alternate Mode drivers 3 + ========================================= 4 + 5 + Introduction 6 + ------------ 7 + 8 + Alternate modes require communication with the partner using Vendor Defined 9 + Messages (VDM) as defined in USB Type-C and USB Power Delivery Specifications. 10 + The communication is SVID (Standard or Vendor ID) specific, i.e. specific for 11 + every alternate mode, so every alternate mode will need a custom driver. 12 + 13 + USB Type-C bus allows binding a driver to the discovered partner alternate 14 + modes by using the SVID and the mode number. 15 + 16 + USB Type-C Connector Class provides a device for every alternate mode a port 17 + supports, and separate device for every alternate mode the partner supports. 18 + The drivers for the alternate modes are bound to the partner alternate mode 19 + devices, and the port alternate mode devices must be handled by the port 20 + drivers. 21 + 22 + When a new partner alternate mode device is registered, it is linked to the 23 + alternate mode device of the port that the partner is attached to, that has 24 + matching SVID and mode. Communication between the port driver and alternate mode 25 + driver will happen using the same API. 26 + 27 + The port alternate mode devices are used as a proxy between the partner and the 28 + alternate mode drivers, so the port drivers are only expected to pass the SVID 29 + specific commands from the alternate mode drivers to the partner, and from the 30 + partners to the alternate mode drivers. No direct SVID specific communication is 31 + needed from the port drivers, but the port drivers need to provide the operation 32 + callbacks for the port alternate mode devices, just like the alternate mode 33 + drivers need to provide them for the partner alternate mode devices. 34 + 35 + Usage: 36 + ------ 37 + 38 + General 39 + ~~~~~~~ 40 + 41 + By default, the alternate mode drivers are responsible for entering the mode. 42 + It is also possible to leave the decision about entering the mode to the user 43 + space (See Documentation/ABI/testing/sysfs-class-typec). Port drivers should not 44 + enter any modes on their own. 45 + 46 + ``->vdm`` is the most important callback in the operation callbacks vector. It 47 + will be used to deliver all the SVID specific commands from the partner to the 48 + alternate mode driver, and vice versa in case of port drivers. The drivers send 49 + the SVID specific commands to each other using :c:func:`typec_altmode_vmd()`. 50 + 51 + If the communication with the partner using the SVID specific commands results 52 + in need to reconfigure the pins on the connector, the alternate mode driver 53 + needs to notify the bus using :c:func:`typec_altmode_notify()`. The driver 54 + passes the negotiated SVID specific pin configuration value to the function as 55 + parameter. The bus driver will then configure the mux behind the connector using 56 + that value as the state value for the mux, and also call blocking notification 57 + chain to notify the external drivers about the state of the connector that need 58 + to know it. 59 + 60 + NOTE: The SVID specific pin configuration values must always start from 61 + ``TYPEC_STATE_MODAL``. USB Type-C specification defines two default states for 62 + the connector: ``TYPEC_STATE_USB`` and ``TYPEC_STATE_SAFE``. These values are 63 + reserved by the bus as the first possible values for the state. When the 64 + alternate mode is entered, the bus will put the connector into 65 + ``TYPEC_STATE_SAFE`` before sending Enter or Exit Mode command as defined in USB 66 + Type-C Specification, and also put the connector back to ``TYPEC_STATE_USB`` 67 + after the mode has been exited. 68 + 69 + An example of working definitions for SVID specific pin configurations would 70 + look like this: 71 + 72 + enum { 73 + ALTMODEX_CONF_A = TYPEC_STATE_MODAL, 74 + ALTMODEX_CONF_B, 75 + ... 76 + }; 77 + 78 + Helper macro ``TYPEC_MODAL_STATE()`` can also be used: 79 + 80 + #define ALTMODEX_CONF_A = TYPEC_MODAL_STATE(0); 81 + #define ALTMODEX_CONF_B = TYPEC_MODAL_STATE(1); 82 + 83 + Notification chain 84 + ~~~~~~~~~~~~~~~~~~ 85 + 86 + The drivers for the components that the alternate modes are designed for need to 87 + get details regarding the results of the negotiation with the partner, and the 88 + pin configuration of the connector. In case of DisplayPort alternate mode for 89 + example, the GPU drivers will need to know those details. In case of 90 + Thunderbolt alternate mode, the thunderbolt drivers will need to know them, and 91 + so on. 92 + 93 + The notification chain is designed for this purpose. The drivers can register 94 + notifiers with :c:func:`typec_altmode_register_notifier()`. 95 + 96 + Cable plug alternate modes 97 + ~~~~~~~~~~~~~~~~~~~~~~~~~~ 98 + 99 + The alternate mode drivers are not bound to cable plug alternate mode devices, 100 + only to the partner alternate mode devices. If the alternate mode supports, or 101 + requires, a cable that responds to SOP Prime, and optionally SOP Double Prime 102 + messages, the driver for that alternate mode must request handle to the cable 103 + plug alternate modes using :c:func:`typec_altmode_get_plug()`, and take over 104 + their control. 105 + 106 + Driver API 107 + ---------- 108 + 109 + Alternate mode driver registering/unregistering 110 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 111 + 112 + .. kernel-doc:: drivers/usb/typec/bus.c 113 + :functions: typec_altmode_register_driver typec_altmode_unregister_driver 114 + 115 + Alternate mode driver operations 116 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 117 + 118 + .. kernel-doc:: drivers/usb/typec/bus.c 119 + :functions: typec_altmode_enter typec_altmode_exit typec_altmode_attention typec_altmode_vdm typec_altmode_notify 120 + 121 + API for the port drivers 122 + ~~~~~~~~~~~~~~~~~~~~~~~~ 123 + 124 + .. kernel-doc:: drivers/usb/typec/bus.c 125 + :functions: typec_match_altmode 126 + 127 + Cable Plug operations 128 + ~~~~~~~~~~~~~~~~~~~~~ 129 + 130 + .. kernel-doc:: drivers/usb/typec/bus.c 131 + :functions: typec_altmode_get_plug typec_altmode_put_plug 132 + 133 + Notifications 134 + ~~~~~~~~~~~~~ 135 + .. kernel-doc:: drivers/usb/typec/class.c 136 + :functions: typec_altmode_register_notifier typec_altmode_unregister_notifier
+10 -1
MAINTAINERS
··· 14955 14955 S: Maintained 14956 14956 F: drivers/usb/typec/mux/pi3usb30532.c 14957 14957 14958 - USB TYPEC SUBSYSTEM 14958 + USB TYPEC CLASS 14959 14959 M: Heikki Krogerus <heikki.krogerus@linux.intel.com> 14960 14960 L: linux-usb@vger.kernel.org 14961 14961 S: Maintained ··· 14963 14963 F: Documentation/driver-api/usb/typec.rst 14964 14964 F: drivers/usb/typec/ 14965 14965 F: include/linux/usb/typec.h 14966 + 14967 + USB TYPEC BUS FOR ALTERNATE MODES 14968 + M: Heikki Krogerus <heikki.krogerus@linux.intel.com> 14969 + L: linux-usb@vger.kernel.org 14970 + S: Maintained 14971 + F: Documentation/ABI/testing/sysfs-bus-typec 14972 + F: Documentation/driver-api/usb/typec_bus.rst 14973 + F: drivers/usb/typec/altmodes/ 14974 + F: include/linux/usb/typec_altmode.h 14966 14975 14967 14976 USB UHCI DRIVER 14968 14977 M: Alan Stern <stern@rowland.harvard.edu>
+1 -1
drivers/usb/typec/Makefile
··· 1 1 # SPDX-License-Identifier: GPL-2.0 2 2 obj-$(CONFIG_TYPEC) += typec.o 3 - typec-y := class.o mux.o 3 + typec-y := class.o mux.o bus.o 4 4 obj-$(CONFIG_TYPEC_TCPM) += tcpm.o 5 5 obj-y += fusb302/ 6 6 obj-$(CONFIG_TYPEC_WCOVE) += typec_wcove.o
+401
drivers/usb/typec/bus.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /** 3 + * Bus for USB Type-C Alternate Modes 4 + * 5 + * Copyright (C) 2018 Intel Corporation 6 + * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com> 7 + */ 8 + 9 + #include <linux/usb/pd_vdo.h> 10 + 11 + #include "bus.h" 12 + 13 + static inline int typec_altmode_set_mux(struct altmode *alt, u8 state) 14 + { 15 + return alt->mux ? alt->mux->set(alt->mux, state) : 0; 16 + } 17 + 18 + static int typec_altmode_set_state(struct typec_altmode *adev, int state) 19 + { 20 + bool is_port = is_typec_port(adev->dev.parent); 21 + struct altmode *port_altmode; 22 + int ret; 23 + 24 + port_altmode = is_port ? to_altmode(adev) : to_altmode(adev)->partner; 25 + 26 + ret = typec_altmode_set_mux(port_altmode, state); 27 + if (ret) 28 + return ret; 29 + 30 + blocking_notifier_call_chain(&port_altmode->nh, state, NULL); 31 + 32 + return 0; 33 + } 34 + 35 + /* -------------------------------------------------------------------------- */ 36 + /* Common API */ 37 + 38 + /** 39 + * typec_altmode_notify - Communication between the OS and alternate mode driver 40 + * @adev: Handle to the alternate mode 41 + * @conf: Alternate mode specific configuration value 42 + * @data: Alternate mode specific data 43 + * 44 + * The primary purpose for this function is to allow the alternate mode drivers 45 + * to tell which pin configuration has been negotiated with the partner. That 46 + * information will then be used for example to configure the muxes. 47 + * Communication to the other direction is also possible, and low level device 48 + * drivers can also send notifications to the alternate mode drivers. The actual 49 + * communication will be specific for every SVID. 50 + */ 51 + int typec_altmode_notify(struct typec_altmode *adev, 52 + unsigned long conf, void *data) 53 + { 54 + bool is_port = is_typec_port(adev->dev.parent); 55 + struct altmode *altmode; 56 + struct altmode *partner; 57 + int ret; 58 + 59 + if (!adev) 60 + return 0; 61 + 62 + altmode = to_altmode(adev); 63 + 64 + if (!altmode->partner) 65 + return -ENODEV; 66 + 67 + partner = altmode->partner; 68 + 69 + ret = typec_altmode_set_mux(is_port ? altmode : partner, (u8)conf); 70 + if (ret) 71 + return ret; 72 + 73 + blocking_notifier_call_chain(is_port ? &altmode->nh : &partner->nh, 74 + conf, data); 75 + 76 + if (partner->adev.ops && partner->adev.ops->notify) 77 + return partner->adev.ops->notify(&partner->adev, conf, data); 78 + 79 + return 0; 80 + } 81 + EXPORT_SYMBOL_GPL(typec_altmode_notify); 82 + 83 + /** 84 + * typec_altmode_enter - Enter Mode 85 + * @adev: The alternate mode 86 + * 87 + * The alternate mode drivers use this function to enter mode. The port drivers 88 + * use this to inform the alternate mode drivers that the partner has initiated 89 + * Enter Mode command. 90 + */ 91 + int typec_altmode_enter(struct typec_altmode *adev) 92 + { 93 + struct altmode *partner = to_altmode(adev)->partner; 94 + struct typec_altmode *pdev = &partner->adev; 95 + int ret; 96 + 97 + if (!adev || adev->active) 98 + return 0; 99 + 100 + if (!pdev->ops || !pdev->ops->enter) 101 + return -EOPNOTSUPP; 102 + 103 + /* Moving to USB Safe State */ 104 + ret = typec_altmode_set_state(adev, TYPEC_STATE_SAFE); 105 + if (ret) 106 + return ret; 107 + 108 + /* Enter Mode */ 109 + return pdev->ops->enter(pdev); 110 + } 111 + EXPORT_SYMBOL_GPL(typec_altmode_enter); 112 + 113 + /** 114 + * typec_altmode_exit - Exit Mode 115 + * @adev: The alternate mode 116 + * 117 + * The partner of @adev has initiated Exit Mode command. 118 + */ 119 + int typec_altmode_exit(struct typec_altmode *adev) 120 + { 121 + struct altmode *partner = to_altmode(adev)->partner; 122 + struct typec_altmode *pdev = &partner->adev; 123 + int ret; 124 + 125 + if (!adev || !adev->active) 126 + return 0; 127 + 128 + if (!pdev->ops || !pdev->ops->enter) 129 + return -EOPNOTSUPP; 130 + 131 + /* Moving to USB Safe State */ 132 + ret = typec_altmode_set_state(adev, TYPEC_STATE_SAFE); 133 + if (ret) 134 + return ret; 135 + 136 + /* Exit Mode command */ 137 + return pdev->ops->exit(pdev); 138 + } 139 + EXPORT_SYMBOL_GPL(typec_altmode_exit); 140 + 141 + /** 142 + * typec_altmode_attention - Attention command 143 + * @adev: The alternate mode 144 + * @vdo: VDO for the Attention command 145 + * 146 + * Notifies the partner of @adev about Attention command. 147 + */ 148 + void typec_altmode_attention(struct typec_altmode *adev, u32 vdo) 149 + { 150 + struct typec_altmode *pdev = &to_altmode(adev)->partner->adev; 151 + 152 + if (pdev->ops && pdev->ops->attention) 153 + pdev->ops->attention(pdev, vdo); 154 + } 155 + EXPORT_SYMBOL_GPL(typec_altmode_attention); 156 + 157 + /** 158 + * typec_altmode_vdm - Send Vendor Defined Messages (VDM) to the partner 159 + * @adev: Alternate mode handle 160 + * @header: VDM Header 161 + * @vdo: Array of Vendor Defined Data Objects 162 + * @count: Number of Data Objects 163 + * 164 + * The alternate mode drivers use this function for SVID specific communication 165 + * with the partner. The port drivers use it to deliver the Structured VDMs 166 + * received from the partners to the alternate mode drivers. 167 + */ 168 + int typec_altmode_vdm(struct typec_altmode *adev, 169 + const u32 header, const u32 *vdo, int count) 170 + { 171 + struct typec_altmode *pdev; 172 + struct altmode *altmode; 173 + 174 + if (!adev) 175 + return 0; 176 + 177 + altmode = to_altmode(adev); 178 + 179 + if (!altmode->partner) 180 + return -ENODEV; 181 + 182 + pdev = &altmode->partner->adev; 183 + 184 + if (!pdev->ops || !pdev->ops->vdm) 185 + return -EOPNOTSUPP; 186 + 187 + return pdev->ops->vdm(pdev, header, vdo, count); 188 + } 189 + EXPORT_SYMBOL_GPL(typec_altmode_vdm); 190 + 191 + const struct typec_altmode * 192 + typec_altmode_get_partner(struct typec_altmode *adev) 193 + { 194 + return &to_altmode(adev)->partner->adev; 195 + } 196 + EXPORT_SYMBOL_GPL(typec_altmode_get_partner); 197 + 198 + /* -------------------------------------------------------------------------- */ 199 + /* API for the alternate mode drivers */ 200 + 201 + /** 202 + * typec_altmode_get_plug - Find cable plug alternate mode 203 + * @adev: Handle to partner alternate mode 204 + * @index: Cable plug index 205 + * 206 + * Increment reference count for cable plug alternate mode device. Returns 207 + * handle to the cable plug alternate mode, or NULL if none is found. 208 + */ 209 + struct typec_altmode *typec_altmode_get_plug(struct typec_altmode *adev, 210 + enum typec_plug_index index) 211 + { 212 + struct altmode *port = to_altmode(adev)->partner; 213 + 214 + if (port->plug[index]) { 215 + get_device(&port->plug[index]->adev.dev); 216 + return &port->plug[index]->adev; 217 + } 218 + 219 + return NULL; 220 + } 221 + EXPORT_SYMBOL_GPL(typec_altmode_get_plug); 222 + 223 + /** 224 + * typec_altmode_put_plug - Decrement cable plug alternate mode reference count 225 + * @plug: Handle to the cable plug alternate mode 226 + */ 227 + void typec_altmode_put_plug(struct typec_altmode *plug) 228 + { 229 + if (plug) 230 + put_device(&plug->dev); 231 + } 232 + EXPORT_SYMBOL_GPL(typec_altmode_put_plug); 233 + 234 + int __typec_altmode_register_driver(struct typec_altmode_driver *drv, 235 + struct module *module) 236 + { 237 + if (!drv->probe) 238 + return -EINVAL; 239 + 240 + drv->driver.owner = module; 241 + drv->driver.bus = &typec_bus; 242 + 243 + return driver_register(&drv->driver); 244 + } 245 + EXPORT_SYMBOL_GPL(__typec_altmode_register_driver); 246 + 247 + void typec_altmode_unregister_driver(struct typec_altmode_driver *drv) 248 + { 249 + driver_unregister(&drv->driver); 250 + } 251 + EXPORT_SYMBOL_GPL(typec_altmode_unregister_driver); 252 + 253 + /* -------------------------------------------------------------------------- */ 254 + /* API for the port drivers */ 255 + 256 + /** 257 + * typec_match_altmode - Match SVID to an array of alternate modes 258 + * @altmodes: Array of alternate modes 259 + * @n: Number of elements in the array, or -1 for NULL termiated arrays 260 + * @svid: Standard or Vendor ID to match with 261 + * 262 + * Return pointer to an alternate mode with SVID mathing @svid, or NULL when no 263 + * match is found. 264 + */ 265 + struct typec_altmode *typec_match_altmode(struct typec_altmode **altmodes, 266 + size_t n, u16 svid, u8 mode) 267 + { 268 + int i; 269 + 270 + for (i = 0; i < n; i++) { 271 + if (!altmodes[i]) 272 + break; 273 + if (altmodes[i]->svid == svid && altmodes[i]->mode == mode) 274 + return altmodes[i]; 275 + } 276 + 277 + return NULL; 278 + } 279 + EXPORT_SYMBOL_GPL(typec_match_altmode); 280 + 281 + /* -------------------------------------------------------------------------- */ 282 + 283 + static ssize_t 284 + description_show(struct device *dev, struct device_attribute *attr, char *buf) 285 + { 286 + struct typec_altmode *alt = to_typec_altmode(dev); 287 + 288 + return sprintf(buf, "%s\n", alt->desc ? alt->desc : ""); 289 + } 290 + static DEVICE_ATTR_RO(description); 291 + 292 + static struct attribute *typec_attrs[] = { 293 + &dev_attr_description.attr, 294 + NULL 295 + }; 296 + ATTRIBUTE_GROUPS(typec); 297 + 298 + static int typec_match(struct device *dev, struct device_driver *driver) 299 + { 300 + struct typec_altmode_driver *drv = to_altmode_driver(driver); 301 + struct typec_altmode *altmode = to_typec_altmode(dev); 302 + const struct typec_device_id *id; 303 + 304 + for (id = drv->id_table; id->svid; id++) 305 + if (id->svid == altmode->svid && 306 + (id->mode == TYPEC_ANY_MODE || id->mode == altmode->mode)) 307 + return 1; 308 + return 0; 309 + } 310 + 311 + static int typec_uevent(struct device *dev, struct kobj_uevent_env *env) 312 + { 313 + struct typec_altmode *altmode = to_typec_altmode(dev); 314 + 315 + if (add_uevent_var(env, "SVID=%04X", altmode->svid)) 316 + return -ENOMEM; 317 + 318 + if (add_uevent_var(env, "MODE=%u", altmode->mode)) 319 + return -ENOMEM; 320 + 321 + return add_uevent_var(env, "MODALIAS=typec:id%04Xm%02X", 322 + altmode->svid, altmode->mode); 323 + } 324 + 325 + static int typec_altmode_create_links(struct altmode *alt) 326 + { 327 + struct device *port_dev = &alt->partner->adev.dev; 328 + struct device *dev = &alt->adev.dev; 329 + int err; 330 + 331 + err = sysfs_create_link(&dev->kobj, &port_dev->kobj, "port"); 332 + if (err) 333 + return err; 334 + 335 + err = sysfs_create_link(&port_dev->kobj, &dev->kobj, "partner"); 336 + if (err) 337 + sysfs_remove_link(&dev->kobj, "port"); 338 + 339 + return err; 340 + } 341 + 342 + static void typec_altmode_remove_links(struct altmode *alt) 343 + { 344 + sysfs_remove_link(&alt->partner->adev.dev.kobj, "partner"); 345 + sysfs_remove_link(&alt->adev.dev.kobj, "port"); 346 + } 347 + 348 + static int typec_probe(struct device *dev) 349 + { 350 + struct typec_altmode_driver *drv = to_altmode_driver(dev->driver); 351 + struct typec_altmode *adev = to_typec_altmode(dev); 352 + struct altmode *altmode = to_altmode(adev); 353 + int ret; 354 + 355 + /* Fail if the port does not support the alternate mode */ 356 + if (!altmode->partner) 357 + return -ENODEV; 358 + 359 + ret = typec_altmode_create_links(altmode); 360 + if (ret) { 361 + dev_warn(dev, "failed to create symlinks\n"); 362 + return ret; 363 + } 364 + 365 + ret = drv->probe(adev); 366 + if (ret) 367 + typec_altmode_remove_links(altmode); 368 + 369 + return ret; 370 + } 371 + 372 + static int typec_remove(struct device *dev) 373 + { 374 + struct typec_altmode_driver *drv = to_altmode_driver(dev->driver); 375 + struct typec_altmode *adev = to_typec_altmode(dev); 376 + struct altmode *altmode = to_altmode(adev); 377 + 378 + typec_altmode_remove_links(altmode); 379 + 380 + if (drv->remove) 381 + drv->remove(to_typec_altmode(dev)); 382 + 383 + if (adev->active) { 384 + WARN_ON(typec_altmode_set_state(adev, TYPEC_STATE_SAFE)); 385 + typec_altmode_update_active(adev, false); 386 + } 387 + 388 + adev->desc = NULL; 389 + adev->ops = NULL; 390 + 391 + return 0; 392 + } 393 + 394 + struct bus_type typec_bus = { 395 + .name = "typec", 396 + .dev_groups = typec_groups, 397 + .match = typec_match, 398 + .uevent = typec_uevent, 399 + .probe = typec_probe, 400 + .remove = typec_remove, 401 + };
+38
drivers/usb/typec/bus.h
··· 1 + /* SPDX-License-Identifier: GPL-2.0 */ 2 + 3 + #ifndef __USB_TYPEC_ALTMODE_H__ 4 + #define __USB_TYPEC_ALTMODE_H__ 5 + 6 + #include <linux/usb/typec_altmode.h> 7 + #include <linux/usb/typec_mux.h> 8 + 9 + struct bus_type; 10 + 11 + struct altmode { 12 + unsigned int id; 13 + struct typec_altmode adev; 14 + struct typec_mux *mux; 15 + 16 + enum typec_port_data roles; 17 + 18 + struct attribute *attrs[5]; 19 + char group_name[6]; 20 + struct attribute_group group; 21 + const struct attribute_group *groups[2]; 22 + 23 + struct altmode *partner; 24 + struct altmode *plug[2]; 25 + 26 + struct blocking_notifier_head nh; 27 + }; 28 + 29 + #define to_altmode(d) container_of(d, struct altmode, adev) 30 + 31 + extern struct bus_type typec_bus; 32 + extern const struct device_type typec_altmode_dev_type; 33 + extern const struct device_type typec_port_dev_type; 34 + 35 + #define is_typec_altmode(_dev_) (_dev_->type == &typec_altmode_dev_type) 36 + #define is_typec_port(_dev_) (_dev_->type == &typec_port_dev_type) 37 + 38 + #endif /* __USB_TYPEC_ALTMODE_H__ */
+292 -74
drivers/usb/typec/class.c
··· 10 10 #include <linux/module.h> 11 11 #include <linux/mutex.h> 12 12 #include <linux/slab.h> 13 - #include <linux/usb/typec.h> 14 - #include <linux/usb/typec_mux.h> 15 13 16 - struct typec_altmode { 17 - struct device dev; 18 - u16 svid; 19 - u8 mode; 20 - 21 - u32 vdo; 22 - char *desc; 23 - enum typec_port_type roles; 24 - unsigned int active:1; 25 - 26 - struct attribute *attrs[5]; 27 - char group_name[6]; 28 - struct attribute_group group; 29 - const struct attribute_group *groups[2]; 30 - }; 14 + #include "bus.h" 31 15 32 16 struct typec_plug { 33 17 struct device dev; 34 18 enum typec_plug_index index; 19 + struct ida mode_ids; 35 20 }; 36 21 37 22 struct typec_cable { ··· 31 46 unsigned int usb_pd:1; 32 47 struct usb_pd_identity *identity; 33 48 enum typec_accessory accessory; 49 + struct ida mode_ids; 34 50 }; 35 51 36 52 struct typec_port { 37 53 unsigned int id; 38 54 struct device dev; 55 + struct ida mode_ids; 39 56 40 57 int prefer_role; 41 58 enum typec_data_role data_role; ··· 58 71 #define to_typec_plug(_dev_) container_of(_dev_, struct typec_plug, dev) 59 72 #define to_typec_cable(_dev_) container_of(_dev_, struct typec_cable, dev) 60 73 #define to_typec_partner(_dev_) container_of(_dev_, struct typec_partner, dev) 61 - #define to_altmode(_dev_) container_of(_dev_, struct typec_altmode, dev) 62 74 63 75 static const struct device_type typec_partner_dev_type; 64 76 static const struct device_type typec_cable_dev_type; 65 77 static const struct device_type typec_plug_dev_type; 66 - static const struct device_type typec_port_dev_type; 67 78 68 79 #define is_typec_partner(_dev_) (_dev_->type == &typec_partner_dev_type) 69 80 #define is_typec_cable(_dev_) (_dev_->type == &typec_cable_dev_type) 70 81 #define is_typec_plug(_dev_) (_dev_->type == &typec_plug_dev_type) 71 - #define is_typec_port(_dev_) (_dev_->type == &typec_port_dev_type) 72 82 73 83 static DEFINE_IDA(typec_index_ida); 74 84 static struct class *typec_class; ··· 147 163 /* ------------------------------------------------------------------------- */ 148 164 /* Alternate Modes */ 149 165 166 + static int altmode_match(struct device *dev, void *data) 167 + { 168 + struct typec_altmode *adev = to_typec_altmode(dev); 169 + struct typec_device_id *id = data; 170 + 171 + if (!is_typec_altmode(dev)) 172 + return 0; 173 + 174 + return ((adev->svid == id->svid) && (adev->mode == id->mode)); 175 + } 176 + 177 + static void typec_altmode_set_partner(struct altmode *altmode) 178 + { 179 + struct typec_altmode *adev = &altmode->adev; 180 + struct typec_device_id id = { adev->svid, adev->mode, }; 181 + struct typec_port *port = typec_altmode2port(adev); 182 + struct altmode *partner; 183 + struct device *dev; 184 + 185 + dev = device_find_child(&port->dev, &id, altmode_match); 186 + if (!dev) 187 + return; 188 + 189 + /* Bind the port alt mode to the partner/plug alt mode. */ 190 + partner = to_altmode(to_typec_altmode(dev)); 191 + altmode->partner = partner; 192 + 193 + /* Bind the partner/plug alt mode to the port alt mode. */ 194 + if (is_typec_plug(adev->dev.parent)) { 195 + struct typec_plug *plug = to_typec_plug(adev->dev.parent); 196 + 197 + partner->plug[plug->index] = altmode; 198 + } else { 199 + partner->partner = altmode; 200 + } 201 + } 202 + 203 + static void typec_altmode_put_partner(struct altmode *altmode) 204 + { 205 + struct altmode *partner = altmode->partner; 206 + struct typec_altmode *adev; 207 + 208 + if (!partner) 209 + return; 210 + 211 + adev = &partner->adev; 212 + 213 + if (is_typec_plug(adev->dev.parent)) { 214 + struct typec_plug *plug = to_typec_plug(adev->dev.parent); 215 + 216 + partner->plug[plug->index] = NULL; 217 + } else { 218 + partner->partner = NULL; 219 + } 220 + put_device(&adev->dev); 221 + } 222 + 223 + static int __typec_port_match(struct device *dev, const void *name) 224 + { 225 + return !strcmp((const char *)name, dev_name(dev)); 226 + } 227 + 228 + static void *typec_port_match(struct device_connection *con, int ep, void *data) 229 + { 230 + return class_find_device(typec_class, NULL, con->endpoint[ep], 231 + __typec_port_match); 232 + } 233 + 234 + struct typec_altmode * 235 + typec_altmode_register_notifier(struct device *dev, u16 svid, u8 mode, 236 + struct notifier_block *nb) 237 + { 238 + struct typec_device_id id = { svid, mode, }; 239 + struct device *altmode_dev; 240 + struct device *port_dev; 241 + struct altmode *altmode; 242 + int ret; 243 + 244 + /* Find the port linked to the caller */ 245 + port_dev = device_connection_find_match(dev, NULL, NULL, 246 + typec_port_match); 247 + if (IS_ERR_OR_NULL(port_dev)) 248 + return port_dev ? ERR_CAST(port_dev) : ERR_PTR(-ENODEV); 249 + 250 + /* Find the altmode with matching svid */ 251 + altmode_dev = device_find_child(port_dev, &id, altmode_match); 252 + 253 + put_device(port_dev); 254 + 255 + if (!altmode_dev) 256 + return ERR_PTR(-ENODEV); 257 + 258 + altmode = to_altmode(to_typec_altmode(altmode_dev)); 259 + 260 + /* Register notifier */ 261 + ret = blocking_notifier_chain_register(&altmode->nh, nb); 262 + if (ret) { 263 + put_device(altmode_dev); 264 + return ERR_PTR(ret); 265 + } 266 + 267 + return &altmode->adev; 268 + } 269 + EXPORT_SYMBOL_GPL(typec_altmode_register_notifier); 270 + 271 + void typec_altmode_unregister_notifier(struct typec_altmode *adev, 272 + struct notifier_block *nb) 273 + { 274 + struct altmode *altmode = to_altmode(adev); 275 + 276 + blocking_notifier_chain_unregister(&altmode->nh, nb); 277 + put_device(&adev->dev); 278 + } 279 + EXPORT_SYMBOL_GPL(typec_altmode_unregister_notifier); 280 + 150 281 /** 151 282 * typec_altmode_update_active - Report Enter/Exit mode 152 - * @alt: Handle to the alternate mode 283 + * @adev: Handle to the alternate mode 153 284 * @active: True when the mode has been entered 154 285 * 155 286 * If a partner or cable plug executes Enter/Exit Mode command successfully, the 156 287 * drivers use this routine to report the updated state of the mode. 157 288 */ 158 - void typec_altmode_update_active(struct typec_altmode *alt, bool active) 289 + void typec_altmode_update_active(struct typec_altmode *adev, bool active) 159 290 { 160 291 char dir[6]; 161 292 162 - if (alt->active == active) 293 + if (adev->active == active) 163 294 return; 164 295 165 - alt->active = active; 166 - snprintf(dir, sizeof(dir), "mode%d", alt->mode); 167 - sysfs_notify(&alt->dev.kobj, dir, "active"); 168 - kobject_uevent(&alt->dev.kobj, KOBJ_CHANGE); 296 + if (!is_typec_port(adev->dev.parent)) { 297 + if (!active) 298 + module_put(adev->dev.driver->owner); 299 + else 300 + WARN_ON(!try_module_get(adev->dev.driver->owner)); 301 + } 302 + 303 + adev->active = active; 304 + snprintf(dir, sizeof(dir), "mode%d", adev->mode); 305 + sysfs_notify(&adev->dev.kobj, dir, "active"); 306 + sysfs_notify(&adev->dev.kobj, NULL, "active"); 307 + kobject_uevent(&adev->dev.kobj, KOBJ_CHANGE); 169 308 } 170 309 EXPORT_SYMBOL_GPL(typec_altmode_update_active); 171 310 ··· 315 208 static ssize_t 316 209 vdo_show(struct device *dev, struct device_attribute *attr, char *buf) 317 210 { 318 - struct typec_altmode *alt = to_altmode(dev); 211 + struct typec_altmode *alt = to_typec_altmode(dev); 319 212 320 213 return sprintf(buf, "0x%08x\n", alt->vdo); 321 214 } ··· 324 217 static ssize_t 325 218 description_show(struct device *dev, struct device_attribute *attr, char *buf) 326 219 { 327 - struct typec_altmode *alt = to_altmode(dev); 220 + struct typec_altmode *alt = to_typec_altmode(dev); 328 221 329 222 return sprintf(buf, "%s\n", alt->desc ? alt->desc : ""); 330 223 } ··· 333 226 static ssize_t 334 227 active_show(struct device *dev, struct device_attribute *attr, char *buf) 335 228 { 336 - struct typec_altmode *alt = to_altmode(dev); 229 + struct typec_altmode *alt = to_typec_altmode(dev); 337 230 338 231 return sprintf(buf, "%s\n", alt->active ? "yes" : "no"); 339 232 } ··· 341 234 static ssize_t active_store(struct device *dev, struct device_attribute *attr, 342 235 const char *buf, size_t size) 343 236 { 344 - struct typec_altmode *alt = to_altmode(dev); 345 - struct typec_port *port = typec_altmode2port(alt); 346 - bool activate; 237 + struct typec_altmode *adev = to_typec_altmode(dev); 238 + struct altmode *altmode = to_altmode(adev); 239 + bool enter; 347 240 int ret; 348 241 349 - if (!port->cap->activate_mode) 350 - return -EOPNOTSUPP; 351 - 352 - ret = kstrtobool(buf, &activate); 242 + ret = kstrtobool(buf, &enter); 353 243 if (ret) 354 244 return ret; 355 245 356 - ret = port->cap->activate_mode(port->cap, alt->mode, activate); 357 - if (ret) 358 - return ret; 246 + if (adev->active == enter) 247 + return size; 248 + 249 + if (is_typec_port(adev->dev.parent)) { 250 + typec_altmode_update_active(adev, enter); 251 + 252 + /* Make sure that the partner exits the mode before disabling */ 253 + if (altmode->partner && !enter && altmode->partner->adev.active) 254 + typec_altmode_exit(&altmode->partner->adev); 255 + } else if (altmode->partner) { 256 + if (enter && !altmode->partner->adev.active) { 257 + dev_warn(dev, "port has the mode disabled\n"); 258 + return -EPERM; 259 + } 260 + } 261 + 262 + /* Note: If there is no driver, the mode will not be entered */ 263 + if (adev->ops && adev->ops->activate) { 264 + ret = adev->ops->activate(adev, enter); 265 + if (ret) 266 + return ret; 267 + } 359 268 360 269 return size; 361 270 } ··· 381 258 supported_roles_show(struct device *dev, struct device_attribute *attr, 382 259 char *buf) 383 260 { 384 - struct typec_altmode *alt = to_altmode(dev); 261 + struct altmode *alt = to_altmode(to_typec_altmode(dev)); 385 262 ssize_t ret; 386 263 387 264 switch (alt->roles) { ··· 400 277 } 401 278 static DEVICE_ATTR_RO(supported_roles); 402 279 403 - static void typec_altmode_release(struct device *dev) 280 + static ssize_t 281 + mode_show(struct device *dev, struct device_attribute *attr, char *buf) 404 282 { 405 - struct typec_altmode *alt = to_altmode(dev); 283 + struct typec_altmode *adev = to_typec_altmode(dev); 406 284 407 - kfree(alt); 285 + return sprintf(buf, "%u\n", adev->mode); 408 286 } 287 + static DEVICE_ATTR_RO(mode); 409 288 410 - static ssize_t svid_show(struct device *dev, struct device_attribute *attr, 411 - char *buf) 289 + static ssize_t 290 + svid_show(struct device *dev, struct device_attribute *attr, char *buf) 412 291 { 413 - struct typec_altmode *alt = to_altmode(dev); 292 + struct typec_altmode *adev = to_typec_altmode(dev); 414 293 415 - return sprintf(buf, "%04x\n", alt->svid); 294 + return sprintf(buf, "%04x\n", adev->svid); 416 295 } 417 296 static DEVICE_ATTR_RO(svid); 418 297 419 298 static struct attribute *typec_altmode_attrs[] = { 299 + &dev_attr_active.attr, 300 + &dev_attr_mode.attr, 420 301 &dev_attr_svid.attr, 302 + &dev_attr_vdo.attr, 421 303 NULL 422 304 }; 423 305 ATTRIBUTE_GROUPS(typec_altmode); 424 306 425 - static const struct device_type typec_altmode_dev_type = { 307 + static int altmode_id_get(struct device *dev) 308 + { 309 + struct ida *ids; 310 + 311 + if (is_typec_partner(dev)) 312 + ids = &to_typec_partner(dev)->mode_ids; 313 + else if (is_typec_plug(dev)) 314 + ids = &to_typec_plug(dev)->mode_ids; 315 + else 316 + ids = &to_typec_port(dev)->mode_ids; 317 + 318 + return ida_simple_get(ids, 0, 0, GFP_KERNEL); 319 + } 320 + 321 + static void altmode_id_remove(struct device *dev, int id) 322 + { 323 + struct ida *ids; 324 + 325 + if (is_typec_partner(dev)) 326 + ids = &to_typec_partner(dev)->mode_ids; 327 + else if (is_typec_plug(dev)) 328 + ids = &to_typec_plug(dev)->mode_ids; 329 + else 330 + ids = &to_typec_port(dev)->mode_ids; 331 + 332 + ida_simple_remove(ids, id); 333 + } 334 + 335 + static void typec_altmode_release(struct device *dev) 336 + { 337 + struct altmode *alt = to_altmode(to_typec_altmode(dev)); 338 + 339 + typec_altmode_put_partner(alt); 340 + 341 + altmode_id_remove(alt->adev.dev.parent, alt->id); 342 + kfree(alt); 343 + } 344 + 345 + const struct device_type typec_altmode_dev_type = { 426 346 .name = "typec_alternate_mode", 427 347 .groups = typec_altmode_groups, 428 348 .release = typec_altmode_release, ··· 475 309 typec_register_altmode(struct device *parent, 476 310 const struct typec_altmode_desc *desc) 477 311 { 478 - struct typec_altmode *alt; 312 + unsigned int id = altmode_id_get(parent); 313 + bool is_port = is_typec_port(parent); 314 + struct altmode *alt; 479 315 int ret; 480 316 481 317 alt = kzalloc(sizeof(*alt), GFP_KERNEL); 482 318 if (!alt) 483 319 return ERR_PTR(-ENOMEM); 484 320 485 - alt->svid = desc->svid; 486 - alt->mode = desc->mode; 487 - alt->vdo = desc->vdo; 321 + alt->adev.svid = desc->svid; 322 + alt->adev.mode = desc->mode; 323 + alt->adev.vdo = desc->vdo; 488 324 alt->roles = desc->roles; 325 + alt->id = id; 489 326 490 327 alt->attrs[0] = &dev_attr_vdo.attr; 491 328 alt->attrs[1] = &dev_attr_description.attr; 492 329 alt->attrs[2] = &dev_attr_active.attr; 493 330 494 - if (is_typec_port(parent)) 331 + if (is_port) { 495 332 alt->attrs[3] = &dev_attr_supported_roles.attr; 333 + alt->adev.active = true; /* Enabled by default */ 334 + } 496 335 497 336 sprintf(alt->group_name, "mode%d", desc->mode); 498 337 alt->group.name = alt->group_name; 499 338 alt->group.attrs = alt->attrs; 500 339 alt->groups[0] = &alt->group; 501 340 502 - alt->dev.parent = parent; 503 - alt->dev.groups = alt->groups; 504 - alt->dev.type = &typec_altmode_dev_type; 505 - dev_set_name(&alt->dev, "%s-%04x:%u", dev_name(parent), 506 - alt->svid, alt->mode); 341 + alt->adev.dev.parent = parent; 342 + alt->adev.dev.groups = alt->groups; 343 + alt->adev.dev.type = &typec_altmode_dev_type; 344 + dev_set_name(&alt->adev.dev, "%s.%u", dev_name(parent), id); 507 345 508 - ret = device_register(&alt->dev); 346 + /* Link partners and plugs with the ports */ 347 + if (is_port) 348 + BLOCKING_INIT_NOTIFIER_HEAD(&alt->nh); 349 + else 350 + typec_altmode_set_partner(alt); 351 + 352 + /* The partners are bind to drivers */ 353 + if (is_typec_partner(parent)) 354 + alt->adev.dev.bus = &typec_bus; 355 + 356 + ret = device_register(&alt->adev.dev); 509 357 if (ret) { 510 358 dev_err(parent, "failed to register alternate mode (%d)\n", 511 359 ret); 512 - put_device(&alt->dev); 360 + put_device(&alt->adev.dev); 513 361 return ERR_PTR(ret); 514 362 } 515 363 516 - return alt; 364 + return &alt->adev; 517 365 } 518 366 519 367 /** 520 368 * typec_unregister_altmode - Unregister Alternate Mode 521 - * @alt: The alternate mode to be unregistered 369 + * @adev: The alternate mode to be unregistered 522 370 * 523 371 * Unregister device created with typec_partner_register_altmode(), 524 372 * typec_plug_register_altmode() or typec_port_register_altmode(). 525 373 */ 526 - void typec_unregister_altmode(struct typec_altmode *alt) 374 + void typec_unregister_altmode(struct typec_altmode *adev) 527 375 { 528 - if (!IS_ERR_OR_NULL(alt)) 529 - device_unregister(&alt->dev); 376 + if (IS_ERR_OR_NULL(adev)) 377 + return; 378 + typec_mux_put(to_altmode(adev)->mux); 379 + device_unregister(&adev->dev); 530 380 } 531 381 EXPORT_SYMBOL_GPL(typec_unregister_altmode); 532 382 ··· 580 398 { 581 399 struct typec_partner *partner = to_typec_partner(dev); 582 400 401 + ida_destroy(&partner->mode_ids); 583 402 kfree(partner); 584 403 } 585 404 ··· 646 463 if (!partner) 647 464 return ERR_PTR(-ENOMEM); 648 465 466 + ida_init(&partner->mode_ids); 649 467 partner->usb_pd = desc->usb_pd; 650 468 partner->accessory = desc->accessory; 651 469 ··· 695 511 { 696 512 struct typec_plug *plug = to_typec_plug(dev); 697 513 514 + ida_destroy(&plug->mode_ids); 698 515 kfree(plug); 699 516 } 700 517 ··· 748 563 749 564 sprintf(name, "plug%d", desc->index); 750 565 566 + ida_init(&plug->mode_ids); 751 567 plug->index = desc->index; 752 568 plug->dev.class = typec_class; 753 569 plug->dev.parent = &cable->dev; ··· 1269 1083 struct typec_port *port = to_typec_port(dev); 1270 1084 1271 1085 ida_simple_remove(&typec_index_ida, port->id); 1086 + ida_destroy(&port->mode_ids); 1272 1087 typec_switch_put(port->sw); 1273 1088 typec_mux_put(port->mux); 1274 1089 kfree(port); 1275 1090 } 1276 1091 1277 - static const struct device_type typec_port_dev_type = { 1092 + const struct device_type typec_port_dev_type = { 1278 1093 .name = "typec_port", 1279 1094 .groups = typec_groups, 1280 1095 .uevent = typec_uevent, ··· 1466 1279 1467 1280 /** 1468 1281 * typec_set_mode - Set mode of operation for USB Type-C connector 1469 - * @port: USB Type-C port for the connector 1470 - * @mode: Operation mode for the connector 1282 + * @port: USB Type-C connector 1283 + * @mode: Accessory Mode, USB Operation or Safe State 1471 1284 * 1472 - * Set mode @mode for @port. This function will configure the muxes needed to 1473 - * enter @mode. 1285 + * Configure @port for Accessory Mode @mode. This function will configure the 1286 + * muxes needed for @mode. 1474 1287 */ 1475 1288 int typec_set_mode(struct typec_port *port, int mode) 1476 1289 { ··· 1484 1297 * typec_port_register_altmode - Register USB Type-C Port Alternate Mode 1485 1298 * @port: USB Type-C Port that supports the alternate mode 1486 1299 * @desc: Description of the alternate mode 1300 + * @drvdata: Private pointer to driver specific info 1487 1301 * 1488 1302 * This routine is used to register an alternate mode that @port is capable of 1489 1303 * supporting. ··· 1495 1307 typec_port_register_altmode(struct typec_port *port, 1496 1308 const struct typec_altmode_desc *desc) 1497 1309 { 1498 - return typec_register_altmode(&port->dev, desc); 1310 + struct typec_altmode *adev; 1311 + struct typec_mux *mux; 1312 + char id[10]; 1313 + 1314 + sprintf(id, "id%04xm%02x", desc->svid, desc->mode); 1315 + 1316 + mux = typec_mux_get(port->dev.parent, id); 1317 + if (IS_ERR(mux)) 1318 + return ERR_CAST(mux); 1319 + 1320 + adev = typec_register_altmode(&port->dev, desc); 1321 + if (IS_ERR(adev)) 1322 + typec_mux_put(mux); 1323 + else 1324 + to_altmode(adev)->mux = mux; 1325 + 1326 + return adev; 1499 1327 } 1500 1328 EXPORT_SYMBOL_GPL(typec_port_register_altmode); 1501 1329 ··· 1585 1381 break; 1586 1382 } 1587 1383 1384 + ida_init(&port->mode_ids); 1385 + mutex_init(&port->port_type_lock); 1386 + 1588 1387 port->id = id; 1589 1388 port->cap = cap; 1590 1389 port->port_type = cap->type; 1591 - mutex_init(&port->port_type_lock); 1592 1390 port->prefer_role = cap->prefer_role; 1593 1391 1594 1392 port->dev.class = typec_class; ··· 1634 1428 1635 1429 static int __init typec_init(void) 1636 1430 { 1431 + int ret; 1432 + 1433 + ret = bus_register(&typec_bus); 1434 + if (ret) 1435 + return ret; 1436 + 1637 1437 typec_class = class_create(THIS_MODULE, "typec"); 1638 - return PTR_ERR_OR_ZERO(typec_class); 1438 + if (IS_ERR(typec_class)) { 1439 + bus_unregister(&typec_bus); 1440 + return PTR_ERR(typec_class); 1441 + } 1442 + 1443 + return 0; 1639 1444 } 1640 1445 subsys_initcall(typec_init); 1641 1446 ··· 1654 1437 { 1655 1438 class_destroy(typec_class); 1656 1439 ida_destroy(&typec_index_ida); 1440 + bus_unregister(&typec_bus); 1657 1441 } 1658 1442 module_exit(typec_exit); 1659 1443
+15
include/linux/mod_devicetable.h
··· 746 746 #define TBSVC_MATCH_PROTOCOL_VERSION 0x0004 747 747 #define TBSVC_MATCH_PROTOCOL_REVISION 0x0008 748 748 749 + /* USB Type-C Alternate Modes */ 750 + 751 + #define TYPEC_ANY_MODE 0x7 752 + 753 + /** 754 + * struct typec_device_id - USB Type-C alternate mode identifiers 755 + * @svid: Standard or Vendor ID 756 + * @mode: Mode index 757 + */ 758 + struct typec_device_id { 759 + __u16 svid; 760 + __u8 mode; 761 + kernel_ulong_t driver_data; 762 + }; 763 + 749 764 #endif /* LINUX_MOD_DEVICETABLE_H */
+3 -11
include/linux/usb/typec.h
··· 5 5 6 6 #include <linux/types.h> 7 7 8 - /* XXX: Once we have a header for USB Power Delivery, this belongs there */ 9 - #define ALTMODE_MAX_MODES 6 10 - 11 8 /* USB Type-C Specification releases */ 12 9 #define USB_TYPEC_REV_1_0 0x100 /* 1.0 */ 13 10 #define USB_TYPEC_REV_1_1 0x110 /* 1.1 */ 14 11 #define USB_TYPEC_REV_1_2 0x120 /* 1.2 */ 15 12 16 - struct typec_altmode; 17 13 struct typec_partner; 18 14 struct typec_cable; 19 15 struct typec_plug; 20 16 struct typec_port; 21 17 22 18 struct fwnode_handle; 19 + struct device; 23 20 24 21 enum typec_port_type { 25 22 TYPEC_PORT_SRC, ··· 104 107 u8 mode; 105 108 u32 vdo; 106 109 /* Only used with ports */ 107 - enum typec_port_type roles; 110 + enum typec_port_data roles; 108 111 }; 109 112 110 113 struct typec_altmode ··· 183 186 * @dr_set: Set Data Role 184 187 * @pr_set: Set Power Role 185 188 * @vconn_set: Set VCONN Role 186 - * @activate_mode: Enter/exit given Alternate Mode 187 189 * @port_type_set: Set port type 188 190 * 189 191 * Static capabilities of a single USB Type-C port. ··· 208 212 enum typec_role); 209 213 int (*vconn_set)(const struct typec_capability *, 210 214 enum typec_role); 211 - 212 - int (*activate_mode)(const struct typec_capability *, 213 - int mode, int activate); 214 215 int (*port_type_set)(const struct typec_capability *, 215 - enum typec_port_type); 216 - 216 + enum typec_port_type); 217 217 }; 218 218 219 219 /* Specific to try_role(). Indicates the user want's to clear the preference. */
+160
include/linux/usb/typec_altmode.h
··· 1 + /* SPDX-License-Identifier: GPL-2.0 */ 2 + 3 + #ifndef __USB_TYPEC_ALTMODE_H 4 + #define __USB_TYPEC_ALTMODE_H 5 + 6 + #include <linux/mod_devicetable.h> 7 + #include <linux/usb/typec.h> 8 + #include <linux/device.h> 9 + 10 + #define MODE_DISCOVERY_MAX 6 11 + 12 + struct typec_altmode_ops; 13 + 14 + /** 15 + * struct typec_altmode - USB Type-C alternate mode device 16 + * @dev: Driver model's view of this device 17 + * @svid: Standard or Vendor ID (SVID) of the alternate mode 18 + * @mode: Index of the Mode 19 + * @vdo: VDO returned by Discover Modes USB PD command 20 + * @active: Tells has the mode been entered or not 21 + * @desc: Optional human readable description of the mode 22 + * @ops: Operations vector from the driver 23 + */ 24 + struct typec_altmode { 25 + struct device dev; 26 + u16 svid; 27 + int mode; 28 + u32 vdo; 29 + unsigned int active:1; 30 + 31 + char *desc; 32 + const struct typec_altmode_ops *ops; 33 + }; 34 + 35 + #define to_typec_altmode(d) container_of(d, struct typec_altmode, dev) 36 + 37 + static inline void typec_altmode_set_drvdata(struct typec_altmode *altmode, 38 + void *data) 39 + { 40 + dev_set_drvdata(&altmode->dev, data); 41 + } 42 + 43 + static inline void *typec_altmode_get_drvdata(struct typec_altmode *altmode) 44 + { 45 + return dev_get_drvdata(&altmode->dev); 46 + } 47 + 48 + /** 49 + * struct typec_altmode_ops - Alternate mode specific operations vector 50 + * @enter: Operations to be executed with Enter Mode Command 51 + * @exit: Operations to be executed with Exit Mode Command 52 + * @attention: Callback for Attention Command 53 + * @vdm: Callback for SVID specific commands 54 + * @notify: Communication channel for platform and the alternate mode 55 + * @activate: User callback for Enter/Exit Mode 56 + */ 57 + struct typec_altmode_ops { 58 + int (*enter)(struct typec_altmode *altmode); 59 + int (*exit)(struct typec_altmode *altmode); 60 + void (*attention)(struct typec_altmode *altmode, u32 vdo); 61 + int (*vdm)(struct typec_altmode *altmode, const u32 hdr, 62 + const u32 *vdo, int cnt); 63 + int (*notify)(struct typec_altmode *altmode, unsigned long conf, 64 + void *data); 65 + int (*activate)(struct typec_altmode *altmode, int activate); 66 + }; 67 + 68 + int typec_altmode_enter(struct typec_altmode *altmode); 69 + int typec_altmode_exit(struct typec_altmode *altmode); 70 + void typec_altmode_attention(struct typec_altmode *altmode, u32 vdo); 71 + int typec_altmode_vdm(struct typec_altmode *altmode, 72 + const u32 header, const u32 *vdo, int count); 73 + int typec_altmode_notify(struct typec_altmode *altmode, unsigned long conf, 74 + void *data); 75 + const struct typec_altmode * 76 + typec_altmode_get_partner(struct typec_altmode *altmode); 77 + 78 + /* 79 + * These are the connector states (USB, Safe and Alt Mode) defined in USB Type-C 80 + * Specification. SVID specific connector states are expected to follow and 81 + * start from the value TYPEC_STATE_MODAL. 82 + */ 83 + enum { 84 + TYPEC_STATE_SAFE, /* USB Safe State */ 85 + TYPEC_STATE_USB, /* USB Operation */ 86 + TYPEC_STATE_MODAL, /* Alternate Modes */ 87 + }; 88 + 89 + /* 90 + * For the muxes there is no difference between Accessory Modes and Alternate 91 + * Modes, so the Accessory Modes are supplied with specific modal state values 92 + * here. Unlike with Alternate Modes, where the mux will be linked with the 93 + * alternate mode device, the mux for Accessory Modes will be linked with the 94 + * port device instead. 95 + * 96 + * Port drivers can use TYPEC_MODE_AUDIO and TYPEC_MODE_DEBUG as the mode 97 + * value for typec_set_mode() when accessory modes are supported. 98 + */ 99 + enum { 100 + TYPEC_MODE_AUDIO = TYPEC_STATE_MODAL, /* Audio Accessory */ 101 + TYPEC_MODE_DEBUG, /* Debug Accessory */ 102 + }; 103 + 104 + #define TYPEC_MODAL_STATE(_state_) ((_state_) + TYPEC_STATE_MODAL) 105 + 106 + struct typec_altmode *typec_altmode_get_plug(struct typec_altmode *altmode, 107 + enum typec_plug_index index); 108 + void typec_altmode_put_plug(struct typec_altmode *plug); 109 + 110 + struct typec_altmode *typec_match_altmode(struct typec_altmode **altmodes, 111 + size_t n, u16 svid, u8 mode); 112 + 113 + struct typec_altmode * 114 + typec_altmode_register_notifier(struct device *dev, u16 svid, u8 mode, 115 + struct notifier_block *nb); 116 + 117 + void typec_altmode_unregister_notifier(struct typec_altmode *adev, 118 + struct notifier_block *nb); 119 + 120 + /** 121 + * typec_altmode_get_orientation - Get cable plug orientation 122 + * altmode: Handle to the alternate mode 123 + */ 124 + static inline enum typec_orientation 125 + typec_altmode_get_orientation(struct typec_altmode *altmode) 126 + { 127 + return typec_get_orientation(typec_altmode2port(altmode)); 128 + } 129 + 130 + /** 131 + * struct typec_altmode_driver - USB Type-C alternate mode device driver 132 + * @id_table: Null terminated array of SVIDs 133 + * @probe: Callback for device binding 134 + * @remove: Callback for device unbinding 135 + * @driver: Device driver model driver 136 + * 137 + * These drivers will be bind to the partner alternate mode devices. They will 138 + * handle all SVID specific communication. 139 + */ 140 + struct typec_altmode_driver { 141 + const struct typec_device_id *id_table; 142 + int (*probe)(struct typec_altmode *altmode); 143 + void (*remove)(struct typec_altmode *altmode); 144 + struct device_driver driver; 145 + }; 146 + 147 + #define to_altmode_driver(d) container_of(d, struct typec_altmode_driver, \ 148 + driver) 149 + 150 + #define typec_altmode_register_driver(drv) \ 151 + __typec_altmode_register_driver(drv, THIS_MODULE) 152 + int __typec_altmode_register_driver(struct typec_altmode_driver *drv, 153 + struct module *module); 154 + void typec_altmode_unregister_driver(struct typec_altmode_driver *drv); 155 + 156 + #define module_typec_altmode_driver(__typec_altmode_driver) \ 157 + module_driver(__typec_altmode_driver, typec_altmode_register_driver, \ 158 + typec_altmode_unregister_driver) 159 + 160 + #endif /* __USB_TYPEC_ALTMODE_H */
+4
scripts/mod/devicetable-offsets.c
··· 221 221 DEVID_FIELD(tb_service_id, protocol_version); 222 222 DEVID_FIELD(tb_service_id, protocol_revision); 223 223 224 + DEVID(typec_device_id); 225 + DEVID_FIELD(typec_device_id, svid); 226 + DEVID_FIELD(typec_device_id, mode); 227 + 224 228 return 0; 225 229 }
+13
scripts/mod/file2alias.c
··· 1352 1352 } 1353 1353 ADD_TO_DEVTABLE("tbsvc", tb_service_id, do_tbsvc_entry); 1354 1354 1355 + /* Looks like: typec:idNmN */ 1356 + static int do_typec_entry(const char *filename, void *symval, char *alias) 1357 + { 1358 + DEF_FIELD(symval, typec_device_id, svid); 1359 + DEF_FIELD(symval, typec_device_id, mode); 1360 + 1361 + sprintf(alias, "typec:id%04X", svid); 1362 + ADD(alias, "m", mode != TYPEC_ANY_MODE, mode); 1363 + 1364 + return 1; 1365 + } 1366 + ADD_TO_DEVTABLE("typec", typec_device_id, do_typec_entry); 1367 + 1355 1368 /* Does namelen bytes of name exactly match the symbol? */ 1356 1369 static bool sym_is(const char *name, unsigned namelen, const char *symbol) 1357 1370 {