forked from mirrors/linux
		
	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. | 		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) | ||||||
|  | 		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) | 		if (ret) | ||||||
| 			return 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
	
	 Heikki Krogerus
						Heikki Krogerus