mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 10:40:15 +02:00 
			
		
		
		
	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
					
				
					 14 changed files with 1174 additions and 147 deletions
				
			
		
							
								
								
									
										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.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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@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
 | 
			
		||||
What:		/sys/class/typec/<port>/<alt mode>/supported_roles
 | 
			
		||||
Date:		April 2017
 | 
			
		||||
Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
 | 
			
		||||
Description:
 | 
			
		||||
		Space separated list of the supported roles.
 | 
			
		||||
 | 
			
		||||
		This attribute is available for the devices describing the
 | 
			
		||||
		alternate modes a port supports, and it will not be exposed with
 | 
			
		||||
		the devices presenting the alternate modes the partners or cable
 | 
			
		||||
		plugs support.
 | 
			
		||||
 | 
			
		||||
		Valid values: source, sink
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										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
 | 
			
		||||
F:	drivers/usb/typec/mux/pi3usb30532.c
 | 
			
		||||
 | 
			
		||||
USB TYPEC SUBSYSTEM
 | 
			
		||||
USB TYPEC CLASS
 | 
			
		||||
M:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
 | 
			
		||||
L:	linux-usb@vger.kernel.org
 | 
			
		||||
S:	Maintained
 | 
			
		||||
| 
						 | 
				
			
			@ -14964,6 +14964,15 @@ F:	Documentation/driver-api/usb/typec.rst
 | 
			
		|||
F:	drivers/usb/typec/
 | 
			
		||||
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
 | 
			
		||||
M:	Alan Stern <stern@rowland.harvard.edu>
 | 
			
		||||
L:	linux-usb@vger.kernel.org
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										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/mutex.h>
 | 
			
		||||
#include <linux/slab.h>
 | 
			
		||||
#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 +31,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 +58,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,25 +147,148 @@ 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_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
 | 
			
		||||
 * @alt: Handle to the alternate mode
 | 
			
		||||
 * @adev: Handle to the alternate mode
 | 
			
		||||
 * @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, bool active)
 | 
			
		||||
void typec_altmode_update_active(struct typec_altmode *adev, bool active)
 | 
			
		||||
{
 | 
			
		||||
	char dir[6];
 | 
			
		||||
 | 
			
		||||
	if (alt->active == active)
 | 
			
		||||
	if (adev->active == active)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	alt->active = active;
 | 
			
		||||
	snprintf(dir, sizeof(dir), "mode%d", alt->mode);
 | 
			
		||||
	sysfs_notify(&alt->dev.kobj, dir, "active");
 | 
			
		||||
	kobject_uevent(&alt->dev.kobj, KOBJ_CHANGE);
 | 
			
		||||
	if (!is_typec_port(adev->dev.parent)) {
 | 
			
		||||
		if (!active)
 | 
			
		||||
			module_put(adev->dev.driver->owner);
 | 
			
		||||
		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);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -208,7 +315,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);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -217,7 +324,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 : "");
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -226,7 +333,7 @@ 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");
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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,
 | 
			
		||||
			    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 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 (ret)
 | 
			
		||||
		return ret;
 | 
			
		||||
	if (adev->active == enter)
 | 
			
		||||
		return size;
 | 
			
		||||
 | 
			
		||||
	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;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -258,7 +381,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) {
 | 
			
		||||
| 
						 | 
				
			
			@ -277,29 +400,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,
 | 
			
		||||
| 
						 | 
				
			
			@ -309,58 +475,74 @@ 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);
 | 
			
		||||
 | 
			
		||||
	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) {
 | 
			
		||||
		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))
 | 
			
		||||
		return;
 | 
			
		||||
	typec_mux_put(to_altmode(adev)->mux);
 | 
			
		||||
	device_unregister(&adev->dev);
 | 
			
		||||
}
 | 
			
		||||
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);
 | 
			
		||||
 | 
			
		||||
	ida_destroy(&partner->mode_ids);
 | 
			
		||||
	kfree(partner);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -463,6 +646,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;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -511,6 +695,7 @@ static void typec_plug_release(struct device *dev)
 | 
			
		|||
{
 | 
			
		||||
	struct typec_plug *plug = to_typec_plug(dev);
 | 
			
		||||
 | 
			
		||||
	ida_destroy(&plug->mode_ids);
 | 
			
		||||
	kfree(plug);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -563,6 +748,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;
 | 
			
		||||
| 
						 | 
				
			
			@ -1083,12 +1269,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,
 | 
			
		||||
| 
						 | 
				
			
			@ -1279,11 +1466,11 @@ EXPORT_SYMBOL_GPL(typec_get_orientation);
 | 
			
		|||
 | 
			
		||||
/**
 | 
			
		||||
 * typec_set_mode - Set mode of operation for USB Type-C connector
 | 
			
		||||
 * @port: USB Type-C port for the connector
 | 
			
		||||
 * @mode: Operation mode for the connector
 | 
			
		||||
 * @port: USB Type-C connector
 | 
			
		||||
 * @mode: Accessory Mode, USB Operation or Safe State
 | 
			
		||||
 *
 | 
			
		||||
 * Set mode @mode for @port. This function will configure the muxes needed to
 | 
			
		||||
 * enter @mode.
 | 
			
		||||
 * Configure @port for Accessory Mode @mode. This function will configure the
 | 
			
		||||
 * muxes needed for @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
 | 
			
		||||
 * @port: USB Type-C Port that supports 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
 | 
			
		||||
 * supporting.
 | 
			
		||||
| 
						 | 
				
			
			@ -1307,7 +1495,23 @@ struct typec_altmode *
 | 
			
		|||
typec_port_register_altmode(struct typec_port *port,
 | 
			
		||||
			    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);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1381,10 +1585,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;
 | 
			
		||||
| 
						 | 
				
			
			@ -1428,8 +1634,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);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1437,6 +1654,7 @@ static void __exit typec_exit(void)
 | 
			
		|||
{
 | 
			
		||||
	class_destroy(typec_class);
 | 
			
		||||
	ida_destroy(&typec_index_ida);
 | 
			
		||||
	bus_unregister(&typec_bus);
 | 
			
		||||
}
 | 
			
		||||
module_exit(typec_exit);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -746,4 +746,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 */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,21 +5,18 @@
 | 
			
		|||
 | 
			
		||||
#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 */
 | 
			
		||||
#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;
 | 
			
		||||
struct typec_port;
 | 
			
		||||
 | 
			
		||||
struct fwnode_handle;
 | 
			
		||||
struct device;
 | 
			
		||||
 | 
			
		||||
enum typec_port_type {
 | 
			
		||||
	TYPEC_PORT_SRC,
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -186,7 +183,6 @@ struct typec_partner_desc {
 | 
			
		|||
 * @dr_set: Set Data Role
 | 
			
		||||
 * @pr_set: Set Power Role
 | 
			
		||||
 * @vconn_set: Set VCONN Role
 | 
			
		||||
 * @activate_mode: Enter/exit given Alternate Mode
 | 
			
		||||
 * @port_type_set: Set port type
 | 
			
		||||
 *
 | 
			
		||||
 * Static capabilities of a single USB Type-C port.
 | 
			
		||||
| 
						 | 
				
			
			@ -212,12 +208,8 @@ 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);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* 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_revision);
 | 
			
		||||
 | 
			
		||||
	DEVID(typec_device_id);
 | 
			
		||||
	DEVID_FIELD(typec_device_id, svid);
 | 
			
		||||
	DEVID_FIELD(typec_device_id, mode);
 | 
			
		||||
 | 
			
		||||
	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);
 | 
			
		||||
 | 
			
		||||
/* 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)
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue