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.kroge...@linux.intel.com>
---
 Documentation/ABI/obsolete/sysfs-class-typec |  48 +++
 Documentation/ABI/testing/sysfs-bus-typec    |  51 ++++
 Documentation/ABI/testing/sysfs-class-typec  |  62 +---
 Documentation/driver-api/usb/typec_bus.rst   | 143 +++++++++
 drivers/usb/typec/Makefile                   |   2 +-
 drivers/usb/typec/bus.c                      | 421 +++++++++++++++++++++++++++
 drivers/usb/typec/bus.h                      |  37 +++
 drivers/usb/typec/class.c                    | 325 +++++++++++++++++----
 include/linux/mod_devicetable.h              |  15 +
 include/linux/usb/typec.h                    |  16 +-
 include/linux/usb/typec_altmode.h            | 136 +++++++++
 scripts/mod/devicetable-offsets.c            |   4 +
 scripts/mod/file2alias.c                     |  13 +
 13 files changed, 1138 insertions(+), 135 deletions(-)
 create mode 100644 Documentation/ABI/obsolete/sysfs-class-typec
 create mode 100644 Documentation/ABI/testing/sysfs-bus-typec
 create mode 100644 Documentation/driver-api/usb/typec_bus.rst
 create mode 100644 drivers/usb/typec/bus.c
 create mode 100644 drivers/usb/typec/bus.h
 create mode 100644 include/linux/usb/typec_altmode.h

diff --git a/Documentation/ABI/obsolete/sysfs-class-typec 
b/Documentation/ABI/obsolete/sysfs-class-typec
new file mode 100644
index 000000000000..32623514ee87
--- /dev/null
+++ b/Documentation/ABI/obsolete/sysfs-class-typec
@@ -0,0 +1,48 @@
+These files are deprecated and will be removed. The same files are available
+under /sys/bus/typec (see Documentation/ABI/testing/sysfs-bus-typec).
+
+What:          /sys/class/typec/<port|partner|cable>/<dev>/svid
+Date:          April 2017
+Contact:       Heikki Krogerus <heikki.kroge...@linux.intel.com>
+Description:
+               The SVID (Standard or Vendor ID) assigned by USB-IF for this
+               alternate mode.
+
+What:          /sys/class/typec/<port|partner|cable>/<dev>/mode<index>/
+Date:          April 2017
+Contact:       Heikki Krogerus <heikki.kroge...@linux.intel.com>
+Description:
+               Every supported mode will have its own directory. The name of
+               a mode will be "mode<index>" (for example mode1), where <index>
+               is the actual index to the mode VDO returned by Discover Modes
+               USB power delivery command.
+
+What:          
/sys/class/typec/<port|partner|cable>/<dev>/mode<index>/description
+Date:          April 2017
+Contact:       Heikki Krogerus <heikki.kroge...@linux.intel.com>
+Description:
+               Shows description of the mode. The description is optional for
+               the drivers, just like with the Billboard Devices.
+
+What:          /sys/class/typec/<port|partner|cable>/<dev>/mode<index>/vdo
+Date:          April 2017
+Contact:       Heikki Krogerus <heikki.kroge...@linux.intel.com>
+Description:
+               Shows the VDO in hexadecimal returned by Discover Modes command
+               for this mode.
+
+What:          /sys/class/typec/<port|partner|cable>/<dev>/mode<index>/active
+Date:          April 2017
+Contact:       Heikki Krogerus <heikki.kroge...@linux.intel.com>
+Description:
+               Shows if the mode is active or not. The attribute can be used
+               for entering/exiting the mode with partners and cable plugs, and
+               with the port alternate modes it can be used for disabling
+               support for specific alternate modes. Entering/exiting modes is
+               supported as synchronous operation so write(2) to the attribute
+               does not return until the enter/exit mode operation has
+               finished. The attribute is notified when the mode is
+               entered/exited so poll(2) on the attribute wakes up.
+               Entering/exiting a mode will also generate uevent KOBJ_CHANGE.
+
+               Valid values: yes, no
diff --git a/Documentation/ABI/testing/sysfs-bus-typec 
b/Documentation/ABI/testing/sysfs-bus-typec
new file mode 100644
index 000000000000..ead63f97d9a2
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-bus-typec
@@ -0,0 +1,51 @@
+What:          /sys/bus/typec/devices/.../active
+Date:          April 2018
+Contact:       Heikki Krogerus <heikki.kroge...@linux.intel.com>
+Description:
+               Shows if the mode is active or not. The attribute can be used
+               for entering/exiting the mode. Entering/exiting modes is
+               supported as synchronous operation so write(2) to the attribute
+               does not return until the enter/exit mode operation has
+               finished. The attribute is notified when the mode is
+               entered/exited so poll(2) on the attribute wakes up.
+               Entering/exiting a mode will also generate uevent KOBJ_CHANGE.
+
+               Valid values are boolean.
+
+What:          /sys/bus/typec/devices/.../description
+Date:          April 2018
+Contact:       Heikki Krogerus <heikki.kroge...@linux.intel.com>
+Description:
+               Shows description of the mode. The description is optional for
+               the drivers, just like with the Billboard Devices.
+
+What:          /sys/bus/typec/devices/.../mode
+Date:          April 2018
+Contact:       Heikki Krogerus <heikki.kroge...@linux.intel.com>
+Description:
+               The index number of the mode returned by Discover Modes USB
+               Power Delivery command. Depending on the alternate mode, the
+               mode index may be significant.
+
+               With some alternate modes (SVIDs), the mode index is assigned
+               for specific functionality in the specification for that
+               alternate mode.
+
+               With other alternate modes, the mode index values are not
+               assigned, and can not be therefore used for identification. When
+               the mode index is not assigned, identifying the alternate mode
+               must be done with either mode VDO or the description.
+
+What:          /sys/bus/typec/devices/.../svid
+Date:          April 2018
+Contact:       Heikki Krogerus <heikki.kroge...@linux.intel.com>
+Description:
+               The Standard or Vendor ID (SVID) assigned by USB-IF for this
+               alternate mode.
+
+What:          /sys/bus/typec/devices/.../vdo
+Date:          April 2018
+Contact:       Heikki Krogerus <heikki.kroge...@linux.intel.com>
+Description:
+               Shows the VDO in hexadecimal returned by Discover Modes command
+               for this mode.
diff --git a/Documentation/ABI/testing/sysfs-class-typec 
b/Documentation/ABI/testing/sysfs-class-typec
index 5be552e255e9..d7647b258c3c 100644
--- a/Documentation/ABI/testing/sysfs-class-typec
+++ b/Documentation/ABI/testing/sysfs-class-typec
@@ -222,70 +222,12 @@ Description:
                available. The value can be polled.
 
 
-Alternate Mode devices.
+USB Type-C port alternate mode devices.
 
-The alternate modes will have Standard or Vendor ID (SVID) assigned by USB-IF.
-The ports, partners and cable plugs can have alternate modes. A supported SVID
-will consist of a set of modes. Every SVID a port/partner/plug supports will
-have a device created for it, and every supported mode for a supported SVID 
will
-have its own directory under that device. Below <dev> refers to the device for
-the alternate mode.
-
-What:          /sys/class/typec/<port|partner|cable>/<dev>/svid
-Date:          April 2017
-Contact:       Heikki Krogerus <heikki.kroge...@linux.intel.com>
-Description:
-               The SVID (Standard or Vendor ID) assigned by USB-IF for this
-               alternate mode.
-
-What:          /sys/class/typec/<port|partner|cable>/<dev>/mode<index>/
-Date:          April 2017
-Contact:       Heikki Krogerus <heikki.kroge...@linux.intel.com>
-Description:
-               Every supported mode will have its own directory. The name of
-               a mode will be "mode<index>" (for example mode1), where <index>
-               is the actual index to the mode VDO returned by Discover Modes
-               USB power delivery command.
-
-What:          
/sys/class/typec/<port|partner|cable>/<dev>/mode<index>/description
-Date:          April 2017
-Contact:       Heikki Krogerus <heikki.kroge...@linux.intel.com>
-Description:
-               Shows description of the mode. The description is optional for
-               the drivers, just like with the Billboard Devices.
-
-What:          /sys/class/typec/<port|partner|cable>/<dev>/mode<index>/vdo
-Date:          April 2017
-Contact:       Heikki Krogerus <heikki.kroge...@linux.intel.com>
-Description:
-               Shows the VDO in hexadecimal returned by Discover Modes command
-               for this mode.
-
-What:          /sys/class/typec/<port|partner|cable>/<dev>/mode<index>/active
-Date:          April 2017
-Contact:       Heikki Krogerus <heikki.kroge...@linux.intel.com>
-Description:
-               Shows if the mode is active or not. The attribute can be used
-               for entering/exiting the mode with partners and cable plugs, and
-               with the port alternate modes it can be used for disabling
-               support for specific alternate modes. Entering/exiting modes is
-               supported as synchronous operation so write(2) to the attribute
-               does not return until the enter/exit mode operation has
-               finished. The attribute is notified when the mode is
-               entered/exited so poll(2) on the attribute wakes up.
-               Entering/exiting a mode will also generate uevent KOBJ_CHANGE.
-
-               Valid values: yes, no
-
-What:          /sys/class/typec/<port>/<dev>/mode<index>/supported_roles
+What:          /sys/class/typec/<port>/<alt mode>/supported_roles
 Date:          April 2017
 Contact:       Heikki Krogerus <heikki.kroge...@linux.intel.com>
 Description:
                Space separated list of the supported roles.
 
-               This attribute is available for the devices describing the
-               alternate modes a port supports, and it will not be exposed with
-               the devices presenting the alternate modes the partners or cable
-               plugs support.
-
                Valid values: source, sink
diff --git a/Documentation/driver-api/usb/typec_bus.rst 
b/Documentation/driver-api/usb/typec_bus.rst
new file mode 100644
index 000000000000..ef3c049a8a7f
--- /dev/null
+++ b/Documentation/driver-api/usb/typec_bus.rst
@@ -0,0 +1,143 @@
+
+API for USB Type-C Alternate Mode drivers
+=========================================
+
+Introduction
+------------
+
+Alternate modes require communication with the partner using Vendor Defined
+Messages (VDM) as defined in USB Type-C and USB Power Delivery Specifications.
+The communication is SVID (Standard or Vendor ID) specific, i.e. specific for
+every alternate mode, so every alternate mode will need custom driver.
+
+USB Type-C bus allows binding a driver to the discovered partner alternate
+modes by using the SVID and the mode number.
+
+USB Type-C Connector Class provides a device for every alternate mode a port
+supports, and separate device for every alternate mode the partner supports.
+The drivers for the alternate modes are bind to the partner alternate mode
+devices, and the port alternate mode devices must be handled by the port
+drivers.
+
+When a new partner alternate mode device is registered, it is linked to the
+alternate mode device of the port that the partner is attached to, that has
+matching SVID and mode. Communication between the port driver and alternate 
mode
+driver will happen using the same API.
+
+The port alternate mode devices are used as a proxy between the partner and the
+alternate mode drivers, so the port drivers are only expected to pass the SVID
+specific commands from the alternate mode drivers to the partner, and from the
+partners to the alternate mode drivers. No direct SVID specific communication 
is
+needed from the port drivers, but the port drivers need to provide the 
operation
+callbacks for the port alternate mode devices, just like the alternate mode
+drivers need to provide them for the partner alternate mode devices.
+
+Usage:
+------
+
+General
+~~~~~~~
+
+By default, the alternate mode drivers are responsible for entering the mode.
+It is also possible to leave the decision about entering the mode to the user
+space (See Documentation/ABI/testing/sysfs-class-typec). Port drivers should 
not
+enter any modes on their own.
+
+The alternate mode drivers need to register their operation vector in their
+``->probe`` callback with :c:func:`typec_altmode_register_ops()`. They should
+be registered before the mode is entered using :c:func:`typec_altmode_enter()`.
+
+``->vdm`` is the most important callback in the vector. It will be used to
+deliver all the SVID specific commands from the partner to the alternate mode
+driver, and vise versa in case of port drivers. The drivers send the SVID
+specific commands to each other using :c:func:`typec_altmode_vmd()`.
+
+If the communication with the partner using the SVID specific commands results
+in need to re-configure the pins on the connector, the alternate mode driver
+needs to notify the bus using :c:func:`typec_altmode_notify()`. The driver
+passes the negotiated SVID specific pin configuration value to the function as
+parameter. The bus driver will then configure the mux behind the connector 
using
+that value as the state value for the mux, and also call blocking notification
+chain to notify the external drivers about the state of the connector that need
+to know it.
+
+NOTE: The SVID specific pin configuration values must always start from
+``TYPEC_STATE_MODAL``. USB Type-C specification defines two default states for
+the connector: ``TYPEC_STATE_USB`` and ``TYPEC_STATE_SAFE``. USB Type-C
+Specification also defines two Accessory modes, Audio and Debug:
+``TYPEC_STATE_AUDIO`` and ``TYPEC_STATE_DEBUG`` These values are reserved by 
the
+bus as the four first possible values for the state, and attempts to use them
+from the alternate mode drivers will result in failure. When the alternate mode
+is entered, the bus will put the connector into ``TYPEC_STATE_SAFE`` before
+sending Enter or Exit Mode command as defined in USB Type-C Specification, and
+also put the connector back to ``TYPEC_STATE_USB`` after the mode has been
+exited.
+
+An example of working definitions for SVID specific pin configurations would
+look like this:
+
+enum {
+       ALTMODEX_CONF_A = TYPEC_STATE_MODAL,
+       ALTMODEX_CONF_B,
+       ...
+};
+
+Helper macro ``TYPEC_MODAL_STATE()`` can also be used:
+
+#define ALTMODEX_CONF_A = TYPEC_MODAL_STATE(0);
+#define ALTMODEX_CONF_B = TYPEC_MODAL_STATE(1);
+
+Notification chain
+~~~~~~~~~~~~~~~~~~
+
+The drivers for the components that the alternate modes are designed for need 
to
+get details regarding the results of the negotiation with the partner, and the
+pin configuration of the connector. In case of DisplayPort alternate mode for
+example, the GPU drivers will need to know those details. In case of
+Thunderbolt alternate mode, the thunderbolt drivers will need to know them, and
+so on.
+
+The notification chain is designed for this purpose. The drivers can register
+notifiers with :c:func:`typec_altmode_register_notifier()`.
+
+Cable plug alternate modes
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The alternate mode drivers are not bind to cable plug alternate mode devices,
+only to the partner alternate mode devices. If the alternate mode supports, or
+requires, a cable that responds to SOP Prime, and optionally SOP Double Prime
+messages, the driver for that alternate mode must request handle to the cable
+plug alternate modes using :c:func:`typec_altmode_get_plug()`, and taking over
+their control.
+
+Driver API
+----------
+
+Alternate mode driver registering/unregistering
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. kernel-doc:: drivers/usb/typec/bus.c
+   :functions: typec_altmode_register_driver typec_altmode_unregister_driver
+
+Alternate mode driver operations
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. kernel-doc:: drivers/usb/typec/bus.c
+   :functions: typec_altmode_register_ops typec_altmode_enter 
typec_altmode_exit typec_altmode_attention typec_altmode_vdm 
typec_altmode_notify
+
+API for the port drivers
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. kernel-doc:: drivers/usb/typec/bus.c
+   :functions: typec_match_altmode
+
+Cable Plug operations
+~~~~~~~~~~~~~~~~~~~~~
+
+.. kernel-doc:: drivers/usb/typec/bus.c
+   :functions: typec_altmode_get_plug typec_altmode_put_plug
+
+Notifications
+~~~~~~~~~~~~~
+.. kernel-doc:: drivers/usb/typec/class.c
+   :functions: typec_altmode_register_notifier 
typec_altmode_unregister_notifier
diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile
index 1f599a6c30cc..5466c62c8e97 100644
--- a/drivers/usb/typec/Makefile
+++ b/drivers/usb/typec/Makefile
@@ -1,6 +1,6 @@
 # SPDX-License-Identifier: GPL-2.0
 obj-$(CONFIG_TYPEC)            += typec.o
-typec-y                                := class.o mux.o
+typec-y                                := class.o mux.o bus.o
 obj-$(CONFIG_TYPEC_TCPM)       += tcpm.o
 obj-y                          += fusb302/
 obj-$(CONFIG_TYPEC_WCOVE)      += typec_wcove.o
diff --git a/drivers/usb/typec/bus.c b/drivers/usb/typec/bus.c
new file mode 100644
index 000000000000..92944aaf3d6a
--- /dev/null
+++ b/drivers/usb/typec/bus.c
@@ -0,0 +1,421 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ * Bus for USB Type-C Alternate Modes
+ *
+ * Copyright (C) 2018 Intel Corporation
+ * Author: Heikki Krogerus <heikki.kroge...@linux.intel.com>
+ */
+
+#include "bus.h"
+
+/* -------------------------------------------------------------------------- 
*/
+/* Common API */
+
+/**
+ * typec_altmode_notify - Communication between the OS and alternate mode 
driver
+ * @adev: Handle to the alternate mode
+ * @conf: Alternate mode specific configuration value
+ * @data: Alternate mode specific data
+ *
+ * The primary purpose for this function is to allow the alternate mode drivers
+ * to tell which pin configuration has been negotiated with the partner. That
+ * information will then be used for example to configure the muxes.
+ * Communication to the other direction is also possible, and low level device
+ * drivers can also send notifications to the alternate mode drivers. The 
actual
+ * communication will be specific for every SVID.
+ */
+int typec_altmode_notify(struct typec_altmode *adev,
+                        unsigned long conf, void *data)
+{
+       struct altmode *altmode;
+       struct altmode *partner;
+       int ret;
+
+       /*
+        * All SVID specific configuration values must start from
+        * TYPEC_STATE_MODAL. The first values are reserved for the pin states
+        * defined in USB Type-C specification: TYPEC_STATE_USB and
+        * TYPEC_STATE_SAFE. We'll follow this rule even with modes that do not
+        * require pin reconfiguration for the sake of simplicity.
+        */
+       if (conf < TYPEC_STATE_MODAL)
+               return -EINVAL;
+
+       if (!adev)
+               return 0;
+
+       altmode = to_altmode(adev);
+
+       if (!altmode->partner)
+               return -ENODEV;
+
+       ret = typec_set_mode(typec_altmode2port(adev), (int)conf);
+       if (ret)
+               return ret;
+
+       partner = altmode->partner;
+
+       blocking_notifier_call_chain(is_typec_port(adev->dev.parent) ?
+                                    &altmode->nh : &partner->nh,
+                                    conf, data);
+
+       if (partner->ops && partner->ops->notify)
+               return partner->ops->notify(&partner->adev, conf, data);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(typec_altmode_notify);
+
+/**
+ * typec_altmode_enter - Enter Mode
+ * @adev: The alternate mode
+ *
+ * The alternate mode drivers use this function to enter mode. The port drivers
+ * use this to inform the alternate mode drivers that their mode has been
+ * entered successfully.
+ */
+int typec_altmode_enter(struct typec_altmode *adev)
+{
+       struct altmode *partner = to_altmode(adev)->partner;
+       struct typec_altmode *pdev = &partner->adev;
+       int ret;
+
+       if (is_typec_port(adev->dev.parent)) {
+               typec_altmode_update_active(pdev, pdev->mode, true);
+               sysfs_notify(&pdev->dev.kobj, NULL, "active");
+               goto enter_mode;
+       }
+
+       if (!pdev->active)
+               return -EPERM;
+
+       /* First moving to USB Safe State */
+       ret = typec_set_mode(typec_altmode2port(adev), TYPEC_STATE_SAFE);
+       if (ret)
+               return ret;
+
+       blocking_notifier_call_chain(&partner->nh, TYPEC_STATE_SAFE, NULL);
+
+enter_mode:
+       /* Enter Mode command */
+       if (partner->ops && partner->ops->enter)
+               partner->ops->enter(pdev);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(typec_altmode_enter);
+
+/**
+ * typec_altmode_enter - Exit Mode
+ * @adev: The alternate mode
+ *
+ * The alternate mode drivers use this function to exit mode. The port drivers
+ * can also inform the alternate mode drivers with this function that the mode
+ * was successfully exited.
+ */
+int typec_altmode_exit(struct typec_altmode *adev)
+{
+       struct altmode *partner = to_altmode(adev)->partner;
+       struct typec_port *port = typec_altmode2port(adev);
+       struct typec_altmode *pdev = &partner->adev;
+       int ret;
+
+       /* In case of port, just calling the driver and exiting */
+       if (is_typec_port(adev->dev.parent)) {
+               typec_altmode_update_active(pdev, pdev->mode, false);
+               sysfs_notify(&pdev->dev.kobj, NULL, "active");
+
+               if (partner->ops && partner->ops->exit)
+                       partner->ops->exit(pdev);
+               return 0;
+       }
+
+       /* Moving to USB Safe State */
+       ret = typec_set_mode(port, TYPEC_STATE_SAFE);
+       if (ret)
+               return ret;
+
+       blocking_notifier_call_chain(&partner->nh, TYPEC_STATE_SAFE, NULL);
+
+       /* Exit Mode command */
+       if (partner->ops && partner->ops->exit)
+               partner->ops->exit(pdev);
+
+       /* Back to USB operation */
+       ret = typec_set_mode(port, TYPEC_STATE_USB);
+       if (ret)
+               return ret;
+
+       blocking_notifier_call_chain(&partner->nh, TYPEC_STATE_USB, NULL);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(typec_altmode_exit);
+
+/**
+ * typec_altmode_attention - Attention command
+ * @adev: The alternate mode
+ * @vdo: VDO for the Attention command
+ *
+ * Notifies the partner of @adev about Attention command.
+ */
+void typec_altmode_attention(struct typec_altmode *adev, const u32 vdo)
+{
+       struct altmode *partner = to_altmode(adev)->partner;
+
+       if (partner && partner->ops && partner->ops->attention)
+               partner->ops->attention(&partner->adev, vdo);
+}
+EXPORT_SYMBOL_GPL(typec_altmode_attention);
+
+/**
+ * typec_altmode_vdm - Send Vendor Defined Messages (VDM) to the partner
+ * @adev: Alternate mode handle
+ * @header: VDM Header
+ * @vdo: Array of Vendor Defined Data Objects
+ * @count: Number of Data Objects
+ *
+ * The alternate mode drivers use this function for SVID specific communication
+ * with the partner. The port drivers use it to deliver the Structured VDMs
+ * received from the partners to the alternate mode drivers.
+ */
+int typec_altmode_vdm(struct typec_altmode *adev,
+                     const u32 header, const u32 *vdo, int count)
+{
+       struct altmode *altmode;
+       struct altmode *partner;
+
+       if (!adev)
+               return 0;
+
+       altmode = to_altmode(adev);
+
+       if (!altmode->partner)
+               return -ENODEV;
+
+       partner = altmode->partner;
+
+       if (partner->ops && partner->ops->vdm)
+               return partner->ops->vdm(&partner->adev, header, vdo, count);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(typec_altmode_vdm);
+
+void typec_altmode_register_ops(struct typec_altmode *adev,
+                               const struct typec_altmode_ops *ops)
+{
+       to_altmode(adev)->ops = ops;
+}
+EXPORT_SYMBOL_GPL(typec_altmode_register_ops);
+
+/* -------------------------------------------------------------------------- 
*/
+/* API for the alternate mode drivers */
+
+/**
+ * typec_altmode_get_plug - Find cable plug alternate mode
+ * @adev: Handle to partner alternate mode
+ * @index: Cable plug index
+ *
+ * Increment reference count for cable plug alternate mode device. Returns
+ * handle to the cable plug alternate mode, or NULL if none is found.
+ */
+struct typec_altmode *typec_altmode_get_plug(struct typec_altmode *adev,
+                                            int index)
+{
+       struct altmode *port = to_altmode(adev)->partner;
+
+       if (port->plug[index]) {
+               get_device(&port->plug[index]->adev.dev);
+               return &port->plug[index]->adev;
+       }
+
+       return NULL;
+}
+EXPORT_SYMBOL_GPL(typec_altmode_get_plug);
+
+/**
+ * typec_altmode_get_plug - Decrement cable plug alternate mode reference count
+ * @plug: Handle to the cable plug alternate mode
+ */
+void typec_altmode_put_plug(struct typec_altmode *plug)
+{
+       if (plug)
+               put_device(&plug->dev);
+}
+EXPORT_SYMBOL_GPL(typec_altmode_put_plug);
+
+int __typec_altmode_register_driver(struct typec_altmode_driver *drv,
+                                   struct module *module)
+{
+       if (!drv->probe)
+               return -EINVAL;
+
+       drv->driver.owner = module;
+       drv->driver.bus = &typec_bus;
+
+       return driver_register(&drv->driver);
+}
+EXPORT_SYMBOL_GPL(__typec_altmode_register_driver);
+
+void typec_altmode_unregister_driver(struct typec_altmode_driver *drv)
+{
+       driver_unregister(&drv->driver);
+}
+EXPORT_SYMBOL_GPL(typec_altmode_unregister_driver);
+
+/* -------------------------------------------------------------------------- 
*/
+/* API for the port drivers */
+
+bool typec_altmode_ufp_capable(struct typec_altmode *adev)
+{
+       struct altmode *altmode = to_altmode(adev);
+
+       if (!is_typec_port(adev->dev.parent))
+               return false;
+
+       return !(altmode->roles == TYPEC_PORT_DFP);
+}
+EXPORT_SYMBOL_GPL(typec_altmode_ufp_capable);
+
+bool typec_altmode_dfp_capable(struct typec_altmode *adev)
+{
+       struct altmode *altmode = to_altmode(adev);
+
+       if (!is_typec_port(adev->dev.parent))
+               return false;
+
+       return !(altmode->roles == TYPEC_PORT_UFP);
+}
+EXPORT_SYMBOL_GPL(typec_altmode_dfp_capable);
+
+/**
+ * typec_match_altmode - Match SVID to an array of alternate modes
+ * @altmodes: Array of alternate modes
+ * @n: Number of elements in the array, or -1 for NULL termiated arrays
+ * @svid: Standard or Vendor ID to match with
+ *
+ * Return pointer to an alternate mode with SVID mathing @svid, or NULL when no
+ * match is found.
+ */
+struct typec_altmode *typec_match_altmode(struct typec_altmode **altmodes,
+                                         size_t n, u16 svid, u8 mode)
+{
+       int i;
+
+       for (i = 0; i < n; i++) {
+               if (!altmodes[i])
+                       break;
+               if (altmodes[i]->svid == svid && altmodes[i]->mode == mode)
+                       return altmodes[i];
+       }
+
+       return NULL;
+}
+EXPORT_SYMBOL_GPL(typec_match_altmode);
+
+/* -------------------------------------------------------------------------- 
*/
+
+static ssize_t
+description_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct typec_altmode *alt = to_typec_altmode(dev);
+
+       return sprintf(buf, "%s\n", alt->desc ? alt->desc : "");
+}
+static DEVICE_ATTR_RO(description);
+
+static struct attribute *typec_attrs[] = {
+       &dev_attr_description.attr,
+       NULL
+};
+ATTRIBUTE_GROUPS(typec);
+
+static int typec_match(struct device *dev, struct device_driver *driver)
+{
+       struct typec_altmode_driver *drv = to_altmode_driver(driver);
+       struct typec_altmode *altmode = to_typec_altmode(dev);
+       const struct typec_device_id *id;
+
+       for (id = drv->id_table; id->svid; id++)
+               if ((id->svid == altmode->svid) &&
+                   (id->mode == TYPEC_ANY_MODE || id->mode == altmode->mode))
+                       return 1;
+       return 0;
+}
+
+static int typec_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+       struct typec_altmode *altmode = to_typec_altmode(dev);
+
+       if (add_uevent_var(env, "SVID=%04X", altmode->svid))
+               return -ENOMEM;
+
+       if (add_uevent_var(env, "MODE=%u", altmode->mode))
+               return -ENOMEM;
+
+       return add_uevent_var(env, "MODALIAS=typec:id%04Xm%02X",
+                             altmode->svid, altmode->mode);
+}
+
+static int typec_altmode_create_links(struct altmode *alt)
+{
+       struct device *port_dev = &alt->partner->adev.dev;
+       struct device *dev = &alt->adev.dev;
+       int err;
+
+       err = sysfs_create_link(&dev->kobj, &port_dev->kobj, "port");
+       if (err)
+               return err;
+
+       err = sysfs_create_link(&port_dev->kobj, &dev->kobj, "partner");
+       if (err)
+               sysfs_remove_link(&dev->kobj, "port");
+
+       return err;
+}
+
+static int typec_probe(struct device *dev)
+{
+       struct typec_altmode_driver *drv = to_altmode_driver(dev->driver);
+       struct typec_altmode *adev = to_typec_altmode(dev);
+       struct altmode *altmode = to_altmode(adev);
+
+       /* Fail if the port does not support the alternate mode */
+       if (!altmode->partner)
+               return -ENODEV;
+
+       if (typec_altmode_create_links(altmode))
+               dev_warn(dev, "failed to create symlinks\n");
+
+       return drv->probe(adev, altmode->partner->adev.vdo);
+}
+
+static int typec_remove(struct device *dev)
+{
+       struct typec_altmode_driver *drv = to_altmode_driver(dev->driver);
+       struct typec_altmode *adev = to_typec_altmode(dev);
+       struct altmode *altmode = to_altmode(adev);
+
+       if (!is_typec_port(adev->dev.parent)) {
+               sysfs_remove_link(&altmode->partner->adev.dev.kobj, "partner");
+               sysfs_remove_link(&adev->dev.kobj, "port");
+       }
+
+       if (drv->remove)
+               drv->remove(to_typec_altmode(dev));
+
+       if (adev->active)
+               typec_altmode_exit(adev);
+
+       return 0;
+}
+
+struct bus_type typec_bus = {
+       .name = "typec",
+       .dev_groups = typec_groups,
+       .match = typec_match,
+       .uevent = typec_uevent,
+       .probe = typec_probe,
+       .remove = typec_remove,
+};
diff --git a/drivers/usb/typec/bus.h b/drivers/usb/typec/bus.h
new file mode 100644
index 000000000000..38585363952f
--- /dev/null
+++ b/drivers/usb/typec/bus.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef __USB_TYPEC_ALTMODE_H__
+#define __USB_TYPEC_ALTMODE_H__
+
+#include <linux/usb/typec.h>
+
+struct bus_type;
+
+struct altmode {
+       unsigned int                    id;
+       struct typec_altmode            adev;
+
+       enum typec_port_data            roles;
+
+       struct attribute                *attrs[5];
+       char                            group_name[6];
+       struct attribute_group          group;
+       const struct attribute_group    *groups[2];
+
+       struct altmode                  *partner;
+       struct altmode                  *plug[2];
+       const struct typec_altmode_ops  *ops;
+
+       struct blocking_notifier_head   nh;
+};
+
+#define to_altmode(d) container_of(d, struct altmode, adev)
+
+extern struct bus_type typec_bus;
+extern const struct device_type typec_altmode_dev_type;
+extern const struct device_type typec_port_dev_type;
+
+#define is_typec_altmode(_dev_) (_dev_->type == &typec_altmode_dev_type)
+#define is_typec_port(_dev_) (_dev_->type == &typec_port_dev_type)
+
+#endif /* __USB_TYPEC_ALTMODE_H__ */
diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c
index 26eeab1491b7..33fffb853994 100644
--- a/drivers/usb/typec/class.c
+++ b/drivers/usb/typec/class.c
@@ -6,6 +6,7 @@
  * Author: Heikki Krogerus <heikki.kroge...@linux.intel.com>
  */
 
+#include <linux/connection.h>
 #include <linux/device.h>
 #include <linux/module.h>
 #include <linux/mutex.h>
@@ -13,25 +14,12 @@
 #include <linux/usb/typec.h>
 #include <linux/usb/typec_mux.h>
 
-struct typec_altmode {
-       struct device                   dev;
-       u16                             svid;
-       u8                              mode;
-
-       u32                             vdo;
-       char                            *desc;
-       enum typec_port_type            roles;
-       unsigned int                    active:1;
-
-       struct attribute                *attrs[5];
-       char                            group_name[6];
-       struct attribute_group          group;
-       const struct attribute_group    *groups[2];
-};
+#include "bus.h"
 
 struct typec_plug {
        struct device                   dev;
        enum typec_plug_index           index;
+       struct ida                      mode_ids;
 };
 
 struct typec_cable {
@@ -46,11 +34,13 @@ struct typec_partner {
        unsigned int                    usb_pd:1;
        struct usb_pd_identity          *identity;
        enum typec_accessory            accessory;
+       struct ida                      mode_ids;
 };
 
 struct typec_port {
        unsigned int                    id;
        struct device                   dev;
+       struct ida                      mode_ids;
 
        int                             prefer_role;
        enum typec_data_role            data_role;
@@ -71,17 +61,14 @@ struct typec_port {
 #define to_typec_plug(_dev_) container_of(_dev_, struct typec_plug, dev)
 #define to_typec_cable(_dev_) container_of(_dev_, struct typec_cable, dev)
 #define to_typec_partner(_dev_) container_of(_dev_, struct typec_partner, dev)
-#define to_altmode(_dev_) container_of(_dev_, struct typec_altmode, dev)
 
 static const struct device_type typec_partner_dev_type;
 static const struct device_type typec_cable_dev_type;
 static const struct device_type typec_plug_dev_type;
-static const struct device_type typec_port_dev_type;
 
 #define is_typec_partner(_dev_) (_dev_->type == &typec_partner_dev_type)
 #define is_typec_cable(_dev_) (_dev_->type == &typec_cable_dev_type)
 #define is_typec_plug(_dev_) (_dev_->type == &typec_plug_dev_type)
-#define is_typec_port(_dev_) (_dev_->type == &typec_port_dev_type)
 
 static DEFINE_IDA(typec_index_ida);
 static struct class *typec_class;
@@ -163,27 +150,141 @@ static void typec_report_identity(struct device *dev)
 /* ------------------------------------------------------------------------- */
 /* Alternate Modes */
 
+static int altmode_match(struct device *dev, void *data)
+{
+       struct typec_altmode *adev = to_typec_altmode(dev);
+       struct typec_device_id *id = data;
+
+       if (!is_typec_altmode(dev))
+               return 0;
+
+       return ((adev->svid == id->svid) && (adev->mode == id->mode));
+}
+
+static void typec_altmode_get_partner(struct altmode *altmode)
+{
+       struct typec_altmode *adev = &altmode->adev;
+       struct typec_device_id id = { adev->svid, adev->mode, };
+       struct typec_port *port = typec_altmode2port(adev);
+       struct altmode *partner;
+       struct device *dev;
+
+       dev = device_find_child(&port->dev, &id, altmode_match);
+       if (!dev)
+               return;
+
+       /* Bind the port alt mode to the partner/plug alt mode. */
+       partner = to_altmode(to_typec_altmode(dev));
+       altmode->partner = partner;
+
+       /* Bind the partner/plug alt mode to the port alt mode. */
+       if (is_typec_plug(adev->dev.parent)) {
+               struct typec_plug *plug = to_typec_plug(adev->dev.parent);
+
+               partner->plug[plug->index] = altmode;
+       } else {
+               partner->partner = altmode;
+       }
+}
+
+static void typec_altmode_put_partner(struct altmode *altmode)
+{
+       struct altmode *partner = altmode->partner;
+       struct typec_altmode *adev;
+
+       if (!partner)
+               return;
+
+       adev = &partner->adev;
+
+       if (is_typec_plug(adev->dev.parent)) {
+               struct typec_plug *plug = to_typec_plug(adev->dev.parent);
+
+               partner->plug[plug->index] = NULL;
+       } else {
+               partner->partner = NULL;
+       }
+       put_device(&adev->dev);
+}
+
+static int __typec_port_match(struct device *dev, const void *name)
+{
+       return !strcmp((const char *)name, dev_name(dev));
+}
+
+static void *typec_port_match(struct devcon *con, int ep, void *data)
+{
+       return class_find_device(typec_class, NULL, con->endpoint[ep],
+                                __typec_port_match);
+}
+
+struct typec_altmode *
+typec_altmode_register_notifier(struct device *dev, u16 svid, u8 mode,
+                               struct notifier_block *nb)
+{
+       struct typec_device_id id = { svid, mode, };
+       struct device *altmode_dev;
+       struct device *port_dev;
+       struct altmode *altmode;
+       int ret;
+
+       /* Find the port linked to the caller */
+       port_dev = __device_find_connection(dev, NULL, NULL, typec_port_match);
+       if (!port_dev)
+               return ERR_PTR(-ENODEV);
+
+       /* Find the altmode with matching svid */
+       altmode_dev = device_find_child(port_dev, &id, altmode_match);
+
+       put_device(port_dev);
+
+       if (!altmode_dev)
+               return ERR_PTR(-ENODEV);
+
+       altmode = to_altmode(to_typec_altmode(altmode_dev));
+
+       /* Register notifier */
+       ret = blocking_notifier_chain_register(&altmode->nh, nb);
+       if (ret) {
+               put_device(altmode_dev);
+               return ERR_PTR(ret);
+       }
+
+       return &altmode->adev;
+}
+EXPORT_SYMBOL_GPL(typec_altmode_register_notifier);
+
+void typec_altmode_unregister_notifier(struct typec_altmode *adev,
+                                      struct notifier_block *nb)
+{
+       struct altmode *altmode = to_altmode(adev);
+
+       blocking_notifier_chain_unregister(&altmode->nh, nb);
+       put_device(&adev->dev);
+}
+EXPORT_SYMBOL_GPL(typec_altmode_unregister_notifier);
+
 /**
  * typec_altmode_update_active - Report Enter/Exit mode
- * @alt: Handle to the alternate mode
+ * @adev: Handle to the alternate mode
  * @mode: Mode index
  * @active: True when the mode has been entered
  *
  * If a partner or cable plug executes Enter/Exit Mode command successfully, 
the
  * drivers use this routine to report the updated state of the mode.
  */
-void typec_altmode_update_active(struct typec_altmode *alt, int mode,
+void typec_altmode_update_active(struct typec_altmode *adev, int mode,
                                 bool active)
 {
        char dir[6];
 
-       if (alt->active == active)
+       if (adev->active == active)
                return;
 
-       alt->active = active;
+       adev->active = active;
        snprintf(dir, sizeof(dir), "mode%d", mode);
-       sysfs_notify(&alt->dev.kobj, dir, "active");
-       kobject_uevent(&alt->dev.kobj, KOBJ_CHANGE);
+       sysfs_notify(&adev->dev.kobj, dir, "active");
+       kobject_uevent(&adev->dev.kobj, KOBJ_CHANGE);
 }
 EXPORT_SYMBOL_GPL(typec_altmode_update_active);
 
@@ -210,7 +311,7 @@ EXPORT_SYMBOL_GPL(typec_altmode2port);
 static ssize_t
 vdo_show(struct device *dev, struct device_attribute *attr, char *buf)
 {
-       struct typec_altmode *alt = to_altmode(dev);
+       struct typec_altmode *alt = to_typec_altmode(dev);
 
        return sprintf(buf, "0x%08x\n", alt->vdo);
 }
@@ -219,7 +320,7 @@ static DEVICE_ATTR_RO(vdo);
 static ssize_t
 description_show(struct device *dev, struct device_attribute *attr, char *buf)
 {
-       struct typec_altmode *alt = to_altmode(dev);
+       struct typec_altmode *alt = to_typec_altmode(dev);
 
        return sprintf(buf, "%s\n", alt->desc ? alt->desc : "");
 }
@@ -228,28 +329,46 @@ static DEVICE_ATTR_RO(description);
 static ssize_t
 active_show(struct device *dev, struct device_attribute *attr, char *buf)
 {
-       struct typec_altmode *alt = to_altmode(dev);
+       struct typec_altmode *alt = to_typec_altmode(dev);
 
        return sprintf(buf, "%s\n", alt->active ? "yes" : "no");
 }
 
-static ssize_t
-active_store(struct device *dev, struct device_attribute *attr,
-                          const char *buf, size_t size)
+static ssize_t active_store(struct device *dev, struct device_attribute *attr,
+                           const char *buf, size_t size)
 {
-       struct typec_altmode *alt = to_altmode(dev);
-       struct typec_port *port = typec_altmode2port(alt);
-       bool activate;
+       struct typec_altmode *adev = to_typec_altmode(dev);
+       struct typec_port *port = typec_altmode2port(adev);
+       struct altmode *altmode = to_altmode(adev);
+       bool enter;
        int ret;
 
        if (!port->cap->activate_mode)
                return -EOPNOTSUPP;
 
-       ret = kstrtobool(buf, &activate);
+       ret = kstrtobool(buf, &enter);
        if (ret)
                return ret;
 
-       ret = port->cap->activate_mode(port->cap, alt->mode, activate);
+       if (adev->active == enter)
+               return size;
+
+       if (is_typec_port(adev->dev.parent)) {
+               typec_altmode_update_active(adev, adev->mode, enter);
+               sysfs_notify(&adev->dev.kobj, NULL, "active");
+
+               if (!altmode->partner)
+                       return size;
+       } else {
+               adev = &altmode->partner->adev;
+
+               if (!adev->active) {
+                       dev_warn(dev, "port has the mode disabled\n");
+                       return -EPERM;
+               }
+       }
+
+       ret = port->cap->activate_mode(adev, enter);
        if (ret)
                return ret;
 
@@ -261,7 +380,7 @@ static ssize_t
 supported_roles_show(struct device *dev, struct device_attribute *attr,
                     char *buf)
 {
-       struct typec_altmode *alt = to_altmode(dev);
+       struct altmode *alt = to_altmode(to_typec_altmode(dev));
        ssize_t ret;
 
        switch (alt->roles) {
@@ -280,29 +399,72 @@ supported_roles_show(struct device *dev, struct 
device_attribute *attr,
 }
 static DEVICE_ATTR_RO(supported_roles);
 
-static void typec_altmode_release(struct device *dev)
+static ssize_t
+mode_show(struct device *dev, struct device_attribute *attr, char *buf)
 {
-       struct typec_altmode *alt = to_altmode(dev);
+       struct typec_altmode *adev = to_typec_altmode(dev);
 
-       kfree(alt);
+       return sprintf(buf, "%u\n", adev->mode);
 }
+static DEVICE_ATTR_RO(mode);
 
-static ssize_t svid_show(struct device *dev, struct device_attribute *attr,
-                        char *buf)
+static ssize_t
+svid_show(struct device *dev, struct device_attribute *attr, char *buf)
 {
-       struct typec_altmode *alt = to_altmode(dev);
+       struct typec_altmode *adev = to_typec_altmode(dev);
 
-       return sprintf(buf, "%04x\n", alt->svid);
+       return sprintf(buf, "%04x\n", adev->svid);
 }
 static DEVICE_ATTR_RO(svid);
 
 static struct attribute *typec_altmode_attrs[] = {
+       &dev_attr_active.attr,
+       &dev_attr_mode.attr,
        &dev_attr_svid.attr,
+       &dev_attr_vdo.attr,
        NULL
 };
 ATTRIBUTE_GROUPS(typec_altmode);
 
-static const struct device_type typec_altmode_dev_type = {
+static int altmode_id_get(struct device *dev)
+{
+       struct ida *ids;
+
+       if (is_typec_partner(dev))
+               ids = &to_typec_partner(dev)->mode_ids;
+       else if (is_typec_plug(dev))
+               ids = &to_typec_plug(dev)->mode_ids;
+       else
+               ids = &to_typec_port(dev)->mode_ids;
+
+       return ida_simple_get(ids, 0, 0, GFP_KERNEL);
+}
+
+static void altmode_id_remove(struct device *dev, int id)
+{
+       struct ida *ids;
+
+       if (is_typec_partner(dev))
+               ids = &to_typec_partner(dev)->mode_ids;
+       else if (is_typec_plug(dev))
+               ids = &to_typec_plug(dev)->mode_ids;
+       else
+               ids = &to_typec_port(dev)->mode_ids;
+
+       ida_simple_remove(ids, id);
+}
+
+static void typec_altmode_release(struct device *dev)
+{
+       struct altmode *alt = to_altmode(to_typec_altmode(dev));
+
+       typec_altmode_put_partner(alt);
+
+       altmode_id_remove(alt->adev.dev.parent, alt->id);
+       kfree(alt);
+}
+
+const struct device_type typec_altmode_dev_type = {
        .name = "typec_alternate_mode",
        .groups = typec_altmode_groups,
        .release = typec_altmode_release,
@@ -312,58 +474,72 @@ static struct typec_altmode *
 typec_register_altmode(struct device *parent,
                       const struct typec_altmode_desc *desc)
 {
-       struct typec_altmode *alt;
+       unsigned int id = altmode_id_get(parent);
+       bool is_port = is_typec_port(parent);
+       struct altmode *alt;
        int ret;
 
        alt = kzalloc(sizeof(*alt), GFP_KERNEL);
        if (!alt)
                return ERR_PTR(-ENOMEM);
 
-       alt->svid = desc->svid;
-       alt->mode = desc->mode;
-       alt->vdo = desc->vdo;
+       alt->adev.svid = desc->svid;
+       alt->adev.mode = desc->mode;
+       alt->adev.vdo = desc->vdo;
        alt->roles = desc->roles;
+       alt->id = id;
 
        alt->attrs[0] = &dev_attr_vdo.attr;
        alt->attrs[1] = &dev_attr_description.attr;
        alt->attrs[2] = &dev_attr_active.attr;
 
-       if (is_typec_port(parent))
+       if (is_port) {
                alt->attrs[3] = &dev_attr_supported_roles.attr;
+               alt->adev.active = true; /* Enabled by default */
+       }
 
        sprintf(alt->group_name, "mode%d", desc->mode);
        alt->group.name = alt->group_name;
        alt->group.attrs = alt->attrs;
        alt->groups[0] = &alt->group;
 
-       alt->dev.parent = parent;
-       alt->dev.groups = alt->groups;
-       alt->dev.type = &typec_altmode_dev_type;
-       dev_set_name(&alt->dev, "%s-%04x:%u", dev_name(parent),
-                    alt->svid, alt->mode);
+       alt->adev.dev.parent = parent;
+       alt->adev.dev.groups = alt->groups;
+       alt->adev.dev.type = &typec_altmode_dev_type;
+       dev_set_name(&alt->adev.dev, "%s.%u", dev_name(parent), id);
+
+       /* Link partners and plugs with the ports */
+       if (is_port)
+               BLOCKING_INIT_NOTIFIER_HEAD(&alt->nh);
+       else
+               typec_altmode_get_partner(alt);
 
-       ret = device_register(&alt->dev);
+       /* The partners are bind to drivers */
+       if (is_typec_partner(parent))
+               alt->adev.dev.bus = &typec_bus;
+
+       ret = device_register(&alt->adev.dev);
        if (ret) {
                dev_err(parent, "failed to register alternate mode (%d)\n",
                        ret);
-               put_device(&alt->dev);
+               put_device(&alt->adev.dev);
                return ERR_PTR(ret);
        }
 
-       return alt;
+       return &alt->adev;
 }
 
 /**
  * typec_unregister_altmode - Unregister Alternate Mode
- * @alt: The alternate mode to be unregistered
+ * @adev: The alternate mode to be unregistered
  *
  * Unregister device created with typec_partner_register_altmode(),
  * typec_plug_register_altmode() or typec_port_register_altmode().
  */
-void typec_unregister_altmode(struct typec_altmode *alt)
+void typec_unregister_altmode(struct typec_altmode *adev)
 {
-       if (!IS_ERR_OR_NULL(alt))
-               device_unregister(&alt->dev);
+       if (!IS_ERR_OR_NULL(adev))
+               device_unregister(&adev->dev);
 }
 EXPORT_SYMBOL_GPL(typec_unregister_altmode);
 
@@ -401,6 +577,7 @@ static void typec_partner_release(struct device *dev)
 {
        struct typec_partner *partner = to_typec_partner(dev);
 
+       ida_destroy(&partner->mode_ids);
        kfree(partner);
 }
 
@@ -466,6 +643,7 @@ struct typec_partner *typec_register_partner(struct 
typec_port *port,
        if (!partner)
                return ERR_PTR(-ENOMEM);
 
+       ida_init(&partner->mode_ids);
        partner->usb_pd = desc->usb_pd;
        partner->accessory = desc->accessory;
 
@@ -514,6 +692,7 @@ static void typec_plug_release(struct device *dev)
 {
        struct typec_plug *plug = to_typec_plug(dev);
 
+       ida_destroy(&plug->mode_ids);
        kfree(plug);
 }
 
@@ -566,6 +745,7 @@ struct typec_plug *typec_register_plug(struct typec_cable 
*cable,
 
        sprintf(name, "plug%d", desc->index);
 
+       ida_init(&plug->mode_ids);
        plug->index = desc->index;
        plug->dev.class = typec_class;
        plug->dev.parent = &cable->dev;
@@ -1080,12 +1260,13 @@ static void typec_release(struct device *dev)
        struct typec_port *port = to_typec_port(dev);
 
        ida_simple_remove(&typec_index_ida, port->id);
+       ida_destroy(&port->mode_ids);
        typec_switch_put(port->sw);
        typec_mux_put(port->mux);
        kfree(port);
 }
 
-static const struct device_type typec_port_dev_type = {
+const struct device_type typec_port_dev_type = {
        .name = "typec_port",
        .groups = typec_groups,
        .uevent = typec_uevent,
@@ -1238,6 +1419,8 @@ EXPORT_SYMBOL_GPL(typec_set_mode);
  * typec_port_register_altmode - Register USB Type-C Port Alternate Mode
  * @port: USB Type-C Port that supports the alternate mode
  * @desc: Description of the alternate mode
+ * @ops: Port specific operations for the alternate mode
+ * @drvdata: Private pointer to driver specific info
  *
  * This routine is used to register an alternate mode that @port is capable of
  * supporting.
@@ -1322,10 +1505,12 @@ struct typec_port *typec_register_port(struct device 
*parent,
                break;
        }
 
+       ida_init(&port->mode_ids);
+       mutex_init(&port->port_type_lock);
+
        port->id = id;
        port->cap = cap;
        port->port_type = cap->type;
-       mutex_init(&port->port_type_lock);
        port->prefer_role = cap->prefer_role;
 
        port->dev.class = typec_class;
@@ -1369,8 +1554,19 @@ EXPORT_SYMBOL_GPL(typec_unregister_port);
 
 static int __init typec_init(void)
 {
+       int ret;
+
+       ret = bus_register(&typec_bus);
+       if (ret)
+               return ret;
+
        typec_class = class_create(THIS_MODULE, "typec");
-       return PTR_ERR_OR_ZERO(typec_class);
+       if (IS_ERR(typec_class)) {
+               bus_unregister(&typec_bus);
+               return PTR_ERR(typec_class);
+       }
+
+       return 0;
 }
 subsys_initcall(typec_init);
 
@@ -1378,6 +1574,7 @@ static void __exit typec_exit(void)
 {
        class_destroy(typec_class);
        ida_destroy(&typec_index_ida);
+       bus_unregister(&typec_bus);
 }
 module_exit(typec_exit);
 
diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h
index 48fb2b43c35a..17c1a912f524 100644
--- a/include/linux/mod_devicetable.h
+++ b/include/linux/mod_devicetable.h
@@ -733,4 +733,19 @@ struct tb_service_id {
 #define TBSVC_MATCH_PROTOCOL_VERSION   0x0004
 #define TBSVC_MATCH_PROTOCOL_REVISION  0x0008
 
+/* USB Type-C Alternate Modes */
+
+#define TYPEC_ANY_MODE 0x7
+
+/**
+ * struct typec_device_id - USB Type-C alternate mode identifiers
+ * @svid: Standard or Vendor ID
+ * @mode: Mode index
+ */
+struct typec_device_id {
+       __u16 svid;
+       __u8 mode;
+       kernel_ulong_t driver_data;
+};
+
 #endif /* LINUX_MOD_DEVICETABLE_H */
diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h
index 278b6b42c7ea..a19aa3db4272 100644
--- a/include/linux/usb/typec.h
+++ b/include/linux/usb/typec.h
@@ -4,16 +4,13 @@
 #define __LINUX_USB_TYPEC_H
 
 #include <linux/types.h>
-
-/* XXX: Once we have a header for USB Power Delivery, this belongs there */
-#define ALTMODE_MAX_MODES      6
+#include <linux/usb/typec_altmode.h>
 
 /* USB Type-C Specification releases */
 #define USB_TYPEC_REV_1_0      0x100 /* 1.0 */
 #define USB_TYPEC_REV_1_1      0x110 /* 1.1 */
 #define USB_TYPEC_REV_1_2      0x120 /* 1.2 */
 
-struct typec_altmode;
 struct typec_partner;
 struct typec_cable;
 struct typec_plug;
@@ -107,7 +104,7 @@ struct typec_altmode_desc {
        u8                      mode;
        u32                     vdo;
        /* Only used with ports */
-       enum typec_port_type    roles;
+       enum typec_port_data    roles;
 };
 
 struct typec_altmode
@@ -118,7 +115,8 @@ struct typec_altmode
                             const struct typec_altmode_desc *desc);
 struct typec_altmode
 *typec_port_register_altmode(struct typec_port *port,
-                            const struct typec_altmode_desc *desc);
+                           const struct typec_altmode_desc *desc);
+
 void typec_unregister_altmode(struct typec_altmode *altmode);
 
 struct typec_port *typec_altmode2port(struct typec_altmode *alt);
@@ -213,12 +211,10 @@ struct typec_capability {
                                  enum typec_role);
        int             (*vconn_set)(const struct typec_capability *,
                                     enum typec_role);
-
-       int             (*activate_mode)(const struct typec_capability *,
-                                        int mode, int activate);
        int             (*port_type_set)(const struct typec_capability *,
-                                       enum typec_port_type);
+                                        enum typec_port_type);
 
+       int             (*activate_mode)(struct typec_altmode *alt, int active);
 };
 
 /* Specific to try_role(). Indicates the user want's to clear the preference. 
*/
diff --git a/include/linux/usb/typec_altmode.h 
b/include/linux/usb/typec_altmode.h
new file mode 100644
index 000000000000..bc765352a3c8
--- /dev/null
+++ b/include/linux/usb/typec_altmode.h
@@ -0,0 +1,136 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef __USB_TYPEC_ALTMODE_H
+#define __USB_TYPEC_ALTMODE_H
+
+#include <linux/device.h>
+#include <linux/mod_devicetable.h>
+
+#define MODE_DISCOVERY_MAX     6
+
+/**
+ * struct typec_altmode - USB Type-C alternate mode device
+ * @dev: Driver model's view of this device
+ * @svid: Standard or Vendor ID (SVID) of the alternate mode
+ * @mode: Index of the Mode
+ * @vdo: VDO returned by Discover Modes USB PD command
+ * @desc: Optional human readable description of the mode
+ * @active: Tells has the mode been entered or not
+ */
+struct typec_altmode {
+       struct device           dev;
+       u16                     svid;
+       int                     mode;
+       u32                     vdo;
+       char                    *desc;
+       bool                    active;
+} __packed;
+
+#define to_typec_altmode(d) container_of(d, struct typec_altmode, dev)
+
+static inline void typec_altmode_set_drvdata(struct typec_altmode *altmode,
+                                            void *data)
+{
+       dev_set_drvdata(&altmode->dev, data);
+}
+
+static inline void *typec_altmode_get_drvdata(struct typec_altmode *altmode)
+{
+       return dev_get_drvdata(&altmode->dev);
+}
+
+/**
+ * struct typec_altmode_ops - Alternate mode specific operations vector
+ * @enter: Operations to be executed with Enter Mode Command
+ * @exit: Operations to be executed with Exit Mode Command
+ * @attention: Callback for Attention Command
+ * @vdm: Callback for SVID specific commands
+ * @notify: Communication channel for platform and the alternate mode
+ */
+struct typec_altmode_ops {
+       void (*enter)(struct typec_altmode *altmode);
+       void (*exit)(struct typec_altmode *altmode);
+       void (*attention)(struct typec_altmode *altmode, const u32 vdo);
+       int (*vdm)(struct typec_altmode *altmode, const u32 hdr,
+                  const u32 *vdo, int cnt);
+       int (*notify)(struct typec_altmode *altmode, unsigned long conf,
+                     void *data);
+};
+
+void typec_altmode_register_ops(struct typec_altmode *altmode,
+                               const struct typec_altmode_ops *ops);
+
+int typec_altmode_enter(struct typec_altmode *altmode);
+int typec_altmode_exit(struct typec_altmode *altmode);
+void typec_altmode_attention(struct typec_altmode *altmode, const u32 vdo);
+int typec_altmode_vdm(struct typec_altmode *altmode,
+                     const u32 header, const u32 *vdo, int count);
+int typec_altmode_notify(struct typec_altmode *altmode, unsigned long conf,
+                        void *data);
+
+/* Return values for type_altmode_vdm() */
+#define VDM_DONE               0 /* Don't care */
+#define VDM_OK                 1 /* Suits me */
+
+/*
+ * These are the pin states (USB, Safe and Alt Mode) and accessory modes (Audio
+ * and Debug) defined in USB Type-C Specification. SVID specific pin states are
+ * expected to follow and start from the value TYPEC_STATE_MODAL.
+ *
+ * Port drivers should use TYPEC_STATE_AUDIO and TYPEC_STATE_DEBUG as the
+ * operation value for typec_set_mode() when accessory modes are in use.
+ *
+ * NOTE: typec_altmode_notify() does not accept values smaller then
+ * TYPEC_STATE_MODAL. USB Type-C bus will follow USB Type-C Specification with
+ * TYPEC_STATE_USB and TYPEC_STATE_SAFE.
+ */
+enum {
+       TYPEC_STATE_USB,        /* USB Operation */
+       TYPEC_STATE_AUDIO,      /* Audio Accessory */
+       TYPEC_STATE_DEBUG,      /* Debug Accessory */
+       TYPEC_STATE_SAFE,       /* USB Safe State */
+       TYPEC_STATE_MODAL,      /* Alternate Modes */
+};
+
+#define TYPEC_MODAL_STATE(_state_)     ((_state_) + TYPEC_STATE_MODAL)
+
+struct typec_altmode *typec_altmode_get_plug(struct typec_altmode *altmode,
+                                            int index);
+void typec_altmode_put_plug(struct typec_altmode *plug);
+
+bool typec_altmode_ufp_capable(struct typec_altmode *altmode);
+bool typec_altmode_dfp_capable(struct typec_altmode *altmode);
+struct typec_altmode *typec_match_altmode(struct typec_altmode **altmodes,
+                                         size_t n, u16 svid, u8 mode);
+
+/**
+ * struct typec_altmode_driver - USB Type-C alternate mode device driver
+ * @id_table: Null terminated array of SVIDs
+ * @probe: Callback for device binding
+ * @remove: Callback for device unbinding
+ * @driver: Device driver model driver
+ *
+ * These drivers will be bind to the partner alternate mode devices. They will
+ * handle all SVID specific communication.
+ */
+struct typec_altmode_driver {
+       const struct typec_device_id *id_table;
+       int (*probe)(struct typec_altmode *altmode, u32 port_vdo);
+       void (*remove)(struct typec_altmode *altmode);
+       struct device_driver driver;
+};
+
+#define to_altmode_driver(d) container_of(d, struct typec_altmode_driver, \
+                                         driver)
+
+#define typec_altmode_register_driver(drv) \
+               __typec_altmode_register_driver(drv, THIS_MODULE)
+int __typec_altmode_register_driver(struct typec_altmode_driver *drv,
+                                   struct module *module);
+void typec_altmode_unregister_driver(struct typec_altmode_driver *drv);
+
+#define module_typec_altmode_driver(__typec_altmode_driver) \
+       module_driver(__typec_altmode_driver, typec_altmode_register_driver, \
+                     typec_altmode_unregister_driver)
+
+#endif /* __USB_TYPEC_ALTMODE_H */
diff --git a/scripts/mod/devicetable-offsets.c 
b/scripts/mod/devicetable-offsets.c
index 9fad6afe4c41..c48c7f56ae64 100644
--- a/scripts/mod/devicetable-offsets.c
+++ b/scripts/mod/devicetable-offsets.c
@@ -218,5 +218,9 @@ int main(void)
        DEVID_FIELD(tb_service_id, protocol_version);
        DEVID_FIELD(tb_service_id, protocol_revision);
 
+       DEVID(typec_device_id);
+       DEVID_FIELD(typec_device_id, svid);
+       DEVID_FIELD(typec_device_id, mode);
+
        return 0;
 }
diff --git a/scripts/mod/file2alias.c b/scripts/mod/file2alias.c
index b9beeaa4695b..a8afba836409 100644
--- a/scripts/mod/file2alias.c
+++ b/scripts/mod/file2alias.c
@@ -1341,6 +1341,19 @@ static int do_tbsvc_entry(const char *filename, void 
*symval, char *alias)
 }
 ADD_TO_DEVTABLE("tbsvc", tb_service_id, do_tbsvc_entry);
 
+/* Looks like: typec:idNmN */
+static int do_typec_entry(const char *filename, void *symval, char *alias)
+{
+       DEF_FIELD(symval, typec_device_id, svid);
+       DEF_FIELD(symval, typec_device_id, mode);
+
+       sprintf(alias, "typec:id%04X", svid);
+       ADD(alias, "m", mode != TYPEC_ANY_MODE, mode);
+
+       return 1;
+}
+ADD_TO_DEVTABLE("typec", typec_device_id, do_typec_entry);
+
 /* Does namelen bytes of name exactly match the symbol? */
 static bool sym_is(const char *name, unsigned namelen, const char *symbol)
 {
-- 
2.16.1

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to