Introducing a simple bus for the alternate modes. Bus allows binding drivers to the discovered alternate modes the partners support.
Unified · Split
| @@ -0,0 +1,48 @@ | ||
| 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 | |
| @@ -0,0 +1,51 @@ | ||
| 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. | |
| @@ -222,70 +222,12 @@ Description: · 221 unmodified lines | ||
| 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 | 232 | |
| 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 | − | |
| 291 | 233 | Valid values: source, sink |
| @@ -0,0 +1,136 @@ | ||
| 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 | |
| @@ -14955,7 +14955,7 @@ L: linux-usb@vger.kernel.org · 14954 unmodified lines | ||
| 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 |
| @@ -14964,6 +14964,15 @@ F: Documentation/driver-api/usb/typec.rst · 2 unmodified lines | ||
| 14964 | 14964 | F: drivers/usb/typec/ |
| 14965 | 14965 | F: include/linux/usb/typec.h |
| 14966 | 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 | |
| 14975 | + | |
| 14967 | 14976 | USB UHCI DRIVER |
| 14968 | 14977 | M: Alan Stern <stern@rowland.harvard.edu> |
| 14969 | 14978 | L: linux-usb@vger.kernel.org |
| @@ -1,6 +1,6 @@ | ||
| 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 |
| @@ -0,0 +1,401 @@ | ||
| 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 | +}; | |
| @@ -0,0 +1,38 @@ | ||
| 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__ */ | |
| @@ -10,28 +10,13 @@ · 9 unmodified lines | ||
| 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 { |
| @@ -46,11 +31,13 @@ struct typec_partner { · 8 unmodified lines | ||
| 46 | 31 | unsigned int usb_pd:1; |
| 47 | 32 | struct usb_pd_identity *identity; |
| 48 | 33 | enum typec_accessory accessory; |
| 34 | + struct ida mode_ids; | |
| 49 | 35 | }; |
| 50 | 36 | |
| 51 | 37 | struct typec_port { |
| 52 | 38 | unsigned int id; |
| 53 | 39 | struct device dev; |
| 40 | + struct ida mode_ids; | |
| 54 | 41 | |
| 55 | 42 | int prefer_role; |
| 56 | 43 | enum typec_data_role data_role; |
| @@ -71,17 +58,14 @@ struct typec_port { · 14 unmodified lines | ||
| 71 | 58 | #define to_typec_plug(_dev_) container_of(_dev_, struct typec_plug, dev) |
| 72 | 59 | #define to_typec_cable(_dev_) container_of(_dev_, struct typec_cable, dev) |
| 73 | 60 | #define to_typec_partner(_dev_) container_of(_dev_, struct typec_partner, dev) |
| 74 | −#define to_altmode(_dev_) container_of(_dev_, struct typec_altmode, dev) | |
| 75 | 61 | |
| 76 | 62 | static const struct device_type typec_partner_dev_type; |
| 77 | 63 | static const struct device_type typec_cable_dev_type; |
| 78 | 64 | static const struct device_type typec_plug_dev_type; |
| 79 | −static const struct device_type typec_port_dev_type; | |
| 80 | 65 | |
| 81 | 66 | #define is_typec_partner(_dev_) (_dev_->type == &typec_partner_dev_type) |
| 82 | 67 | #define is_typec_cable(_dev_) (_dev_->type == &typec_cable_dev_type) |
| 83 | 68 | #define is_typec_plug(_dev_) (_dev_->type == &typec_plug_dev_type) |
| 84 | −#define is_typec_port(_dev_) (_dev_->type == &typec_port_dev_type) | |
| 85 | 69 | |
| 86 | 70 | static DEFINE_IDA(typec_index_ida); |
| 87 | 71 | static struct class *typec_class; |
| @@ -163,25 +147,148 @@ static void typec_report_identity(struct device *dev) · 75 unmodified lines | ||
| 163 | 147 | /* ------------------------------------------------------------------------- */ |
| 164 | 148 | /* Alternate Modes */ |
| 165 | 149 | |
| 150 | +static int altmode_match(struct device *dev, void *data) | |
| 151 | +{ | |
| 152 | + struct typec_altmode *adev = to_typec_altmode(dev); | |
| 153 | + struct typec_device_id *id = data; | |
| 154 | + | |
| 155 | + if (!is_typec_altmode(dev)) | |
| 156 | + return 0; | |
| 157 | + | |
| 158 | + return ((adev->svid == id->svid) && (adev->mode == id->mode)); | |
| 159 | +} | |
| 160 | + | |
| 161 | +static void typec_altmode_set_partner(struct altmode *altmode) | |
| 162 | +{ | |
| 163 | + struct typec_altmode *adev = &altmode->adev; | |
| 164 | + struct typec_device_id id = { adev->svid, adev->mode, }; | |
| 165 | + struct typec_port *port = typec_altmode2port(adev); | |
| 166 | + struct altmode *partner; | |
| 167 | + struct device *dev; | |
| 168 | + | |
| 169 | + dev = device_find_child(&port->dev, &id, altmode_match); | |
| 170 | + if (!dev) | |
| 171 | + return; | |
| 172 | + | |
| 173 | + /* Bind the port alt mode to the partner/plug alt mode. */ | |
| 174 | + partner = to_altmode(to_typec_altmode(dev)); | |
| 175 | + altmode->partner = partner; | |
| 176 | + | |
| 177 | + /* Bind the partner/plug alt mode to the port alt mode. */ | |
| 178 | + if (is_typec_plug(adev->dev.parent)) { | |
| 179 | + struct typec_plug *plug = to_typec_plug(adev->dev.parent); | |
| 180 | + | |
| 181 | + partner->plug[plug->index] = altmode; | |
| 182 | + } else { | |
| 183 | + partner->partner = altmode; | |
| 184 | + } | |
| 185 | +} | |
| 186 | + | |
| 187 | +static void typec_altmode_put_partner(struct altmode *altmode) | |
| 188 | +{ | |
| 189 | + struct altmode *partner = altmode->partner; | |
| 190 | + struct typec_altmode *adev; | |
| 191 | + | |
| 192 | + if (!partner) | |
| 193 | + return; | |
| 194 | + | |
| 195 | + adev = &partner->adev; | |
| 196 | + | |
| 197 | + if (is_typec_plug(adev->dev.parent)) { | |
| 198 | + struct typec_plug *plug = to_typec_plug(adev->dev.parent); | |
| 199 | + | |
| 200 | + partner->plug[plug->index] = NULL; | |
| 201 | + } else { | |
| 202 | + partner->partner = NULL; | |
| 203 | + } | |
| 204 | + put_device(&adev->dev); | |
| 205 | +} | |
| 206 | + | |
| 207 | +static int __typec_port_match(struct device *dev, const void *name) | |
| 208 | +{ | |
| 209 | + return !strcmp((const char *)name, dev_name(dev)); | |
| 210 | +} | |
| 211 | + | |
| 212 | +static void *typec_port_match(struct device_connection *con, int ep, void *data) | |
| 213 | +{ | |
| 214 | + return class_find_device(typec_class, NULL, con->endpoint[ep], | |
| 215 | + __typec_port_match); | |
| 216 | +} | |
| 217 | + | |
| 218 | +struct typec_altmode * | |
| 219 | +typec_altmode_register_notifier(struct device *dev, u16 svid, u8 mode, | |
| 220 | + struct notifier_block *nb) | |
| 221 | +{ | |
| 222 | + struct typec_device_id id = { svid, mode, }; | |
| 223 | + struct device *altmode_dev; | |
| 224 | + struct device *port_dev; | |
| 225 | + struct altmode *altmode; | |
| 226 | + int ret; | |
| 227 | + | |
| 228 | + /* Find the port linked to the caller */ | |
| 229 | + port_dev = device_connection_find_match(dev, NULL, NULL, | |
| 230 | + typec_port_match); | |
| 231 | + if (IS_ERR_OR_NULL(port_dev)) | |
| 232 | + return port_dev ? ERR_CAST(port_dev) : ERR_PTR(-ENODEV); | |
| 233 | + | |
| 234 | + /* Find the altmode with matching svid */ | |
| 235 | + altmode_dev = device_find_child(port_dev, &id, altmode_match); | |
| 236 | + | |
| 237 | + put_device(port_dev); | |
| 238 | + | |
| 239 | + if (!altmode_dev) | |
| 240 | + return ERR_PTR(-ENODEV); | |
| 241 | + | |
| 242 | + altmode = to_altmode(to_typec_altmode(altmode_dev)); | |
| 243 | + | |
| 244 | + /* Register notifier */ | |
| 245 | + ret = blocking_notifier_chain_register(&altmode->nh, nb); | |
| 246 | + if (ret) { | |
| 247 | + put_device(altmode_dev); | |
| 248 | + return ERR_PTR(ret); | |
| 249 | + } | |
| 250 | + | |
| 251 | + return &altmode->adev; | |
| 252 | +} | |
| 253 | +EXPORT_SYMBOL_GPL(typec_altmode_register_notifier); | |
| 254 | + | |
| 255 | +void typec_altmode_unregister_notifier(struct typec_altmode *adev, | |
| 256 | + struct notifier_block *nb) | |
| 257 | +{ | |
| 258 | + struct altmode *altmode = to_altmode(adev); | |
| 259 | + | |
| 260 | + blocking_notifier_chain_unregister(&altmode->nh, nb); | |
| 261 | + put_device(&adev->dev); | |
| 262 | +} | |
| 263 | +EXPORT_SYMBOL_GPL(typec_altmode_unregister_notifier); | |
| 264 | + | |
| 166 | 265 | /** |
| 167 | 266 | * typec_altmode_update_active - Report Enter/Exit mode |
| 168 | − * @alt: Handle to the alternate mode | |
| 267 | + * @adev: Handle to the alternate mode | |
| 169 | 268 | * @active: True when the mode has been entered |
| 170 | 269 | * |
| 171 | 270 | * If a partner or cable plug executes Enter/Exit Mode command successfully, the |
| 172 | 271 | * drivers use this routine to report the updated state of the mode. |
| 173 | 272 | */ |
| 174 | −void typec_altmode_update_active(struct typec_altmode *alt, bool active) | |
| 273 | +void typec_altmode_update_active(struct typec_altmode *adev, bool active) | |
| 175 | 274 | { |
| 176 | 275 | char dir[6]; |
| 177 | 276 | |
| 178 | − if (alt->active == active) | |
| 277 | + if (adev->active == active) | |
| 179 | 278 | return; |
| 180 | 279 | |
| 181 | − alt->active = active; | |
| 182 | − snprintf(dir, sizeof(dir), "mode%d", alt->mode); | |
| 183 | − sysfs_notify(&alt->dev.kobj, dir, "active"); | |
| 184 | − kobject_uevent(&alt->dev.kobj, KOBJ_CHANGE); | |
| 280 | + if (!is_typec_port(adev->dev.parent)) { | |
| 281 | + if (!active) | |
| 282 | + module_put(adev->dev.driver->owner); | |
| 283 | + else | |
| 284 | + WARN_ON(!try_module_get(adev->dev.driver->owner)); | |
| 285 | + } | |
| 286 | + | |
| 287 | + adev->active = active; | |
| 288 | + snprintf(dir, sizeof(dir), "mode%d", adev->mode); | |
| 289 | + sysfs_notify(&adev->dev.kobj, dir, "active"); | |
| 290 | + sysfs_notify(&adev->dev.kobj, NULL, "active"); | |
| 291 | + kobject_uevent(&adev->dev.kobj, KOBJ_CHANGE); | |
| 185 | 292 | } |
| 186 | 293 | EXPORT_SYMBOL_GPL(typec_altmode_update_active); |
| 187 | 294 | |
| @@ -208,7 +315,7 @@ EXPORT_SYMBOL_GPL(typec_altmode2port); · 20 unmodified lines | ||
| 208 | 315 | static ssize_t |
| 209 | 316 | vdo_show(struct device *dev, struct device_attribute *attr, char *buf) |
| 210 | 317 | { |
| 211 | − struct typec_altmode *alt = to_altmode(dev); | |
| 318 | + struct typec_altmode *alt = to_typec_altmode(dev); | |
| 212 | 319 | |
| 213 | 320 | return sprintf(buf, "0x%08x\n", alt->vdo); |
| 214 | 321 | } |
| @@ -217,7 +324,7 @@ static DEVICE_ATTR_RO(vdo); · 2 unmodified lines | ||
| 217 | 324 | static ssize_t |
| 218 | 325 | description_show(struct device *dev, struct device_attribute *attr, char *buf) |
| 219 | 326 | { |
| 220 | − struct typec_altmode *alt = to_altmode(dev); | |
| 327 | + struct typec_altmode *alt = to_typec_altmode(dev); | |
| 221 | 328 | |
| 222 | 329 | return sprintf(buf, "%s\n", alt->desc ? alt->desc : ""); |
| 223 | 330 | } |
| @@ -226,7 +333,7 @@ static DEVICE_ATTR_RO(description); · 2 unmodified lines | ||
| 226 | 333 | static ssize_t |
| 227 | 334 | active_show(struct device *dev, struct device_attribute *attr, char *buf) |
| 228 | 335 | { |
| 229 | − struct typec_altmode *alt = to_altmode(dev); | |
| 336 | + struct typec_altmode *alt = to_typec_altmode(dev); | |
| 230 | 337 | |
| 231 | 338 | return sprintf(buf, "%s\n", alt->active ? "yes" : "no"); |
| 232 | 339 | } |
| @@ -234,21 +341,37 @@ active_show(struct device *dev, struct device_attribute *attr, char *buf) · 1 unmodified line | ||
| 234 | 341 | static ssize_t active_store(struct device *dev, struct device_attribute *attr, |
| 235 | 342 | const char *buf, size_t size) |
| 236 | 343 | { |
| 237 | − struct typec_altmode *alt = to_altmode(dev); | |
| 238 | − struct typec_port *port = typec_altmode2port(alt); | |
| 239 | − bool activate; | |
| 344 | + struct typec_altmode *adev = to_typec_altmode(dev); | |
| 345 | + struct altmode *altmode = to_altmode(adev); | |
| 346 | + bool enter; | |
| 240 | 347 | int ret; |
| 241 | 348 | |
| 242 | − if (!port->cap->activate_mode) | |
| 243 | − return -EOPNOTSUPP; | |
| 244 | − | |
| 245 | − ret = kstrtobool(buf, &activate); | |
| 349 | + ret = kstrtobool(buf, &enter); | |
| 246 | 350 | if (ret) |
| 247 | 351 | return ret; |
| 248 | 352 | |
| 249 | − ret = port->cap->activate_mode(port->cap, alt->mode, activate); | |
| 250 | − if (ret) | |
| 251 | − return ret; | |
| 353 | + if (adev->active == enter) | |
| 354 | + return size; | |
| 355 | + | |
| 356 | + if (is_typec_port(adev->dev.parent)) { | |
| 357 | + typec_altmode_update_active(adev, enter); | |
| 358 | + | |
| 359 | + /* Make sure that the partner exits the mode before disabling */ | |
| 360 | + if (altmode->partner && !enter && altmode->partner->adev.active) | |
| 361 | + typec_altmode_exit(&altmode->partner->adev); | |
| 362 | + } else if (altmode->partner) { | |
| 363 | + if (enter && !altmode->partner->adev.active) { | |
| 364 | + dev_warn(dev, "port has the mode disabled\n"); | |
| 365 | + return -EPERM; | |
| 366 | + } | |
| 367 | + } | |
| 368 | + | |
| 369 | + /* Note: If there is no driver, the mode will not be entered */ | |
| 370 | + if (adev->ops && adev->ops->activate) { | |
| 371 | + ret = adev->ops->activate(adev, enter); | |
| 372 | + if (ret) | |
| 373 | + return ret; | |
| 374 | + } | |
| 252 | 375 | |
| 253 | 376 | return size; |
| 254 | 377 | } |
| @@ -258,7 +381,7 @@ static ssize_t · 3 unmodified lines | ||
| 258 | 381 | supported_roles_show(struct device *dev, struct device_attribute *attr, |
| 259 | 382 | char *buf) |
| 260 | 383 | { |
| 261 | − struct typec_altmode *alt = to_altmode(dev); | |
| 384 | + struct altmode *alt = to_altmode(to_typec_altmode(dev)); | |
| 262 | 385 | ssize_t ret; |
| 263 | 386 | |
| 264 | 387 | switch (alt->roles) { |
| @@ -277,29 +400,72 @@ supported_roles_show(struct device *dev, struct device_attribute *attr, · 12 unmodified lines | ||
| 277 | 400 | } |
| 278 | 401 | static DEVICE_ATTR_RO(supported_roles); |
| 279 | 402 | |
| 280 | −static void typec_altmode_release(struct device *dev) | |
| 403 | +static ssize_t | |
| 404 | +mode_show(struct device *dev, struct device_attribute *attr, char *buf) | |
| 281 | 405 | { |
| 282 | − struct typec_altmode *alt = to_altmode(dev); | |
| 406 | + struct typec_altmode *adev = to_typec_altmode(dev); | |
| 283 | 407 | |
| 284 | − kfree(alt); | |
| 408 | + return sprintf(buf, "%u\n", adev->mode); | |
| 285 | 409 | } |
| 410 | +static DEVICE_ATTR_RO(mode); | |
| 286 | 411 | |
| 287 | −static ssize_t svid_show(struct device *dev, struct device_attribute *attr, | |
| 288 | − char *buf) | |
| 412 | +static ssize_t | |
| 413 | +svid_show(struct device *dev, struct device_attribute *attr, char *buf) | |
| 289 | 414 | { |
| 290 | − struct typec_altmode *alt = to_altmode(dev); | |
| 415 | + struct typec_altmode *adev = to_typec_altmode(dev); | |
| 291 | 416 | |
| 292 | − return sprintf(buf, "%04x\n", alt->svid); | |
| 417 | + return sprintf(buf, "%04x\n", adev->svid); | |
| 293 | 418 | } |
| 294 | 419 | static DEVICE_ATTR_RO(svid); |
| 295 | 420 | |
| 296 | 421 | static struct attribute *typec_altmode_attrs[] = { |
| 422 | + &dev_attr_active.attr, | |
| 423 | + &dev_attr_mode.attr, | |
| 297 | 424 | &dev_attr_svid.attr, |
| 425 | + &dev_attr_vdo.attr, | |
| 298 | 426 | NULL |
| 299 | 427 | }; |
| 300 | 428 | ATTRIBUTE_GROUPS(typec_altmode); |
| 301 | 429 | |
| 302 | −static const struct device_type typec_altmode_dev_type = { | |
| 430 | +static int altmode_id_get(struct device *dev) | |
| 431 | +{ | |
| 432 | + struct ida *ids; | |
| 433 | + | |
| 434 | + if (is_typec_partner(dev)) | |
| 435 | + ids = &to_typec_partner(dev)->mode_ids; | |
| 436 | + else if (is_typec_plug(dev)) | |
| 437 | + ids = &to_typec_plug(dev)->mode_ids; | |
| 438 | + else | |
| 439 | + ids = &to_typec_port(dev)->mode_ids; | |
| 440 | + | |
| 441 | + return ida_simple_get(ids, 0, 0, GFP_KERNEL); | |
| 442 | +} | |
| 443 | + | |
| 444 | +static void altmode_id_remove(struct device *dev, int id) | |
| 445 | +{ | |
| 446 | + struct ida *ids; | |
| 447 | + | |
| 448 | + if (is_typec_partner(dev)) | |
| 449 | + ids = &to_typec_partner(dev)->mode_ids; | |
| 450 | + else if (is_typec_plug(dev)) | |
| 451 | + ids = &to_typec_plug(dev)->mode_ids; | |
| 452 | + else | |
| 453 | + ids = &to_typec_port(dev)->mode_ids; | |
| 454 | + | |
| 455 | + ida_simple_remove(ids, id); | |
| 456 | +} | |
| 457 | + | |
| 458 | +static void typec_altmode_release(struct device *dev) | |
| 459 | +{ | |
| 460 | + struct altmode *alt = to_altmode(to_typec_altmode(dev)); | |
| 461 | + | |
| 462 | + typec_altmode_put_partner(alt); | |
| 463 | + | |
| 464 | + altmode_id_remove(alt->adev.dev.parent, alt->id); | |
| 465 | + kfree(alt); | |
| 466 | +} | |
| 467 | + | |
| 468 | +const struct device_type typec_altmode_dev_type = { | |
| 303 | 469 | .name = "typec_alternate_mode", |
| 304 | 470 | .groups = typec_altmode_groups, |
| 305 | 471 | .release = typec_altmode_release, |
| @@ -309,58 +475,74 @@ static struct typec_altmode * · 3 unmodified lines | ||
| 309 | 475 | typec_register_altmode(struct device *parent, |
| 310 | 476 | const struct typec_altmode_desc *desc) |
| 311 | 477 | { |
| 312 | − struct typec_altmode *alt; | |
| 478 | + unsigned int id = altmode_id_get(parent); | |
| 479 | + bool is_port = is_typec_port(parent); | |
| 480 | + struct altmode *alt; | |
| 313 | 481 | int ret; |
| 314 | 482 | |
| 315 | 483 | alt = kzalloc(sizeof(*alt), GFP_KERNEL); |
| 316 | 484 | if (!alt) |
| 317 | 485 | return ERR_PTR(-ENOMEM); |
| 318 | 486 | |
| 319 | − alt->svid = desc->svid; | |
| 320 | − alt->mode = desc->mode; | |
| 321 | − alt->vdo = desc->vdo; | |
| 487 | + alt->adev.svid = desc->svid; | |
| 488 | + alt->adev.mode = desc->mode; | |
| 489 | + alt->adev.vdo = desc->vdo; | |
| 322 | 490 | alt->roles = desc->roles; |
| 491 | + alt->id = id; | |
| 323 | 492 | |
| 324 | 493 | alt->attrs[0] = &dev_attr_vdo.attr; |
| 325 | 494 | alt->attrs[1] = &dev_attr_description.attr; |
| 326 | 495 | alt->attrs[2] = &dev_attr_active.attr; |
| 327 | 496 | |
| 328 | − if (is_typec_port(parent)) | |
| 497 | + if (is_port) { | |
| 329 | 498 | alt->attrs[3] = &dev_attr_supported_roles.attr; |
| 499 | + alt->adev.active = true; /* Enabled by default */ | |
| 500 | + } | |
| 330 | 501 | |
| 331 | 502 | sprintf(alt->group_name, "mode%d", desc->mode); |
| 332 | 503 | alt->group.name = alt->group_name; |
| 333 | 504 | alt->group.attrs = alt->attrs; |
| 334 | 505 | alt->groups[0] = &alt->group; |
| 335 | 506 | |
| 336 | − alt->dev.parent = parent; | |
| 337 | − alt->dev.groups = alt->groups; | |
| 338 | − alt->dev.type = &typec_altmode_dev_type; | |
| 339 | − dev_set_name(&alt->dev, "%s-%04x:%u", dev_name(parent), | |
| 340 | − alt->svid, alt->mode); | |
| 507 | + alt->adev.dev.parent = parent; | |
| 508 | + alt->adev.dev.groups = alt->groups; | |
| 509 | + alt->adev.dev.type = &typec_altmode_dev_type; | |
| 510 | + dev_set_name(&alt->adev.dev, "%s.%u", dev_name(parent), id); | |
| 511 | + | |
| 512 | + /* Link partners and plugs with the ports */ | |
| 513 | + if (is_port) | |
| 514 | + BLOCKING_INIT_NOTIFIER_HEAD(&alt->nh); | |
| 515 | + else | |
| 516 | + typec_altmode_set_partner(alt); | |
| 517 | + | |
| 518 | + /* The partners are bind to drivers */ | |
| 519 | + if (is_typec_partner(parent)) | |
| 520 | + alt->adev.dev.bus = &typec_bus; | |
| 341 | 521 | |
| 342 | − ret = device_register(&alt->dev); | |
| 522 | + ret = device_register(&alt->adev.dev); | |
| 343 | 523 | if (ret) { |
| 344 | 524 | dev_err(parent, "failed to register alternate mode (%d)\n", |
| 345 | 525 | ret); |
| 346 | − put_device(&alt->dev); | |
| 526 | + put_device(&alt->adev.dev); | |
| 347 | 527 | return ERR_PTR(ret); |
| 348 | 528 | } |
| 349 | 529 | |
| 350 | − return alt; | |
| 530 | + return &alt->adev; | |
| 351 | 531 | } |
| 352 | 532 | |
| 353 | 533 | /** |
| 354 | 534 | * typec_unregister_altmode - Unregister Alternate Mode |
| 355 | − * @alt: The alternate mode to be unregistered | |
| 535 | + * @adev: The alternate mode to be unregistered | |
| 356 | 536 | * |
| 357 | 537 | * Unregister device created with typec_partner_register_altmode(), |
| 358 | 538 | * typec_plug_register_altmode() or typec_port_register_altmode(). |
| 359 | 539 | */ |
| 360 | −void typec_unregister_altmode(struct typec_altmode *alt) | |
| 540 | +void typec_unregister_altmode(struct typec_altmode *adev) | |
| 361 | 541 | { |
| 362 | − if (!IS_ERR_OR_NULL(alt)) | |
| 363 | − device_unregister(&alt->dev); | |
| 542 | + if (IS_ERR_OR_NULL(adev)) | |
| 543 | + return; | |
| 544 | + typec_mux_put(to_altmode(adev)->mux); | |
| 545 | + device_unregister(&adev->dev); | |
| 364 | 546 | } |
| 365 | 547 | EXPORT_SYMBOL_GPL(typec_unregister_altmode); |
| 366 | 548 | |
| @@ -398,6 +580,7 @@ static void typec_partner_release(struct device *dev) · 31 unmodified lines | ||
| 398 | 580 | { |
| 399 | 581 | struct typec_partner *partner = to_typec_partner(dev); |
| 400 | 582 | |
| 583 | + ida_destroy(&partner->mode_ids); | |
| 401 | 584 | kfree(partner); |
| 402 | 585 | } |
| 403 | 586 | |
| @@ -463,6 +646,7 @@ struct typec_partner *typec_register_partner(struct typec_port *port, · 59 unmodified lines | ||
| 463 | 646 | if (!partner) |
| 464 | 647 | return ERR_PTR(-ENOMEM); |
| 465 | 648 | |
| 649 | + ida_init(&partner->mode_ids); | |
| 466 | 650 | partner->usb_pd = desc->usb_pd; |
| 467 | 651 | partner->accessory = desc->accessory; |
| 468 | 652 | |
| @@ -511,6 +695,7 @@ static void typec_plug_release(struct device *dev) · 42 unmodified lines | ||
| 511 | 695 | { |
| 512 | 696 | struct typec_plug *plug = to_typec_plug(dev); |
| 513 | 697 | |
| 698 | + ida_destroy(&plug->mode_ids); | |
| 514 | 699 | kfree(plug); |
| 515 | 700 | } |
| 516 | 701 | |
| @@ -563,6 +748,7 @@ struct typec_plug *typec_register_plug(struct typec_cable *cable, · 46 unmodified lines | ||
| 563 | 748 | |
| 564 | 749 | sprintf(name, "plug%d", desc->index); |
| 565 | 750 | |
| 751 | + ida_init(&plug->mode_ids); | |
| 566 | 752 | plug->index = desc->index; |
| 567 | 753 | plug->dev.class = typec_class; |
| 568 | 754 | plug->dev.parent = &cable->dev; |
| @@ -1083,12 +1269,13 @@ static void typec_release(struct device *dev) · 514 unmodified lines | ||
| 1083 | 1269 | struct typec_port *port = to_typec_port(dev); |
| 1084 | 1270 | |
| 1085 | 1271 | ida_simple_remove(&typec_index_ida, port->id); |
| 1272 | + ida_destroy(&port->mode_ids); | |
| 1086 | 1273 | typec_switch_put(port->sw); |
| 1087 | 1274 | typec_mux_put(port->mux); |
| 1088 | 1275 | kfree(port); |
| 1089 | 1276 | } |
| 1090 | 1277 | |
| 1091 | −static const struct device_type typec_port_dev_type = { | |
| 1278 | +const struct device_type typec_port_dev_type = { | |
| 1092 | 1279 | .name = "typec_port", |
| 1093 | 1280 | .groups = typec_groups, |
| 1094 | 1281 | .uevent = typec_uevent, |
| @@ -1279,11 +1466,11 @@ EXPORT_SYMBOL_GPL(typec_get_orientation); · 184 unmodified lines | ||
| 1279 | 1466 | |
| 1280 | 1467 | /** |
| 1281 | 1468 | * typec_set_mode - Set mode of operation for USB Type-C connector |
| 1282 | − * @port: USB Type-C port for the connector | |
| 1283 | − * @mode: Operation mode for the connector | |
| 1469 | + * @port: USB Type-C connector | |
| 1470 | + * @mode: Accessory Mode, USB Operation or Safe State | |
| 1284 | 1471 | * |
| 1285 | − * Set mode @mode for @port. This function will configure the muxes needed to | |
| 1286 | − * enter @mode. | |
| 1472 | + * Configure @port for Accessory Mode @mode. This function will configure the | |
| 1473 | + * muxes needed for @mode. | |
| 1287 | 1474 | */ |
| 1288 | 1475 | int typec_set_mode(struct typec_port *port, int mode) |
| 1289 | 1476 | { |
| @@ -1297,6 +1484,7 @@ EXPORT_SYMBOL_GPL(typec_set_mode); · 7 unmodified lines | ||
| 1297 | 1484 | * typec_port_register_altmode - Register USB Type-C Port Alternate Mode |
| 1298 | 1485 | * @port: USB Type-C Port that supports the alternate mode |
| 1299 | 1486 | * @desc: Description of the alternate mode |
| 1487 | + * @drvdata: Private pointer to driver specific info | |
| 1300 | 1488 | * |
| 1301 | 1489 | * This routine is used to register an alternate mode that @port is capable of |
| 1302 | 1490 | * supporting. |
| @@ -1307,7 +1495,23 @@ struct typec_altmode * · 4 unmodified lines | ||
| 1307 | 1495 | typec_port_register_altmode(struct typec_port *port, |
| 1308 | 1496 | const struct typec_altmode_desc *desc) |
| 1309 | 1497 | { |
| 1310 | − return typec_register_altmode(&port->dev, desc); | |
| 1498 | + struct typec_altmode *adev; | |
| 1499 | + struct typec_mux *mux; | |
| 1500 | + char id[10]; | |
| 1501 | + | |
| 1502 | + sprintf(id, "id%04xm%02x", desc->svid, desc->mode); | |
| 1503 | + | |
| 1504 | + mux = typec_mux_get(port->dev.parent, id); | |
| 1505 | + if (IS_ERR(mux)) | |
| 1506 | + return ERR_CAST(mux); | |
| 1507 | + | |
| 1508 | + adev = typec_register_altmode(&port->dev, desc); | |
| 1509 | + if (IS_ERR(adev)) | |
| 1510 | + typec_mux_put(mux); | |
| 1511 | + else | |
| 1512 | + to_altmode(adev)->mux = mux; | |
| 1513 | + | |
| 1514 | + return adev; | |
| 1311 | 1515 | } |
| 1312 | 1516 | EXPORT_SYMBOL_GPL(typec_port_register_altmode); |
| 1313 | 1517 | |
| @@ -1381,10 +1585,12 @@ struct typec_port *typec_register_port(struct device *parent, · 67 unmodified lines | ||
| 1381 | 1585 | break; |
| 1382 | 1586 | } |
| 1383 | 1587 | |
| 1588 | + ida_init(&port->mode_ids); | |
| 1589 | + mutex_init(&port->port_type_lock); | |
| 1590 | + | |
| 1384 | 1591 | port->id = id; |
| 1385 | 1592 | port->cap = cap; |
| 1386 | 1593 | port->port_type = cap->type; |
| 1387 | − mutex_init(&port->port_type_lock); | |
| 1388 | 1594 | port->prefer_role = cap->prefer_role; |
| 1389 | 1595 | |
| 1390 | 1596 | port->dev.class = typec_class; |
| @@ -1428,8 +1634,19 @@ EXPORT_SYMBOL_GPL(typec_unregister_port); · 37 unmodified lines | ||
| 1428 | 1634 | |
| 1429 | 1635 | static int __init typec_init(void) |
| 1430 | 1636 | { |
| 1637 | + int ret; | |
| 1638 | + | |
| 1639 | + ret = bus_register(&typec_bus); | |
| 1640 | + if (ret) | |
| 1641 | + return ret; | |
| 1642 | + | |
| 1431 | 1643 | typec_class = class_create(THIS_MODULE, "typec"); |
| 1432 | − return PTR_ERR_OR_ZERO(typec_class); | |
| 1644 | + if (IS_ERR(typec_class)) { | |
| 1645 | + bus_unregister(&typec_bus); | |
| 1646 | + return PTR_ERR(typec_class); | |
| 1647 | + } | |
| 1648 | + | |
| 1649 | + return 0; | |
| 1433 | 1650 | } |
| 1434 | 1651 | subsys_initcall(typec_init); |
| 1435 | 1652 | |
| @@ -1437,6 +1654,7 @@ static void __exit typec_exit(void) · 1 unmodified line | ||
| 1437 | 1654 | { |
| 1438 | 1655 | class_destroy(typec_class); |
| 1439 | 1656 | ida_destroy(&typec_index_ida); |
| 1657 | + bus_unregister(&typec_bus); | |
| 1440 | 1658 | } |
| 1441 | 1659 | module_exit(typec_exit); |
| 1442 | 1660 | |
| @@ -746,4 +746,19 @@ struct tb_service_id { · 745 unmodified lines | ||
| 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 */ |
| @@ -5,21 +5,18 @@ · 4 unmodified lines | ||
| 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, |
| @@ -107,7 +104,7 @@ struct typec_altmode_desc { · 81 unmodified lines | ||
| 107 | 104 | u8 mode; |
| 108 | 105 | u32 vdo; |
| 109 | 106 | /* Only used with ports */ |
| 110 | − enum typec_port_type roles; | |
| 107 | + enum typec_port_data roles; | |
| 111 | 108 | }; |
| 112 | 109 | |
| 113 | 110 | struct typec_altmode |
| @@ -186,7 +183,6 @@ struct typec_partner_desc { · 72 unmodified lines | ||
| 186 | 183 | * @dr_set: Set Data Role |
| 187 | 184 | * @pr_set: Set Power Role |
| 188 | 185 | * @vconn_set: Set VCONN Role |
| 189 | − * @activate_mode: Enter/exit given Alternate Mode | |
| 190 | 186 | * @port_type_set: Set port type |
| 191 | 187 | * |
| 192 | 188 | * Static capabilities of a single USB Type-C port. |
| @@ -212,12 +208,8 @@ struct typec_capability { · 19 unmodified lines | ||
| 212 | 208 | enum typec_role); |
| 213 | 209 | int (*vconn_set)(const struct typec_capability *, |
| 214 | 210 | enum typec_role); |
| 215 | − | |
| 216 | − int (*activate_mode)(const struct typec_capability *, | |
| 217 | − int mode, int activate); | |
| 218 | 211 | int (*port_type_set)(const struct typec_capability *, |
| 219 | − enum typec_port_type); | |
| 220 | − | |
| 212 | + enum typec_port_type); | |
| 221 | 213 | }; |
| 222 | 214 | |
| 223 | 215 | /* Specific to try_role(). Indicates the user want's to clear the preference. */ |
| @@ -0,0 +1,160 @@ | ||
| 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 */ | |
| @@ -221,5 +221,9 @@ int main(void) · 220 unmodified lines | ||
| 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 | } |
| @@ -1352,6 +1352,19 @@ static int do_tbsvc_entry(const char *filename, void *symval, char *alias) · 1351 unmodified lines | ||
| 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 | { |