On Fri, Mar 09, 2018 at 06:19:17PM +0300, Heikki Krogerus wrote: > Introducing a simple bus for the alternate modes. Bus allows > binding drivers to the discovered alternate modes the > partners support. > > Signed-off-by: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx> > --- > 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.krogerus@xxxxxxxxxxxxxxx> > +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.krogerus@xxxxxxxxxxxxxxx> > +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.krogerus@xxxxxxxxxxxxxxx> > +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.krogerus@xxxxxxxxxxxxxxx> > +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.krogerus@xxxxxxxxxxxxxxx> > +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.krogerus@xxxxxxxxxxxxxxx> > +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.krogerus@xxxxxxxxxxxxxxx> > +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.krogerus@xxxxxxxxxxxxxxx> > +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.krogerus@xxxxxxxxxxxxxxx> > +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.krogerus@xxxxxxxxxxxxxxx> > +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.krogerus@xxxxxxxxxxxxxxx> > -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.krogerus@xxxxxxxxxxxxxxx> > -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.krogerus@xxxxxxxxxxxxxxx> > -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.krogerus@xxxxxxxxxxxxxxx> > -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.krogerus@xxxxxxxxxxxxxxx> > -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.krogerus@xxxxxxxxxxxxxxx> > 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.krogerus@xxxxxxxxxxxxxxx> > + */ > + > +#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 typec_altmode_exit > + * @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 typec_altmode_put_plug > + * @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.krogerus@xxxxxxxxxxxxxxx> > */ > > +#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 majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html