usb: typec: Bus type for alternate modes
Introducing a simple bus for the alternate modes. Bus allows binding drivers to the discovered alternate modes the partners support. Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com> Tested-by: Hans de Goede <hdegoede@redhat.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
4ab8c18d4d
commit
8a37d87d72
48
Documentation/ABI/obsolete/sysfs-class-typec
Normal file
48
Documentation/ABI/obsolete/sysfs-class-typec
Normal file
|
@ -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@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.krogerus@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.krogerus@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.krogerus@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.krogerus@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
|
51
Documentation/ABI/testing/sysfs-bus-typec
Normal file
51
Documentation/ABI/testing/sysfs-bus-typec
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
What: /sys/bus/typec/devices/.../active
|
||||||
|
Date: July 2018
|
||||||
|
Contact: Heikki Krogerus <heikki.krogerus@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: July 2018
|
||||||
|
Contact: Heikki Krogerus <heikki.krogerus@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: July 2018
|
||||||
|
Contact: Heikki Krogerus <heikki.krogerus@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: July 2018
|
||||||
|
Contact: Heikki Krogerus <heikki.krogerus@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: July 2018
|
||||||
|
Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
|
||||||
|
Description:
|
||||||
|
Shows the VDO in hexadecimal returned by Discover Modes command
|
||||||
|
for this mode.
|
|
@ -222,70 +222,12 @@ Description:
|
||||||
available. The value can be polled.
|
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.
|
What: /sys/class/typec/<port>/<alt mode>/supported_roles
|
||||||
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@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.krogerus@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.krogerus@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.krogerus@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.krogerus@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
|
|
||||||
Date: April 2017
|
Date: April 2017
|
||||||
Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
|
Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
|
||||||
Description:
|
Description:
|
||||||
Space separated list of the supported roles.
|
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
|
Valid values: source, sink
|
||||||
|
|
136
Documentation/driver-api/usb/typec_bus.rst
Normal file
136
Documentation/driver-api/usb/typec_bus.rst
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
|
||||||
|
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 a 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 bound 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.
|
||||||
|
|
||||||
|
``->vdm`` is the most important callback in the operation callbacks vector. It
|
||||||
|
will be used to deliver all the SVID specific commands from the partner to the
|
||||||
|
alternate mode driver, and vice 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 reconfigure 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``. These values are
|
||||||
|
reserved by the bus as the first possible values for the state. 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 bound 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 take 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_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
|
11
MAINTAINERS
11
MAINTAINERS
|
@ -14955,7 +14955,7 @@ L: linux-usb@vger.kernel.org
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: drivers/usb/typec/mux/pi3usb30532.c
|
F: drivers/usb/typec/mux/pi3usb30532.c
|
||||||
|
|
||||||
USB TYPEC SUBSYSTEM
|
USB TYPEC CLASS
|
||||||
M: Heikki Krogerus <heikki.krogerus@linux.intel.com>
|
M: Heikki Krogerus <heikki.krogerus@linux.intel.com>
|
||||||
L: linux-usb@vger.kernel.org
|
L: linux-usb@vger.kernel.org
|
||||||
S: Maintained
|
S: Maintained
|
||||||
|
@ -14964,6 +14964,15 @@ F: Documentation/driver-api/usb/typec.rst
|
||||||
F: drivers/usb/typec/
|
F: drivers/usb/typec/
|
||||||
F: include/linux/usb/typec.h
|
F: include/linux/usb/typec.h
|
||||||
|
|
||||||
|
USB TYPEC BUS FOR ALTERNATE MODES
|
||||||
|
M: Heikki Krogerus <heikki.krogerus@linux.intel.com>
|
||||||
|
L: linux-usb@vger.kernel.org
|
||||||
|
S: Maintained
|
||||||
|
F: Documentation/ABI/testing/sysfs-bus-typec
|
||||||
|
F: Documentation/driver-api/usb/typec_bus.rst
|
||||||
|
F: drivers/usb/typec/altmodes/
|
||||||
|
F: include/linux/usb/typec_altmode.h
|
||||||
|
|
||||||
USB UHCI DRIVER
|
USB UHCI DRIVER
|
||||||
M: Alan Stern <stern@rowland.harvard.edu>
|
M: Alan Stern <stern@rowland.harvard.edu>
|
||||||
L: linux-usb@vger.kernel.org
|
L: linux-usb@vger.kernel.org
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# SPDX-License-Identifier: GPL-2.0
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
obj-$(CONFIG_TYPEC) += typec.o
|
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-$(CONFIG_TYPEC_TCPM) += tcpm.o
|
||||||
obj-y += fusb302/
|
obj-y += fusb302/
|
||||||
obj-$(CONFIG_TYPEC_WCOVE) += typec_wcove.o
|
obj-$(CONFIG_TYPEC_WCOVE) += typec_wcove.o
|
||||||
|
|
401
drivers/usb/typec/bus.c
Normal file
401
drivers/usb/typec/bus.c
Normal file
|
@ -0,0 +1,401 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/**
|
||||||
|
* Bus for USB Type-C Alternate Modes
|
||||||
|
*
|
||||||
|
* Copyright (C) 2018 Intel Corporation
|
||||||
|
* Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/usb/pd_vdo.h>
|
||||||
|
|
||||||
|
#include "bus.h"
|
||||||
|
|
||||||
|
static inline int typec_altmode_set_mux(struct altmode *alt, u8 state)
|
||||||
|
{
|
||||||
|
return alt->mux ? alt->mux->set(alt->mux, state) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int typec_altmode_set_state(struct typec_altmode *adev, int state)
|
||||||
|
{
|
||||||
|
bool is_port = is_typec_port(adev->dev.parent);
|
||||||
|
struct altmode *port_altmode;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
port_altmode = is_port ? to_altmode(adev) : to_altmode(adev)->partner;
|
||||||
|
|
||||||
|
ret = typec_altmode_set_mux(port_altmode, state);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
blocking_notifier_call_chain(&port_altmode->nh, state, NULL);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* 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)
|
||||||
|
{
|
||||||
|
bool is_port = is_typec_port(adev->dev.parent);
|
||||||
|
struct altmode *altmode;
|
||||||
|
struct altmode *partner;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (!adev)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
altmode = to_altmode(adev);
|
||||||
|
|
||||||
|
if (!altmode->partner)
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
partner = altmode->partner;
|
||||||
|
|
||||||
|
ret = typec_altmode_set_mux(is_port ? altmode : partner, (u8)conf);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
blocking_notifier_call_chain(is_port ? &altmode->nh : &partner->nh,
|
||||||
|
conf, data);
|
||||||
|
|
||||||
|
if (partner->adev.ops && partner->adev.ops->notify)
|
||||||
|
return partner->adev.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 the partner has initiated
|
||||||
|
* Enter Mode command.
|
||||||
|
*/
|
||||||
|
int typec_altmode_enter(struct typec_altmode *adev)
|
||||||
|
{
|
||||||
|
struct altmode *partner = to_altmode(adev)->partner;
|
||||||
|
struct typec_altmode *pdev = &partner->adev;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (!adev || adev->active)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (!pdev->ops || !pdev->ops->enter)
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
|
||||||
|
/* Moving to USB Safe State */
|
||||||
|
ret = typec_altmode_set_state(adev, TYPEC_STATE_SAFE);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
/* Enter Mode */
|
||||||
|
return pdev->ops->enter(pdev);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(typec_altmode_enter);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* typec_altmode_exit - Exit Mode
|
||||||
|
* @adev: The alternate mode
|
||||||
|
*
|
||||||
|
* The partner of @adev has initiated Exit Mode command.
|
||||||
|
*/
|
||||||
|
int typec_altmode_exit(struct typec_altmode *adev)
|
||||||
|
{
|
||||||
|
struct altmode *partner = to_altmode(adev)->partner;
|
||||||
|
struct typec_altmode *pdev = &partner->adev;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (!adev || !adev->active)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (!pdev->ops || !pdev->ops->enter)
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
|
||||||
|
/* Moving to USB Safe State */
|
||||||
|
ret = typec_altmode_set_state(adev, TYPEC_STATE_SAFE);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
/* Exit Mode command */
|
||||||
|
return pdev->ops->exit(pdev);
|
||||||
|
}
|
||||||
|
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, u32 vdo)
|
||||||
|
{
|
||||||
|
struct typec_altmode *pdev = &to_altmode(adev)->partner->adev;
|
||||||
|
|
||||||
|
if (pdev->ops && pdev->ops->attention)
|
||||||
|
pdev->ops->attention(pdev, 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 typec_altmode *pdev;
|
||||||
|
struct altmode *altmode;
|
||||||
|
|
||||||
|
if (!adev)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
altmode = to_altmode(adev);
|
||||||
|
|
||||||
|
if (!altmode->partner)
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
pdev = &altmode->partner->adev;
|
||||||
|
|
||||||
|
if (!pdev->ops || !pdev->ops->vdm)
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
|
||||||
|
return pdev->ops->vdm(pdev, header, vdo, count);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(typec_altmode_vdm);
|
||||||
|
|
||||||
|
const struct typec_altmode *
|
||||||
|
typec_altmode_get_partner(struct typec_altmode *adev)
|
||||||
|
{
|
||||||
|
return &to_altmode(adev)->partner->adev;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(typec_altmode_get_partner);
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* 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,
|
||||||
|
enum typec_plug_index 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_put_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 */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 void typec_altmode_remove_links(struct altmode *alt)
|
||||||
|
{
|
||||||
|
sysfs_remove_link(&alt->partner->adev.dev.kobj, "partner");
|
||||||
|
sysfs_remove_link(&alt->adev.dev.kobj, "port");
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/* Fail if the port does not support the alternate mode */
|
||||||
|
if (!altmode->partner)
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
ret = typec_altmode_create_links(altmode);
|
||||||
|
if (ret) {
|
||||||
|
dev_warn(dev, "failed to create symlinks\n");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = drv->probe(adev);
|
||||||
|
if (ret)
|
||||||
|
typec_altmode_remove_links(altmode);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
typec_altmode_remove_links(altmode);
|
||||||
|
|
||||||
|
if (drv->remove)
|
||||||
|
drv->remove(to_typec_altmode(dev));
|
||||||
|
|
||||||
|
if (adev->active) {
|
||||||
|
WARN_ON(typec_altmode_set_state(adev, TYPEC_STATE_SAFE));
|
||||||
|
typec_altmode_update_active(adev, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
adev->desc = NULL;
|
||||||
|
adev->ops = NULL;
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
38
drivers/usb/typec/bus.h
Normal file
38
drivers/usb/typec/bus.h
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
/* SPDX-License-Identifier: GPL-2.0 */
|
||||||
|
|
||||||
|
#ifndef __USB_TYPEC_ALTMODE_H__
|
||||||
|
#define __USB_TYPEC_ALTMODE_H__
|
||||||
|
|
||||||
|
#include <linux/usb/typec_altmode.h>
|
||||||
|
#include <linux/usb/typec_mux.h>
|
||||||
|
|
||||||
|
struct bus_type;
|
||||||
|
|
||||||
|
struct altmode {
|
||||||
|
unsigned int id;
|
||||||
|
struct typec_altmode adev;
|
||||||
|
struct typec_mux *mux;
|
||||||
|
|
||||||
|
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];
|
||||||
|
|
||||||
|
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__ */
|
|
@ -10,28 +10,13 @@
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
#include <linux/mutex.h>
|
#include <linux/mutex.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
#include <linux/usb/typec.h>
|
|
||||||
#include <linux/usb/typec_mux.h>
|
|
||||||
|
|
||||||
struct typec_altmode {
|
#include "bus.h"
|
||||||
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];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct typec_plug {
|
struct typec_plug {
|
||||||
struct device dev;
|
struct device dev;
|
||||||
enum typec_plug_index index;
|
enum typec_plug_index index;
|
||||||
|
struct ida mode_ids;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct typec_cable {
|
struct typec_cable {
|
||||||
|
@ -46,11 +31,13 @@ struct typec_partner {
|
||||||
unsigned int usb_pd:1;
|
unsigned int usb_pd:1;
|
||||||
struct usb_pd_identity *identity;
|
struct usb_pd_identity *identity;
|
||||||
enum typec_accessory accessory;
|
enum typec_accessory accessory;
|
||||||
|
struct ida mode_ids;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct typec_port {
|
struct typec_port {
|
||||||
unsigned int id;
|
unsigned int id;
|
||||||
struct device dev;
|
struct device dev;
|
||||||
|
struct ida mode_ids;
|
||||||
|
|
||||||
int prefer_role;
|
int prefer_role;
|
||||||
enum typec_data_role data_role;
|
enum typec_data_role data_role;
|
||||||
|
@ -71,17 +58,14 @@ struct typec_port {
|
||||||
#define to_typec_plug(_dev_) container_of(_dev_, struct typec_plug, dev)
|
#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_cable(_dev_) container_of(_dev_, struct typec_cable, dev)
|
||||||
#define to_typec_partner(_dev_) container_of(_dev_, struct typec_partner, 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_partner_dev_type;
|
||||||
static const struct device_type typec_cable_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_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_partner(_dev_) (_dev_->type == &typec_partner_dev_type)
|
||||||
#define is_typec_cable(_dev_) (_dev_->type == &typec_cable_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_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 DEFINE_IDA(typec_index_ida);
|
||||||
static struct class *typec_class;
|
static struct class *typec_class;
|
||||||
|
@ -163,25 +147,148 @@ static void typec_report_identity(struct device *dev)
|
||||||
/* ------------------------------------------------------------------------- */
|
/* ------------------------------------------------------------------------- */
|
||||||
/* Alternate Modes */
|
/* 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_set_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 device_connection *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_connection_find_match(dev, NULL, NULL,
|
||||||
|
typec_port_match);
|
||||||
|
if (IS_ERR_OR_NULL(port_dev))
|
||||||
|
return port_dev ? ERR_CAST(port_dev) : 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
|
* typec_altmode_update_active - Report Enter/Exit mode
|
||||||
* @alt: Handle to the alternate mode
|
* @adev: Handle to the alternate mode
|
||||||
* @active: True when the mode has been entered
|
* @active: True when the mode has been entered
|
||||||
*
|
*
|
||||||
* If a partner or cable plug executes Enter/Exit Mode command successfully, the
|
* 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.
|
* drivers use this routine to report the updated state of the mode.
|
||||||
*/
|
*/
|
||||||
void typec_altmode_update_active(struct typec_altmode *alt, bool active)
|
void typec_altmode_update_active(struct typec_altmode *adev, bool active)
|
||||||
{
|
{
|
||||||
char dir[6];
|
char dir[6];
|
||||||
|
|
||||||
if (alt->active == active)
|
if (adev->active == active)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
alt->active = active;
|
if (!is_typec_port(adev->dev.parent)) {
|
||||||
snprintf(dir, sizeof(dir), "mode%d", alt->mode);
|
if (!active)
|
||||||
sysfs_notify(&alt->dev.kobj, dir, "active");
|
module_put(adev->dev.driver->owner);
|
||||||
kobject_uevent(&alt->dev.kobj, KOBJ_CHANGE);
|
else
|
||||||
|
WARN_ON(!try_module_get(adev->dev.driver->owner));
|
||||||
|
}
|
||||||
|
|
||||||
|
adev->active = active;
|
||||||
|
snprintf(dir, sizeof(dir), "mode%d", adev->mode);
|
||||||
|
sysfs_notify(&adev->dev.kobj, dir, "active");
|
||||||
|
sysfs_notify(&adev->dev.kobj, NULL, "active");
|
||||||
|
kobject_uevent(&adev->dev.kobj, KOBJ_CHANGE);
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(typec_altmode_update_active);
|
EXPORT_SYMBOL_GPL(typec_altmode_update_active);
|
||||||
|
|
||||||
|
@ -208,7 +315,7 @@ EXPORT_SYMBOL_GPL(typec_altmode2port);
|
||||||
static ssize_t
|
static ssize_t
|
||||||
vdo_show(struct device *dev, struct device_attribute *attr, char *buf)
|
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);
|
return sprintf(buf, "0x%08x\n", alt->vdo);
|
||||||
}
|
}
|
||||||
|
@ -217,7 +324,7 @@ static DEVICE_ATTR_RO(vdo);
|
||||||
static ssize_t
|
static ssize_t
|
||||||
description_show(struct device *dev, struct device_attribute *attr, char *buf)
|
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 : "");
|
return sprintf(buf, "%s\n", alt->desc ? alt->desc : "");
|
||||||
}
|
}
|
||||||
|
@ -226,7 +333,7 @@ static DEVICE_ATTR_RO(description);
|
||||||
static ssize_t
|
static ssize_t
|
||||||
active_show(struct device *dev, struct device_attribute *attr, char *buf)
|
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");
|
return sprintf(buf, "%s\n", alt->active ? "yes" : "no");
|
||||||
}
|
}
|
||||||
|
@ -234,21 +341,37 @@ active_show(struct device *dev, struct device_attribute *attr, char *buf)
|
||||||
static ssize_t active_store(struct device *dev, struct device_attribute *attr,
|
static ssize_t active_store(struct device *dev, struct device_attribute *attr,
|
||||||
const char *buf, size_t size)
|
const char *buf, size_t size)
|
||||||
{
|
{
|
||||||
struct typec_altmode *alt = to_altmode(dev);
|
struct typec_altmode *adev = to_typec_altmode(dev);
|
||||||
struct typec_port *port = typec_altmode2port(alt);
|
struct altmode *altmode = to_altmode(adev);
|
||||||
bool activate;
|
bool enter;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
if (!port->cap->activate_mode)
|
ret = kstrtobool(buf, &enter);
|
||||||
return -EOPNOTSUPP;
|
|
||||||
|
|
||||||
ret = kstrtobool(buf, &activate);
|
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
ret = port->cap->activate_mode(port->cap, alt->mode, activate);
|
if (adev->active == enter)
|
||||||
if (ret)
|
return size;
|
||||||
return ret;
|
|
||||||
|
if (is_typec_port(adev->dev.parent)) {
|
||||||
|
typec_altmode_update_active(adev, enter);
|
||||||
|
|
||||||
|
/* Make sure that the partner exits the mode before disabling */
|
||||||
|
if (altmode->partner && !enter && altmode->partner->adev.active)
|
||||||
|
typec_altmode_exit(&altmode->partner->adev);
|
||||||
|
} else if (altmode->partner) {
|
||||||
|
if (enter && !altmode->partner->adev.active) {
|
||||||
|
dev_warn(dev, "port has the mode disabled\n");
|
||||||
|
return -EPERM;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Note: If there is no driver, the mode will not be entered */
|
||||||
|
if (adev->ops && adev->ops->activate) {
|
||||||
|
ret = adev->ops->activate(adev, enter);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
@ -258,7 +381,7 @@ static ssize_t
|
||||||
supported_roles_show(struct device *dev, struct device_attribute *attr,
|
supported_roles_show(struct device *dev, struct device_attribute *attr,
|
||||||
char *buf)
|
char *buf)
|
||||||
{
|
{
|
||||||
struct typec_altmode *alt = to_altmode(dev);
|
struct altmode *alt = to_altmode(to_typec_altmode(dev));
|
||||||
ssize_t ret;
|
ssize_t ret;
|
||||||
|
|
||||||
switch (alt->roles) {
|
switch (alt->roles) {
|
||||||
|
@ -277,29 +400,72 @@ supported_roles_show(struct device *dev, struct device_attribute *attr,
|
||||||
}
|
}
|
||||||
static DEVICE_ATTR_RO(supported_roles);
|
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,
|
static ssize_t
|
||||||
char *buf)
|
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 DEVICE_ATTR_RO(svid);
|
||||||
|
|
||||||
static struct attribute *typec_altmode_attrs[] = {
|
static struct attribute *typec_altmode_attrs[] = {
|
||||||
|
&dev_attr_active.attr,
|
||||||
|
&dev_attr_mode.attr,
|
||||||
&dev_attr_svid.attr,
|
&dev_attr_svid.attr,
|
||||||
|
&dev_attr_vdo.attr,
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
ATTRIBUTE_GROUPS(typec_altmode);
|
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",
|
.name = "typec_alternate_mode",
|
||||||
.groups = typec_altmode_groups,
|
.groups = typec_altmode_groups,
|
||||||
.release = typec_altmode_release,
|
.release = typec_altmode_release,
|
||||||
|
@ -309,58 +475,74 @@ static struct typec_altmode *
|
||||||
typec_register_altmode(struct device *parent,
|
typec_register_altmode(struct device *parent,
|
||||||
const struct typec_altmode_desc *desc)
|
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;
|
int ret;
|
||||||
|
|
||||||
alt = kzalloc(sizeof(*alt), GFP_KERNEL);
|
alt = kzalloc(sizeof(*alt), GFP_KERNEL);
|
||||||
if (!alt)
|
if (!alt)
|
||||||
return ERR_PTR(-ENOMEM);
|
return ERR_PTR(-ENOMEM);
|
||||||
|
|
||||||
alt->svid = desc->svid;
|
alt->adev.svid = desc->svid;
|
||||||
alt->mode = desc->mode;
|
alt->adev.mode = desc->mode;
|
||||||
alt->vdo = desc->vdo;
|
alt->adev.vdo = desc->vdo;
|
||||||
alt->roles = desc->roles;
|
alt->roles = desc->roles;
|
||||||
|
alt->id = id;
|
||||||
|
|
||||||
alt->attrs[0] = &dev_attr_vdo.attr;
|
alt->attrs[0] = &dev_attr_vdo.attr;
|
||||||
alt->attrs[1] = &dev_attr_description.attr;
|
alt->attrs[1] = &dev_attr_description.attr;
|
||||||
alt->attrs[2] = &dev_attr_active.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->attrs[3] = &dev_attr_supported_roles.attr;
|
||||||
|
alt->adev.active = true; /* Enabled by default */
|
||||||
|
}
|
||||||
|
|
||||||
sprintf(alt->group_name, "mode%d", desc->mode);
|
sprintf(alt->group_name, "mode%d", desc->mode);
|
||||||
alt->group.name = alt->group_name;
|
alt->group.name = alt->group_name;
|
||||||
alt->group.attrs = alt->attrs;
|
alt->group.attrs = alt->attrs;
|
||||||
alt->groups[0] = &alt->group;
|
alt->groups[0] = &alt->group;
|
||||||
|
|
||||||
alt->dev.parent = parent;
|
alt->adev.dev.parent = parent;
|
||||||
alt->dev.groups = alt->groups;
|
alt->adev.dev.groups = alt->groups;
|
||||||
alt->dev.type = &typec_altmode_dev_type;
|
alt->adev.dev.type = &typec_altmode_dev_type;
|
||||||
dev_set_name(&alt->dev, "%s-%04x:%u", dev_name(parent),
|
dev_set_name(&alt->adev.dev, "%s.%u", dev_name(parent), id);
|
||||||
alt->svid, alt->mode);
|
|
||||||
|
|
||||||
ret = device_register(&alt->dev);
|
/* Link partners and plugs with the ports */
|
||||||
|
if (is_port)
|
||||||
|
BLOCKING_INIT_NOTIFIER_HEAD(&alt->nh);
|
||||||
|
else
|
||||||
|
typec_altmode_set_partner(alt);
|
||||||
|
|
||||||
|
/* 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) {
|
if (ret) {
|
||||||
dev_err(parent, "failed to register alternate mode (%d)\n",
|
dev_err(parent, "failed to register alternate mode (%d)\n",
|
||||||
ret);
|
ret);
|
||||||
put_device(&alt->dev);
|
put_device(&alt->adev.dev);
|
||||||
return ERR_PTR(ret);
|
return ERR_PTR(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
return alt;
|
return &alt->adev;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* typec_unregister_altmode - Unregister Alternate Mode
|
* 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(),
|
* Unregister device created with typec_partner_register_altmode(),
|
||||||
* typec_plug_register_altmode() or typec_port_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))
|
if (IS_ERR_OR_NULL(adev))
|
||||||
device_unregister(&alt->dev);
|
return;
|
||||||
|
typec_mux_put(to_altmode(adev)->mux);
|
||||||
|
device_unregister(&adev->dev);
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(typec_unregister_altmode);
|
EXPORT_SYMBOL_GPL(typec_unregister_altmode);
|
||||||
|
|
||||||
|
@ -398,6 +580,7 @@ static void typec_partner_release(struct device *dev)
|
||||||
{
|
{
|
||||||
struct typec_partner *partner = to_typec_partner(dev);
|
struct typec_partner *partner = to_typec_partner(dev);
|
||||||
|
|
||||||
|
ida_destroy(&partner->mode_ids);
|
||||||
kfree(partner);
|
kfree(partner);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -463,6 +646,7 @@ struct typec_partner *typec_register_partner(struct typec_port *port,
|
||||||
if (!partner)
|
if (!partner)
|
||||||
return ERR_PTR(-ENOMEM);
|
return ERR_PTR(-ENOMEM);
|
||||||
|
|
||||||
|
ida_init(&partner->mode_ids);
|
||||||
partner->usb_pd = desc->usb_pd;
|
partner->usb_pd = desc->usb_pd;
|
||||||
partner->accessory = desc->accessory;
|
partner->accessory = desc->accessory;
|
||||||
|
|
||||||
|
@ -511,6 +695,7 @@ static void typec_plug_release(struct device *dev)
|
||||||
{
|
{
|
||||||
struct typec_plug *plug = to_typec_plug(dev);
|
struct typec_plug *plug = to_typec_plug(dev);
|
||||||
|
|
||||||
|
ida_destroy(&plug->mode_ids);
|
||||||
kfree(plug);
|
kfree(plug);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -563,6 +748,7 @@ struct typec_plug *typec_register_plug(struct typec_cable *cable,
|
||||||
|
|
||||||
sprintf(name, "plug%d", desc->index);
|
sprintf(name, "plug%d", desc->index);
|
||||||
|
|
||||||
|
ida_init(&plug->mode_ids);
|
||||||
plug->index = desc->index;
|
plug->index = desc->index;
|
||||||
plug->dev.class = typec_class;
|
plug->dev.class = typec_class;
|
||||||
plug->dev.parent = &cable->dev;
|
plug->dev.parent = &cable->dev;
|
||||||
|
@ -1083,12 +1269,13 @@ static void typec_release(struct device *dev)
|
||||||
struct typec_port *port = to_typec_port(dev);
|
struct typec_port *port = to_typec_port(dev);
|
||||||
|
|
||||||
ida_simple_remove(&typec_index_ida, port->id);
|
ida_simple_remove(&typec_index_ida, port->id);
|
||||||
|
ida_destroy(&port->mode_ids);
|
||||||
typec_switch_put(port->sw);
|
typec_switch_put(port->sw);
|
||||||
typec_mux_put(port->mux);
|
typec_mux_put(port->mux);
|
||||||
kfree(port);
|
kfree(port);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct device_type typec_port_dev_type = {
|
const struct device_type typec_port_dev_type = {
|
||||||
.name = "typec_port",
|
.name = "typec_port",
|
||||||
.groups = typec_groups,
|
.groups = typec_groups,
|
||||||
.uevent = typec_uevent,
|
.uevent = typec_uevent,
|
||||||
|
@ -1279,11 +1466,11 @@ EXPORT_SYMBOL_GPL(typec_get_orientation);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* typec_set_mode - Set mode of operation for USB Type-C connector
|
* typec_set_mode - Set mode of operation for USB Type-C connector
|
||||||
* @port: USB Type-C port for the connector
|
* @port: USB Type-C connector
|
||||||
* @mode: Operation mode for the connector
|
* @mode: Accessory Mode, USB Operation or Safe State
|
||||||
*
|
*
|
||||||
* Set mode @mode for @port. This function will configure the muxes needed to
|
* Configure @port for Accessory Mode @mode. This function will configure the
|
||||||
* enter @mode.
|
* muxes needed for @mode.
|
||||||
*/
|
*/
|
||||||
int typec_set_mode(struct typec_port *port, int mode)
|
int typec_set_mode(struct typec_port *port, int mode)
|
||||||
{
|
{
|
||||||
|
@ -1297,6 +1484,7 @@ EXPORT_SYMBOL_GPL(typec_set_mode);
|
||||||
* typec_port_register_altmode - Register USB Type-C Port Alternate Mode
|
* typec_port_register_altmode - Register USB Type-C Port Alternate Mode
|
||||||
* @port: USB Type-C Port that supports the alternate mode
|
* @port: USB Type-C Port that supports the alternate mode
|
||||||
* @desc: Description of the alternate mode
|
* @desc: Description of the alternate mode
|
||||||
|
* @drvdata: Private pointer to driver specific info
|
||||||
*
|
*
|
||||||
* This routine is used to register an alternate mode that @port is capable of
|
* This routine is used to register an alternate mode that @port is capable of
|
||||||
* supporting.
|
* supporting.
|
||||||
|
@ -1307,7 +1495,23 @@ struct typec_altmode *
|
||||||
typec_port_register_altmode(struct typec_port *port,
|
typec_port_register_altmode(struct typec_port *port,
|
||||||
const struct typec_altmode_desc *desc)
|
const struct typec_altmode_desc *desc)
|
||||||
{
|
{
|
||||||
return typec_register_altmode(&port->dev, desc);
|
struct typec_altmode *adev;
|
||||||
|
struct typec_mux *mux;
|
||||||
|
char id[10];
|
||||||
|
|
||||||
|
sprintf(id, "id%04xm%02x", desc->svid, desc->mode);
|
||||||
|
|
||||||
|
mux = typec_mux_get(port->dev.parent, id);
|
||||||
|
if (IS_ERR(mux))
|
||||||
|
return ERR_CAST(mux);
|
||||||
|
|
||||||
|
adev = typec_register_altmode(&port->dev, desc);
|
||||||
|
if (IS_ERR(adev))
|
||||||
|
typec_mux_put(mux);
|
||||||
|
else
|
||||||
|
to_altmode(adev)->mux = mux;
|
||||||
|
|
||||||
|
return adev;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(typec_port_register_altmode);
|
EXPORT_SYMBOL_GPL(typec_port_register_altmode);
|
||||||
|
|
||||||
|
@ -1381,10 +1585,12 @@ struct typec_port *typec_register_port(struct device *parent,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ida_init(&port->mode_ids);
|
||||||
|
mutex_init(&port->port_type_lock);
|
||||||
|
|
||||||
port->id = id;
|
port->id = id;
|
||||||
port->cap = cap;
|
port->cap = cap;
|
||||||
port->port_type = cap->type;
|
port->port_type = cap->type;
|
||||||
mutex_init(&port->port_type_lock);
|
|
||||||
port->prefer_role = cap->prefer_role;
|
port->prefer_role = cap->prefer_role;
|
||||||
|
|
||||||
port->dev.class = typec_class;
|
port->dev.class = typec_class;
|
||||||
|
@ -1428,8 +1634,19 @@ EXPORT_SYMBOL_GPL(typec_unregister_port);
|
||||||
|
|
||||||
static int __init typec_init(void)
|
static int __init typec_init(void)
|
||||||
{
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = bus_register(&typec_bus);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
typec_class = class_create(THIS_MODULE, "typec");
|
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);
|
subsys_initcall(typec_init);
|
||||||
|
|
||||||
|
@ -1437,6 +1654,7 @@ static void __exit typec_exit(void)
|
||||||
{
|
{
|
||||||
class_destroy(typec_class);
|
class_destroy(typec_class);
|
||||||
ida_destroy(&typec_index_ida);
|
ida_destroy(&typec_index_ida);
|
||||||
|
bus_unregister(&typec_bus);
|
||||||
}
|
}
|
||||||
module_exit(typec_exit);
|
module_exit(typec_exit);
|
||||||
|
|
||||||
|
|
|
@ -746,4 +746,19 @@ struct tb_service_id {
|
||||||
#define TBSVC_MATCH_PROTOCOL_VERSION 0x0004
|
#define TBSVC_MATCH_PROTOCOL_VERSION 0x0004
|
||||||
#define TBSVC_MATCH_PROTOCOL_REVISION 0x0008
|
#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 */
|
#endif /* LINUX_MOD_DEVICETABLE_H */
|
||||||
|
|
|
@ -5,21 +5,18 @@
|
||||||
|
|
||||||
#include <linux/types.h>
|
#include <linux/types.h>
|
||||||
|
|
||||||
/* XXX: Once we have a header for USB Power Delivery, this belongs there */
|
|
||||||
#define ALTMODE_MAX_MODES 6
|
|
||||||
|
|
||||||
/* USB Type-C Specification releases */
|
/* USB Type-C Specification releases */
|
||||||
#define USB_TYPEC_REV_1_0 0x100 /* 1.0 */
|
#define USB_TYPEC_REV_1_0 0x100 /* 1.0 */
|
||||||
#define USB_TYPEC_REV_1_1 0x110 /* 1.1 */
|
#define USB_TYPEC_REV_1_1 0x110 /* 1.1 */
|
||||||
#define USB_TYPEC_REV_1_2 0x120 /* 1.2 */
|
#define USB_TYPEC_REV_1_2 0x120 /* 1.2 */
|
||||||
|
|
||||||
struct typec_altmode;
|
|
||||||
struct typec_partner;
|
struct typec_partner;
|
||||||
struct typec_cable;
|
struct typec_cable;
|
||||||
struct typec_plug;
|
struct typec_plug;
|
||||||
struct typec_port;
|
struct typec_port;
|
||||||
|
|
||||||
struct fwnode_handle;
|
struct fwnode_handle;
|
||||||
|
struct device;
|
||||||
|
|
||||||
enum typec_port_type {
|
enum typec_port_type {
|
||||||
TYPEC_PORT_SRC,
|
TYPEC_PORT_SRC,
|
||||||
|
@ -107,7 +104,7 @@ struct typec_altmode_desc {
|
||||||
u8 mode;
|
u8 mode;
|
||||||
u32 vdo;
|
u32 vdo;
|
||||||
/* Only used with ports */
|
/* Only used with ports */
|
||||||
enum typec_port_type roles;
|
enum typec_port_data roles;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct typec_altmode
|
struct typec_altmode
|
||||||
|
@ -186,7 +183,6 @@ struct typec_partner_desc {
|
||||||
* @dr_set: Set Data Role
|
* @dr_set: Set Data Role
|
||||||
* @pr_set: Set Power Role
|
* @pr_set: Set Power Role
|
||||||
* @vconn_set: Set VCONN Role
|
* @vconn_set: Set VCONN Role
|
||||||
* @activate_mode: Enter/exit given Alternate Mode
|
|
||||||
* @port_type_set: Set port type
|
* @port_type_set: Set port type
|
||||||
*
|
*
|
||||||
* Static capabilities of a single USB Type-C port.
|
* Static capabilities of a single USB Type-C port.
|
||||||
|
@ -212,12 +208,8 @@ struct typec_capability {
|
||||||
enum typec_role);
|
enum typec_role);
|
||||||
int (*vconn_set)(const struct typec_capability *,
|
int (*vconn_set)(const struct typec_capability *,
|
||||||
enum typec_role);
|
enum typec_role);
|
||||||
|
|
||||||
int (*activate_mode)(const struct typec_capability *,
|
|
||||||
int mode, int activate);
|
|
||||||
int (*port_type_set)(const struct typec_capability *,
|
int (*port_type_set)(const struct typec_capability *,
|
||||||
enum typec_port_type);
|
enum typec_port_type);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Specific to try_role(). Indicates the user want's to clear the preference. */
|
/* Specific to try_role(). Indicates the user want's to clear the preference. */
|
||||||
|
|
160
include/linux/usb/typec_altmode.h
Normal file
160
include/linux/usb/typec_altmode.h
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
/* SPDX-License-Identifier: GPL-2.0 */
|
||||||
|
|
||||||
|
#ifndef __USB_TYPEC_ALTMODE_H
|
||||||
|
#define __USB_TYPEC_ALTMODE_H
|
||||||
|
|
||||||
|
#include <linux/mod_devicetable.h>
|
||||||
|
#include <linux/usb/typec.h>
|
||||||
|
#include <linux/device.h>
|
||||||
|
|
||||||
|
#define MODE_DISCOVERY_MAX 6
|
||||||
|
|
||||||
|
struct typec_altmode_ops;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* @active: Tells has the mode been entered or not
|
||||||
|
* @desc: Optional human readable description of the mode
|
||||||
|
* @ops: Operations vector from the driver
|
||||||
|
*/
|
||||||
|
struct typec_altmode {
|
||||||
|
struct device dev;
|
||||||
|
u16 svid;
|
||||||
|
int mode;
|
||||||
|
u32 vdo;
|
||||||
|
unsigned int active:1;
|
||||||
|
|
||||||
|
char *desc;
|
||||||
|
const struct typec_altmode_ops *ops;
|
||||||
|
};
|
||||||
|
|
||||||
|
#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
|
||||||
|
* @activate: User callback for Enter/Exit Mode
|
||||||
|
*/
|
||||||
|
struct typec_altmode_ops {
|
||||||
|
int (*enter)(struct typec_altmode *altmode);
|
||||||
|
int (*exit)(struct typec_altmode *altmode);
|
||||||
|
void (*attention)(struct typec_altmode *altmode, 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);
|
||||||
|
int (*activate)(struct typec_altmode *altmode, int activate);
|
||||||
|
};
|
||||||
|
|
||||||
|
int typec_altmode_enter(struct typec_altmode *altmode);
|
||||||
|
int typec_altmode_exit(struct typec_altmode *altmode);
|
||||||
|
void typec_altmode_attention(struct typec_altmode *altmode, 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);
|
||||||
|
const struct typec_altmode *
|
||||||
|
typec_altmode_get_partner(struct typec_altmode *altmode);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* These are the connector states (USB, Safe and Alt Mode) defined in USB Type-C
|
||||||
|
* Specification. SVID specific connector states are expected to follow and
|
||||||
|
* start from the value TYPEC_STATE_MODAL.
|
||||||
|
*/
|
||||||
|
enum {
|
||||||
|
TYPEC_STATE_SAFE, /* USB Safe State */
|
||||||
|
TYPEC_STATE_USB, /* USB Operation */
|
||||||
|
TYPEC_STATE_MODAL, /* Alternate Modes */
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For the muxes there is no difference between Accessory Modes and Alternate
|
||||||
|
* Modes, so the Accessory Modes are supplied with specific modal state values
|
||||||
|
* here. Unlike with Alternate Modes, where the mux will be linked with the
|
||||||
|
* alternate mode device, the mux for Accessory Modes will be linked with the
|
||||||
|
* port device instead.
|
||||||
|
*
|
||||||
|
* Port drivers can use TYPEC_MODE_AUDIO and TYPEC_MODE_DEBUG as the mode
|
||||||
|
* value for typec_set_mode() when accessory modes are supported.
|
||||||
|
*/
|
||||||
|
enum {
|
||||||
|
TYPEC_MODE_AUDIO = TYPEC_STATE_MODAL, /* Audio Accessory */
|
||||||
|
TYPEC_MODE_DEBUG, /* Debug Accessory */
|
||||||
|
};
|
||||||
|
|
||||||
|
#define TYPEC_MODAL_STATE(_state_) ((_state_) + TYPEC_STATE_MODAL)
|
||||||
|
|
||||||
|
struct typec_altmode *typec_altmode_get_plug(struct typec_altmode *altmode,
|
||||||
|
enum typec_plug_index index);
|
||||||
|
void typec_altmode_put_plug(struct typec_altmode *plug);
|
||||||
|
|
||||||
|
struct typec_altmode *typec_match_altmode(struct typec_altmode **altmodes,
|
||||||
|
size_t n, u16 svid, u8 mode);
|
||||||
|
|
||||||
|
struct typec_altmode *
|
||||||
|
typec_altmode_register_notifier(struct device *dev, u16 svid, u8 mode,
|
||||||
|
struct notifier_block *nb);
|
||||||
|
|
||||||
|
void typec_altmode_unregister_notifier(struct typec_altmode *adev,
|
||||||
|
struct notifier_block *nb);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* typec_altmode_get_orientation - Get cable plug orientation
|
||||||
|
* altmode: Handle to the alternate mode
|
||||||
|
*/
|
||||||
|
static inline enum typec_orientation
|
||||||
|
typec_altmode_get_orientation(struct typec_altmode *altmode)
|
||||||
|
{
|
||||||
|
return typec_get_orientation(typec_altmode2port(altmode));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
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 */
|
|
@ -221,5 +221,9 @@ int main(void)
|
||||||
DEVID_FIELD(tb_service_id, protocol_version);
|
DEVID_FIELD(tb_service_id, protocol_version);
|
||||||
DEVID_FIELD(tb_service_id, protocol_revision);
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1352,6 +1352,19 @@ static int do_tbsvc_entry(const char *filename, void *symval, char *alias)
|
||||||
}
|
}
|
||||||
ADD_TO_DEVTABLE("tbsvc", tb_service_id, do_tbsvc_entry);
|
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? */
|
/* Does namelen bytes of name exactly match the symbol? */
|
||||||
static bool sym_is(const char *name, unsigned namelen, const char *symbol)
|
static bool sym_is(const char *name, unsigned namelen, const char *symbol)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in a new issue