History

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.

Heikki Krogerus committed 8 years ago

Commit 8a37d871 parent 4ab8c1814 files · +1174 −147Browse files

Unified · Split

14 files
  1. Documentation/ABI/obsolete/sysfs-class-typec +48 −0
  2. Documentation/ABI/testing/sysfs-bus-typec +51 −0
  3. Documentation/ABI/testing/sysfs-class-typec +2 −60
  4. Documentation/driver-api/usb/typec_bus.rst +136 −0
  5. MAINTAINERS +10 −1
  6. drivers/usb/typec/Makefile +1 −1
  7. drivers/usb/typec/bus.c +401 −0
  8. drivers/usb/typec/bus.h +38 −0
  9. drivers/usb/typec/class.c +292 −74
  10. include/linux/mod_devicetable.h +15 −0
  11. include/linux/usb/typec.h +3 −11
  12. include/linux/usb/typec_altmode.h +160 −0
  13. scripts/mod/devicetable-offsets.c +4 −0
  14. scripts/mod/file2alias.c +13 −0
Documentation/ABI/obsolete/sysfs-class-typec+48−0
@@ -0,0 +1,48 @@
1+These files are deprecated and will be removed. The same files are available
2+under /sys/bus/typec (see Documentation/ABI/testing/sysfs-bus-typec).
3+
4+What: /sys/class/typec/<port|partner|cable>/<dev>/svid
5+Date: April 2017
6+Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
7+Description:
8+ The SVID (Standard or Vendor ID) assigned by USB-IF for this
9+ alternate mode.
10+
11+What: /sys/class/typec/<port|partner|cable>/<dev>/mode<index>/
12+Date: April 2017
13+Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
14+Description:
15+ Every supported mode will have its own directory. The name of
16+ a mode will be "mode<index>" (for example mode1), where <index>
17+ is the actual index to the mode VDO returned by Discover Modes
18+ USB power delivery command.
19+
20+What: /sys/class/typec/<port|partner|cable>/<dev>/mode<index>/description
21+Date: April 2017
22+Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
23+Description:
24+ Shows description of the mode. The description is optional for
25+ the drivers, just like with the Billboard Devices.
26+
27+What: /sys/class/typec/<port|partner|cable>/<dev>/mode<index>/vdo
28+Date: April 2017
29+Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
30+Description:
31+ Shows the VDO in hexadecimal returned by Discover Modes command
32+ for this mode.
33+
34+What: /sys/class/typec/<port|partner|cable>/<dev>/mode<index>/active
35+Date: April 2017
36+Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
37+Description:
38+ Shows if the mode is active or not. The attribute can be used
39+ for entering/exiting the mode with partners and cable plugs, and
40+ with the port alternate modes it can be used for disabling
41+ support for specific alternate modes. Entering/exiting modes is
42+ supported as synchronous operation so write(2) to the attribute
43+ does not return until the enter/exit mode operation has
44+ finished. The attribute is notified when the mode is
45+ entered/exited so poll(2) on the attribute wakes up.
46+ Entering/exiting a mode will also generate uevent KOBJ_CHANGE.
47+
48+ Valid values: yes, no
Documentation/ABI/testing/sysfs-bus-typec+51−0
@@ -0,0 +1,51 @@
1+What: /sys/bus/typec/devices/.../active
2+Date: July 2018
3+Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
4+Description:
5+ Shows if the mode is active or not. The attribute can be used
6+ for entering/exiting the mode. Entering/exiting modes is
7+ supported as synchronous operation so write(2) to the attribute
8+ does not return until the enter/exit mode operation has
9+ finished. The attribute is notified when the mode is
10+ entered/exited so poll(2) on the attribute wakes up.
11+ Entering/exiting a mode will also generate uevent KOBJ_CHANGE.
12+
13+ Valid values are boolean.
14+
15+What: /sys/bus/typec/devices/.../description
16+Date: July 2018
17+Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
18+Description:
19+ Shows description of the mode. The description is optional for
20+ the drivers, just like with the Billboard Devices.
21+
22+What: /sys/bus/typec/devices/.../mode
23+Date: July 2018
24+Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
25+Description:
26+ The index number of the mode returned by Discover Modes USB
27+ Power Delivery command. Depending on the alternate mode, the
28+ mode index may be significant.
29+
30+ With some alternate modes (SVIDs), the mode index is assigned
31+ for specific functionality in the specification for that
32+ alternate mode.
33+
34+ With other alternate modes, the mode index values are not
35+ assigned, and can not be therefore used for identification. When
36+ the mode index is not assigned, identifying the alternate mode
37+ must be done with either mode VDO or the description.
38+
39+What: /sys/bus/typec/devices/.../svid
40+Date: July 2018
41+Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
42+Description:
43+ The Standard or Vendor ID (SVID) assigned by USB-IF for this
44+ alternate mode.
45+
46+What: /sys/bus/typec/devices/.../vdo
47+Date: July 2018
48+Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
49+Description:
50+ Shows the VDO in hexadecimal returned by Discover Modes command
51+ for this mode.
Documentation/ABI/testing/sysfs-class-typec+2−60
@@ -222,70 +222,12 @@ Description: · 221 unmodified lines
222222 available. The value can be polled.
223223
224224
225−Alternate Mode devices.
225+USB Type-C port alternate mode devices.
226226
227−The alternate modes will have Standard or Vendor ID (SVID) assigned by USB-IF.
228−The ports, partners and cable plugs can have alternate modes. A supported SVID
229−will consist of a set of modes. Every SVID a port/partner/plug supports will
230−have a device created for it, and every supported mode for a supported SVID will
231−have its own directory under that device. Below <dev> refers to the device for
232−the alternate mode.
233−
234−What: /sys/class/typec/<port|partner|cable>/<dev>/svid
235−Date: April 2017
236−Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
237−Description:
238− The SVID (Standard or Vendor ID) assigned by USB-IF for this
239− alternate mode.
240−
241−What: /sys/class/typec/<port|partner|cable>/<dev>/mode<index>/
242−Date: April 2017
243−Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
244−Description:
245− Every supported mode will have its own directory. The name of
246− a mode will be "mode<index>" (for example mode1), where <index>
247− is the actual index to the mode VDO returned by Discover Modes
248− USB power delivery command.
249−
250−What: /sys/class/typec/<port|partner|cable>/<dev>/mode<index>/description
251−Date: April 2017
252−Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
253−Description:
254− Shows description of the mode. The description is optional for
255− the drivers, just like with the Billboard Devices.
256−
257−What: /sys/class/typec/<port|partner|cable>/<dev>/mode<index>/vdo
258−Date: April 2017
259−Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
260−Description:
261− Shows the VDO in hexadecimal returned by Discover Modes command
262− for this mode.
263−
264−What: /sys/class/typec/<port|partner|cable>/<dev>/mode<index>/active
265−Date: April 2017
266−Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
267−Description:
268− Shows if the mode is active or not. The attribute can be used
269− for entering/exiting the mode with partners and cable plugs, and
270− with the port alternate modes it can be used for disabling
271− support for specific alternate modes. Entering/exiting modes is
272− supported as synchronous operation so write(2) to the attribute
273− does not return until the enter/exit mode operation has
274− finished. The attribute is notified when the mode is
275− entered/exited so poll(2) on the attribute wakes up.
276− Entering/exiting a mode will also generate uevent KOBJ_CHANGE.
277−
278− Valid values: yes, no
279−
280−What: /sys/class/typec/<port>/<dev>/mode<index>/supported_roles
227+What: /sys/class/typec/<port>/<alt mode>/supported_roles
281228 Date: April 2017
282229 Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
283230 Description:
284231 Space separated list of the supported roles.
285232
286− This attribute is available for the devices describing the
287− alternate modes a port supports, and it will not be exposed with
288− the devices presenting the alternate modes the partners or cable
289− plugs support.
290−
291233 Valid values: source, sink
Documentation/driver-api/usb/typec_bus.rst+136−0
@@ -0,0 +1,136 @@
1+
2+API for USB Type-C Alternate Mode drivers
3+=========================================
4+
5+Introduction
6+------------
7+
8+Alternate modes require communication with the partner using Vendor Defined
9+Messages (VDM) as defined in USB Type-C and USB Power Delivery Specifications.
10+The communication is SVID (Standard or Vendor ID) specific, i.e. specific for
11+every alternate mode, so every alternate mode will need a custom driver.
12+
13+USB Type-C bus allows binding a driver to the discovered partner alternate
14+modes by using the SVID and the mode number.
15+
16+USB Type-C Connector Class provides a device for every alternate mode a port
17+supports, and separate device for every alternate mode the partner supports.
18+The drivers for the alternate modes are bound to the partner alternate mode
19+devices, and the port alternate mode devices must be handled by the port
20+drivers.
21+
22+When a new partner alternate mode device is registered, it is linked to the
23+alternate mode device of the port that the partner is attached to, that has
24+matching SVID and mode. Communication between the port driver and alternate mode
25+driver will happen using the same API.
26+
27+The port alternate mode devices are used as a proxy between the partner and the
28+alternate mode drivers, so the port drivers are only expected to pass the SVID
29+specific commands from the alternate mode drivers to the partner, and from the
30+partners to the alternate mode drivers. No direct SVID specific communication is
31+needed from the port drivers, but the port drivers need to provide the operation
32+callbacks for the port alternate mode devices, just like the alternate mode
33+drivers need to provide them for the partner alternate mode devices.
34+
35+Usage:
36+------
37+
38+General
39+~~~~~~~
40+
41+By default, the alternate mode drivers are responsible for entering the mode.
42+It is also possible to leave the decision about entering the mode to the user
43+space (See Documentation/ABI/testing/sysfs-class-typec). Port drivers should not
44+enter any modes on their own.
45+
46+``->vdm`` is the most important callback in the operation callbacks vector. It
47+will be used to deliver all the SVID specific commands from the partner to the
48+alternate mode driver, and vice versa in case of port drivers. The drivers send
49+the SVID specific commands to each other using :c:func:`typec_altmode_vmd()`.
50+
51+If the communication with the partner using the SVID specific commands results
52+in need to reconfigure the pins on the connector, the alternate mode driver
53+needs to notify the bus using :c:func:`typec_altmode_notify()`. The driver
54+passes the negotiated SVID specific pin configuration value to the function as
55+parameter. The bus driver will then configure the mux behind the connector using
56+that value as the state value for the mux, and also call blocking notification
57+chain to notify the external drivers about the state of the connector that need
58+to know it.
59+
60+NOTE: The SVID specific pin configuration values must always start from
61+``TYPEC_STATE_MODAL``. USB Type-C specification defines two default states for
62+the connector: ``TYPEC_STATE_USB`` and ``TYPEC_STATE_SAFE``. These values are
63+reserved by the bus as the first possible values for the state. When the
64+alternate mode is entered, the bus will put the connector into
65+``TYPEC_STATE_SAFE`` before sending Enter or Exit Mode command as defined in USB
66+Type-C Specification, and also put the connector back to ``TYPEC_STATE_USB``
67+after the mode has been exited.
68+
69+An example of working definitions for SVID specific pin configurations would
70+look like this:
71+
72+enum {
73+ ALTMODEX_CONF_A = TYPEC_STATE_MODAL,
74+ ALTMODEX_CONF_B,
75+ ...
76+};
77+
78+Helper macro ``TYPEC_MODAL_STATE()`` can also be used:
79+
80+#define ALTMODEX_CONF_A = TYPEC_MODAL_STATE(0);
81+#define ALTMODEX_CONF_B = TYPEC_MODAL_STATE(1);
82+
83+Notification chain
84+~~~~~~~~~~~~~~~~~~
85+
86+The drivers for the components that the alternate modes are designed for need to
87+get details regarding the results of the negotiation with the partner, and the
88+pin configuration of the connector. In case of DisplayPort alternate mode for
89+example, the GPU drivers will need to know those details. In case of
90+Thunderbolt alternate mode, the thunderbolt drivers will need to know them, and
91+so on.
92+
93+The notification chain is designed for this purpose. The drivers can register
94+notifiers with :c:func:`typec_altmode_register_notifier()`.
95+
96+Cable plug alternate modes
97+~~~~~~~~~~~~~~~~~~~~~~~~~~
98+
99+The alternate mode drivers are not bound to cable plug alternate mode devices,
100+only to the partner alternate mode devices. If the alternate mode supports, or
101+requires, a cable that responds to SOP Prime, and optionally SOP Double Prime
102+messages, the driver for that alternate mode must request handle to the cable
103+plug alternate modes using :c:func:`typec_altmode_get_plug()`, and take over
104+their control.
105+
106+Driver API
107+----------
108+
109+Alternate mode driver registering/unregistering
110+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
111+
112+.. kernel-doc:: drivers/usb/typec/bus.c
113+ :functions: typec_altmode_register_driver typec_altmode_unregister_driver
114+
115+Alternate mode driver operations
116+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
117+
118+.. kernel-doc:: drivers/usb/typec/bus.c
119+ :functions: typec_altmode_enter typec_altmode_exit typec_altmode_attention typec_altmode_vdm typec_altmode_notify
120+
121+API for the port drivers
122+~~~~~~~~~~~~~~~~~~~~~~~~
123+
124+.. kernel-doc:: drivers/usb/typec/bus.c
125+ :functions: typec_match_altmode
126+
127+Cable Plug operations
128+~~~~~~~~~~~~~~~~~~~~~
129+
130+.. kernel-doc:: drivers/usb/typec/bus.c
131+ :functions: typec_altmode_get_plug typec_altmode_put_plug
132+
133+Notifications
134+~~~~~~~~~~~~~
135+.. kernel-doc:: drivers/usb/typec/class.c
136+ :functions: typec_altmode_register_notifier typec_altmode_unregister_notifier
MAINTAINERS+10−1
@@ -14955,7 +14955,7 @@ L: linux-usb@vger.kernel.org · 14954 unmodified lines
1495514955 S: Maintained
1495614956 F: drivers/usb/typec/mux/pi3usb30532.c
1495714957
14958−USB TYPEC SUBSYSTEM
14958+USB TYPEC CLASS
1495914959 M: Heikki Krogerus <heikki.krogerus@linux.intel.com>
1496014960 L: linux-usb@vger.kernel.org
1496114961 S: Maintained
@@ -14964,6 +14964,15 @@ F: Documentation/driver-api/usb/typec.rst · 2 unmodified lines
1496414964 F: drivers/usb/typec/
1496514965 F: include/linux/usb/typec.h
1496614966
14967+USB TYPEC BUS FOR ALTERNATE MODES
14968+M: Heikki Krogerus <heikki.krogerus@linux.intel.com>
14969+L: linux-usb@vger.kernel.org
14970+S: Maintained
14971+F: Documentation/ABI/testing/sysfs-bus-typec
14972+F: Documentation/driver-api/usb/typec_bus.rst
14973+F: drivers/usb/typec/altmodes/
14974+F: include/linux/usb/typec_altmode.h
14975+
1496714976 USB UHCI DRIVER
1496814977 M: Alan Stern <stern@rowland.harvard.edu>
1496914978 L: linux-usb@vger.kernel.org
drivers/usb/typec/Makefile+1−1
@@ -1,6 +1,6 @@
11 # SPDX-License-Identifier: GPL-2.0
22 obj-$(CONFIG_TYPEC) += typec.o
3−typec-y := class.o mux.o
3+typec-y := class.o mux.o bus.o
44 obj-$(CONFIG_TYPEC_TCPM) += tcpm.o
55 obj-y += fusb302/
66 obj-$(CONFIG_TYPEC_WCOVE) += typec_wcove.o
drivers/usb/typec/bus.c+401−0
@@ -0,0 +1,401 @@
1+// SPDX-License-Identifier: GPL-2.0
2+/**
3+ * Bus for USB Type-C Alternate Modes
4+ *
5+ * Copyright (C) 2018 Intel Corporation
6+ * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
7+ */
8+
9+#include <linux/usb/pd_vdo.h>
10+
11+#include "bus.h"
12+
13+static inline int typec_altmode_set_mux(struct altmode *alt, u8 state)
14+{
15+ return alt->mux ? alt->mux->set(alt->mux, state) : 0;
16+}
17+
18+static int typec_altmode_set_state(struct typec_altmode *adev, int state)
19+{
20+ bool is_port = is_typec_port(adev->dev.parent);
21+ struct altmode *port_altmode;
22+ int ret;
23+
24+ port_altmode = is_port ? to_altmode(adev) : to_altmode(adev)->partner;
25+
26+ ret = typec_altmode_set_mux(port_altmode, state);
27+ if (ret)
28+ return ret;
29+
30+ blocking_notifier_call_chain(&port_altmode->nh, state, NULL);
31+
32+ return 0;
33+}
34+
35+/* -------------------------------------------------------------------------- */
36+/* Common API */
37+
38+/**
39+ * typec_altmode_notify - Communication between the OS and alternate mode driver
40+ * @adev: Handle to the alternate mode
41+ * @conf: Alternate mode specific configuration value
42+ * @data: Alternate mode specific data
43+ *
44+ * The primary purpose for this function is to allow the alternate mode drivers
45+ * to tell which pin configuration has been negotiated with the partner. That
46+ * information will then be used for example to configure the muxes.
47+ * Communication to the other direction is also possible, and low level device
48+ * drivers can also send notifications to the alternate mode drivers. The actual
49+ * communication will be specific for every SVID.
50+ */
51+int typec_altmode_notify(struct typec_altmode *adev,
52+ unsigned long conf, void *data)
53+{
54+ bool is_port = is_typec_port(adev->dev.parent);
55+ struct altmode *altmode;
56+ struct altmode *partner;
57+ int ret;
58+
59+ if (!adev)
60+ return 0;
61+
62+ altmode = to_altmode(adev);
63+
64+ if (!altmode->partner)
65+ return -ENODEV;
66+
67+ partner = altmode->partner;
68+
69+ ret = typec_altmode_set_mux(is_port ? altmode : partner, (u8)conf);
70+ if (ret)
71+ return ret;
72+
73+ blocking_notifier_call_chain(is_port ? &altmode->nh : &partner->nh,
74+ conf, data);
75+
76+ if (partner->adev.ops && partner->adev.ops->notify)
77+ return partner->adev.ops->notify(&partner->adev, conf, data);
78+
79+ return 0;
80+}
81+EXPORT_SYMBOL_GPL(typec_altmode_notify);
82+
83+/**
84+ * typec_altmode_enter - Enter Mode
85+ * @adev: The alternate mode
86+ *
87+ * The alternate mode drivers use this function to enter mode. The port drivers
88+ * use this to inform the alternate mode drivers that the partner has initiated
89+ * Enter Mode command.
90+ */
91+int typec_altmode_enter(struct typec_altmode *adev)
92+{
93+ struct altmode *partner = to_altmode(adev)->partner;
94+ struct typec_altmode *pdev = &partner->adev;
95+ int ret;
96+
97+ if (!adev || adev->active)
98+ return 0;
99+
100+ if (!pdev->ops || !pdev->ops->enter)
101+ return -EOPNOTSUPP;
102+
103+ /* Moving to USB Safe State */
104+ ret = typec_altmode_set_state(adev, TYPEC_STATE_SAFE);
105+ if (ret)
106+ return ret;
107+
108+ /* Enter Mode */
109+ return pdev->ops->enter(pdev);
110+}
111+EXPORT_SYMBOL_GPL(typec_altmode_enter);
112+
113+/**
114+ * typec_altmode_exit - Exit Mode
115+ * @adev: The alternate mode
116+ *
117+ * The partner of @adev has initiated Exit Mode command.
118+ */
119+int typec_altmode_exit(struct typec_altmode *adev)
120+{
121+ struct altmode *partner = to_altmode(adev)->partner;
122+ struct typec_altmode *pdev = &partner->adev;
123+ int ret;
124+
125+ if (!adev || !adev->active)
126+ return 0;
127+
128+ if (!pdev->ops || !pdev->ops->enter)
129+ return -EOPNOTSUPP;
130+
131+ /* Moving to USB Safe State */
132+ ret = typec_altmode_set_state(adev, TYPEC_STATE_SAFE);
133+ if (ret)
134+ return ret;
135+
136+ /* Exit Mode command */
137+ return pdev->ops->exit(pdev);
138+}
139+EXPORT_SYMBOL_GPL(typec_altmode_exit);
140+
141+/**
142+ * typec_altmode_attention - Attention command
143+ * @adev: The alternate mode
144+ * @vdo: VDO for the Attention command
145+ *
146+ * Notifies the partner of @adev about Attention command.
147+ */
148+void typec_altmode_attention(struct typec_altmode *adev, u32 vdo)
149+{
150+ struct typec_altmode *pdev = &to_altmode(adev)->partner->adev;
151+
152+ if (pdev->ops && pdev->ops->attention)
153+ pdev->ops->attention(pdev, vdo);
154+}
155+EXPORT_SYMBOL_GPL(typec_altmode_attention);
156+
157+/**
158+ * typec_altmode_vdm - Send Vendor Defined Messages (VDM) to the partner
159+ * @adev: Alternate mode handle
160+ * @header: VDM Header
161+ * @vdo: Array of Vendor Defined Data Objects
162+ * @count: Number of Data Objects
163+ *
164+ * The alternate mode drivers use this function for SVID specific communication
165+ * with the partner. The port drivers use it to deliver the Structured VDMs
166+ * received from the partners to the alternate mode drivers.
167+ */
168+int typec_altmode_vdm(struct typec_altmode *adev,
169+ const u32 header, const u32 *vdo, int count)
170+{
171+ struct typec_altmode *pdev;
172+ struct altmode *altmode;
173+
174+ if (!adev)
175+ return 0;
176+
177+ altmode = to_altmode(adev);
178+
179+ if (!altmode->partner)
180+ return -ENODEV;
181+
182+ pdev = &altmode->partner->adev;
183+
184+ if (!pdev->ops || !pdev->ops->vdm)
185+ return -EOPNOTSUPP;
186+
187+ return pdev->ops->vdm(pdev, header, vdo, count);
188+}
189+EXPORT_SYMBOL_GPL(typec_altmode_vdm);
190+
191+const struct typec_altmode *
192+typec_altmode_get_partner(struct typec_altmode *adev)
193+{
194+ return &to_altmode(adev)->partner->adev;
195+}
196+EXPORT_SYMBOL_GPL(typec_altmode_get_partner);
197+
198+/* -------------------------------------------------------------------------- */
199+/* API for the alternate mode drivers */
200+
201+/**
202+ * typec_altmode_get_plug - Find cable plug alternate mode
203+ * @adev: Handle to partner alternate mode
204+ * @index: Cable plug index
205+ *
206+ * Increment reference count for cable plug alternate mode device. Returns
207+ * handle to the cable plug alternate mode, or NULL if none is found.
208+ */
209+struct typec_altmode *typec_altmode_get_plug(struct typec_altmode *adev,
210+ enum typec_plug_index index)
211+{
212+ struct altmode *port = to_altmode(adev)->partner;
213+
214+ if (port->plug[index]) {
215+ get_device(&port->plug[index]->adev.dev);
216+ return &port->plug[index]->adev;
217+ }
218+
219+ return NULL;
220+}
221+EXPORT_SYMBOL_GPL(typec_altmode_get_plug);
222+
223+/**
224+ * typec_altmode_put_plug - Decrement cable plug alternate mode reference count
225+ * @plug: Handle to the cable plug alternate mode
226+ */
227+void typec_altmode_put_plug(struct typec_altmode *plug)
228+{
229+ if (plug)
230+ put_device(&plug->dev);
231+}
232+EXPORT_SYMBOL_GPL(typec_altmode_put_plug);
233+
234+int __typec_altmode_register_driver(struct typec_altmode_driver *drv,
235+ struct module *module)
236+{
237+ if (!drv->probe)
238+ return -EINVAL;
239+
240+ drv->driver.owner = module;
241+ drv->driver.bus = &typec_bus;
242+
243+ return driver_register(&drv->driver);
244+}
245+EXPORT_SYMBOL_GPL(__typec_altmode_register_driver);
246+
247+void typec_altmode_unregister_driver(struct typec_altmode_driver *drv)
248+{
249+ driver_unregister(&drv->driver);
250+}
251+EXPORT_SYMBOL_GPL(typec_altmode_unregister_driver);
252+
253+/* -------------------------------------------------------------------------- */
254+/* API for the port drivers */
255+
256+/**
257+ * typec_match_altmode - Match SVID to an array of alternate modes
258+ * @altmodes: Array of alternate modes
259+ * @n: Number of elements in the array, or -1 for NULL termiated arrays
260+ * @svid: Standard or Vendor ID to match with
261+ *
262+ * Return pointer to an alternate mode with SVID mathing @svid, or NULL when no
263+ * match is found.
264+ */
265+struct typec_altmode *typec_match_altmode(struct typec_altmode **altmodes,
266+ size_t n, u16 svid, u8 mode)
267+{
268+ int i;
269+
270+ for (i = 0; i < n; i++) {
271+ if (!altmodes[i])
272+ break;
273+ if (altmodes[i]->svid == svid && altmodes[i]->mode == mode)
274+ return altmodes[i];
275+ }
276+
277+ return NULL;
278+}
279+EXPORT_SYMBOL_GPL(typec_match_altmode);
280+
281+/* -------------------------------------------------------------------------- */
282+
283+static ssize_t
284+description_show(struct device *dev, struct device_attribute *attr, char *buf)
285+{
286+ struct typec_altmode *alt = to_typec_altmode(dev);
287+
288+ return sprintf(buf, "%s\n", alt->desc ? alt->desc : "");
289+}
290+static DEVICE_ATTR_RO(description);
291+
292+static struct attribute *typec_attrs[] = {
293+ &dev_attr_description.attr,
294+ NULL
295+};
296+ATTRIBUTE_GROUPS(typec);
297+
298+static int typec_match(struct device *dev, struct device_driver *driver)
299+{
300+ struct typec_altmode_driver *drv = to_altmode_driver(driver);
301+ struct typec_altmode *altmode = to_typec_altmode(dev);
302+ const struct typec_device_id *id;
303+
304+ for (id = drv->id_table; id->svid; id++)
305+ if (id->svid == altmode->svid &&
306+ (id->mode == TYPEC_ANY_MODE || id->mode == altmode->mode))
307+ return 1;
308+ return 0;
309+}
310+
311+static int typec_uevent(struct device *dev, struct kobj_uevent_env *env)
312+{
313+ struct typec_altmode *altmode = to_typec_altmode(dev);
314+
315+ if (add_uevent_var(env, "SVID=%04X", altmode->svid))
316+ return -ENOMEM;
317+
318+ if (add_uevent_var(env, "MODE=%u", altmode->mode))
319+ return -ENOMEM;
320+
321+ return add_uevent_var(env, "MODALIAS=typec:id%04Xm%02X",
322+ altmode->svid, altmode->mode);
323+}
324+
325+static int typec_altmode_create_links(struct altmode *alt)
326+{
327+ struct device *port_dev = &alt->partner->adev.dev;
328+ struct device *dev = &alt->adev.dev;
329+ int err;
330+
331+ err = sysfs_create_link(&dev->kobj, &port_dev->kobj, "port");
332+ if (err)
333+ return err;
334+
335+ err = sysfs_create_link(&port_dev->kobj, &dev->kobj, "partner");
336+ if (err)
337+ sysfs_remove_link(&dev->kobj, "port");
338+
339+ return err;
340+}
341+
342+static void typec_altmode_remove_links(struct altmode *alt)
343+{
344+ sysfs_remove_link(&alt->partner->adev.dev.kobj, "partner");
345+ sysfs_remove_link(&alt->adev.dev.kobj, "port");
346+}
347+
348+static int typec_probe(struct device *dev)
349+{
350+ struct typec_altmode_driver *drv = to_altmode_driver(dev->driver);
351+ struct typec_altmode *adev = to_typec_altmode(dev);
352+ struct altmode *altmode = to_altmode(adev);
353+ int ret;
354+
355+ /* Fail if the port does not support the alternate mode */
356+ if (!altmode->partner)
357+ return -ENODEV;
358+
359+ ret = typec_altmode_create_links(altmode);
360+ if (ret) {
361+ dev_warn(dev, "failed to create symlinks\n");
362+ return ret;
363+ }
364+
365+ ret = drv->probe(adev);
366+ if (ret)
367+ typec_altmode_remove_links(altmode);
368+
369+ return ret;
370+}
371+
372+static int typec_remove(struct device *dev)
373+{
374+ struct typec_altmode_driver *drv = to_altmode_driver(dev->driver);
375+ struct typec_altmode *adev = to_typec_altmode(dev);
376+ struct altmode *altmode = to_altmode(adev);
377+
378+ typec_altmode_remove_links(altmode);
379+
380+ if (drv->remove)
381+ drv->remove(to_typec_altmode(dev));
382+
383+ if (adev->active) {
384+ WARN_ON(typec_altmode_set_state(adev, TYPEC_STATE_SAFE));
385+ typec_altmode_update_active(adev, false);
386+ }
387+
388+ adev->desc = NULL;
389+ adev->ops = NULL;
390+
391+ return 0;
392+}
393+
394+struct bus_type typec_bus = {
395+ .name = "typec",
396+ .dev_groups = typec_groups,
397+ .match = typec_match,
398+ .uevent = typec_uevent,
399+ .probe = typec_probe,
400+ .remove = typec_remove,
401+};
drivers/usb/typec/bus.h+38−0
@@ -0,0 +1,38 @@
1+/* SPDX-License-Identifier: GPL-2.0 */
2+
3+#ifndef __USB_TYPEC_ALTMODE_H__
4+#define __USB_TYPEC_ALTMODE_H__
5+
6+#include <linux/usb/typec_altmode.h>
7+#include <linux/usb/typec_mux.h>
8+
9+struct bus_type;
10+
11+struct altmode {
12+ unsigned int id;
13+ struct typec_altmode adev;
14+ struct typec_mux *mux;
15+
16+ enum typec_port_data roles;
17+
18+ struct attribute *attrs[5];
19+ char group_name[6];
20+ struct attribute_group group;
21+ const struct attribute_group *groups[2];
22+
23+ struct altmode *partner;
24+ struct altmode *plug[2];
25+
26+ struct blocking_notifier_head nh;
27+};
28+
29+#define to_altmode(d) container_of(d, struct altmode, adev)
30+
31+extern struct bus_type typec_bus;
32+extern const struct device_type typec_altmode_dev_type;
33+extern const struct device_type typec_port_dev_type;
34+
35+#define is_typec_altmode(_dev_) (_dev_->type == &typec_altmode_dev_type)
36+#define is_typec_port(_dev_) (_dev_->type == &typec_port_dev_type)
37+
38+#endif /* __USB_TYPEC_ALTMODE_H__ */
drivers/usb/typec/class.c+292−74
@@ -10,28 +10,13 @@ · 9 unmodified lines
1010 #include <linux/module.h>
1111 #include <linux/mutex.h>
1212 #include <linux/slab.h>
13−#include <linux/usb/typec.h>
14−#include <linux/usb/typec_mux.h>
1513
16−struct typec_altmode {
17− struct device dev;
18− u16 svid;
19− u8 mode;
20−
21− u32 vdo;
22− char *desc;
23− enum typec_port_type roles;
24− unsigned int active:1;
25−
26− struct attribute *attrs[5];
27− char group_name[6];
28− struct attribute_group group;
29− const struct attribute_group *groups[2];
30−};
14+#include "bus.h"
3115
3216 struct typec_plug {
3317 struct device dev;
3418 enum typec_plug_index index;
19+ struct ida mode_ids;
3520 };
3621
3722 struct typec_cable {
@@ -46,11 +31,13 @@ struct typec_partner { · 8 unmodified lines
4631 unsigned int usb_pd:1;
4732 struct usb_pd_identity *identity;
4833 enum typec_accessory accessory;
34+ struct ida mode_ids;
4935 };
5036
5137 struct typec_port {
5238 unsigned int id;
5339 struct device dev;
40+ struct ida mode_ids;
5441
5542 int prefer_role;
5643 enum typec_data_role data_role;
@@ -71,17 +58,14 @@ struct typec_port { · 14 unmodified lines
7158 #define to_typec_plug(_dev_) container_of(_dev_, struct typec_plug, dev)
7259 #define to_typec_cable(_dev_) container_of(_dev_, struct typec_cable, dev)
7360 #define to_typec_partner(_dev_) container_of(_dev_, struct typec_partner, dev)
74−#define to_altmode(_dev_) container_of(_dev_, struct typec_altmode, dev)
7561
7662 static const struct device_type typec_partner_dev_type;
7763 static const struct device_type typec_cable_dev_type;
7864 static const struct device_type typec_plug_dev_type;
79−static const struct device_type typec_port_dev_type;
8065
8166 #define is_typec_partner(_dev_) (_dev_->type == &typec_partner_dev_type)
8267 #define is_typec_cable(_dev_) (_dev_->type == &typec_cable_dev_type)
8368 #define is_typec_plug(_dev_) (_dev_->type == &typec_plug_dev_type)
84−#define is_typec_port(_dev_) (_dev_->type == &typec_port_dev_type)
8569
8670 static DEFINE_IDA(typec_index_ida);
8771 static struct class *typec_class;
@@ -163,25 +147,148 @@ static void typec_report_identity(struct device *dev) · 75 unmodified lines
163147 /* ------------------------------------------------------------------------- */
164148 /* Alternate Modes */
165149
150+static int altmode_match(struct device *dev, void *data)
151+{
152+ struct typec_altmode *adev = to_typec_altmode(dev);
153+ struct typec_device_id *id = data;
154+
155+ if (!is_typec_altmode(dev))
156+ return 0;
157+
158+ return ((adev->svid == id->svid) && (adev->mode == id->mode));
159+}
160+
161+static void typec_altmode_set_partner(struct altmode *altmode)
162+{
163+ struct typec_altmode *adev = &altmode->adev;
164+ struct typec_device_id id = { adev->svid, adev->mode, };
165+ struct typec_port *port = typec_altmode2port(adev);
166+ struct altmode *partner;
167+ struct device *dev;
168+
169+ dev = device_find_child(&port->dev, &id, altmode_match);
170+ if (!dev)
171+ return;
172+
173+ /* Bind the port alt mode to the partner/plug alt mode. */
174+ partner = to_altmode(to_typec_altmode(dev));
175+ altmode->partner = partner;
176+
177+ /* Bind the partner/plug alt mode to the port alt mode. */
178+ if (is_typec_plug(adev->dev.parent)) {
179+ struct typec_plug *plug = to_typec_plug(adev->dev.parent);
180+
181+ partner->plug[plug->index] = altmode;
182+ } else {
183+ partner->partner = altmode;
184+ }
185+}
186+
187+static void typec_altmode_put_partner(struct altmode *altmode)
188+{
189+ struct altmode *partner = altmode->partner;
190+ struct typec_altmode *adev;
191+
192+ if (!partner)
193+ return;
194+
195+ adev = &partner->adev;
196+
197+ if (is_typec_plug(adev->dev.parent)) {
198+ struct typec_plug *plug = to_typec_plug(adev->dev.parent);
199+
200+ partner->plug[plug->index] = NULL;
201+ } else {
202+ partner->partner = NULL;
203+ }
204+ put_device(&adev->dev);
205+}
206+
207+static int __typec_port_match(struct device *dev, const void *name)
208+{
209+ return !strcmp((const char *)name, dev_name(dev));
210+}
211+
212+static void *typec_port_match(struct device_connection *con, int ep, void *data)
213+{
214+ return class_find_device(typec_class, NULL, con->endpoint[ep],
215+ __typec_port_match);
216+}
217+
218+struct typec_altmode *
219+typec_altmode_register_notifier(struct device *dev, u16 svid, u8 mode,
220+ struct notifier_block *nb)
221+{
222+ struct typec_device_id id = { svid, mode, };
223+ struct device *altmode_dev;
224+ struct device *port_dev;
225+ struct altmode *altmode;
226+ int ret;
227+
228+ /* Find the port linked to the caller */
229+ port_dev = device_connection_find_match(dev, NULL, NULL,
230+ typec_port_match);
231+ if (IS_ERR_OR_NULL(port_dev))
232+ return port_dev ? ERR_CAST(port_dev) : ERR_PTR(-ENODEV);
233+
234+ /* Find the altmode with matching svid */
235+ altmode_dev = device_find_child(port_dev, &id, altmode_match);
236+
237+ put_device(port_dev);
238+
239+ if (!altmode_dev)
240+ return ERR_PTR(-ENODEV);
241+
242+ altmode = to_altmode(to_typec_altmode(altmode_dev));
243+
244+ /* Register notifier */
245+ ret = blocking_notifier_chain_register(&altmode->nh, nb);
246+ if (ret) {
247+ put_device(altmode_dev);
248+ return ERR_PTR(ret);
249+ }
250+
251+ return &altmode->adev;
252+}
253+EXPORT_SYMBOL_GPL(typec_altmode_register_notifier);
254+
255+void typec_altmode_unregister_notifier(struct typec_altmode *adev,
256+ struct notifier_block *nb)
257+{
258+ struct altmode *altmode = to_altmode(adev);
259+
260+ blocking_notifier_chain_unregister(&altmode->nh, nb);
261+ put_device(&adev->dev);
262+}
263+EXPORT_SYMBOL_GPL(typec_altmode_unregister_notifier);
264+
166265 /**
167266 * typec_altmode_update_active - Report Enter/Exit mode
168− * @alt: Handle to the alternate mode
267+ * @adev: Handle to the alternate mode
169268 * @active: True when the mode has been entered
170269 *
171270 * If a partner or cable plug executes Enter/Exit Mode command successfully, the
172271 * drivers use this routine to report the updated state of the mode.
173272 */
174−void typec_altmode_update_active(struct typec_altmode *alt, bool active)
273+void typec_altmode_update_active(struct typec_altmode *adev, bool active)
175274 {
176275 char dir[6];
177276
178− if (alt->active == active)
277+ if (adev->active == active)
179278 return;
180279
181− alt->active = active;
182− snprintf(dir, sizeof(dir), "mode%d", alt->mode);
183− sysfs_notify(&alt->dev.kobj, dir, "active");
184− kobject_uevent(&alt->dev.kobj, KOBJ_CHANGE);
280+ if (!is_typec_port(adev->dev.parent)) {
281+ if (!active)
282+ module_put(adev->dev.driver->owner);
283+ else
284+ WARN_ON(!try_module_get(adev->dev.driver->owner));
285+ }
286+
287+ adev->active = active;
288+ snprintf(dir, sizeof(dir), "mode%d", adev->mode);
289+ sysfs_notify(&adev->dev.kobj, dir, "active");
290+ sysfs_notify(&adev->dev.kobj, NULL, "active");
291+ kobject_uevent(&adev->dev.kobj, KOBJ_CHANGE);
185292 }
186293 EXPORT_SYMBOL_GPL(typec_altmode_update_active);
187294
@@ -208,7 +315,7 @@ EXPORT_SYMBOL_GPL(typec_altmode2port); · 20 unmodified lines
208315 static ssize_t
209316 vdo_show(struct device *dev, struct device_attribute *attr, char *buf)
210317 {
211− struct typec_altmode *alt = to_altmode(dev);
318+ struct typec_altmode *alt = to_typec_altmode(dev);
212319
213320 return sprintf(buf, "0x%08x\n", alt->vdo);
214321 }
@@ -217,7 +324,7 @@ static DEVICE_ATTR_RO(vdo); · 2 unmodified lines
217324 static ssize_t
218325 description_show(struct device *dev, struct device_attribute *attr, char *buf)
219326 {
220− struct typec_altmode *alt = to_altmode(dev);
327+ struct typec_altmode *alt = to_typec_altmode(dev);
221328
222329 return sprintf(buf, "%s\n", alt->desc ? alt->desc : "");
223330 }
@@ -226,7 +333,7 @@ static DEVICE_ATTR_RO(description); · 2 unmodified lines
226333 static ssize_t
227334 active_show(struct device *dev, struct device_attribute *attr, char *buf)
228335 {
229− struct typec_altmode *alt = to_altmode(dev);
336+ struct typec_altmode *alt = to_typec_altmode(dev);
230337
231338 return sprintf(buf, "%s\n", alt->active ? "yes" : "no");
232339 }
@@ -234,21 +341,37 @@ active_show(struct device *dev, struct device_attribute *attr, char *buf) · 1 unmodified line
234341 static ssize_t active_store(struct device *dev, struct device_attribute *attr,
235342 const char *buf, size_t size)
236343 {
237− struct typec_altmode *alt = to_altmode(dev);
238− struct typec_port *port = typec_altmode2port(alt);
239− bool activate;
344+ struct typec_altmode *adev = to_typec_altmode(dev);
345+ struct altmode *altmode = to_altmode(adev);
346+ bool enter;
240347 int ret;
241348
242− if (!port->cap->activate_mode)
243− return -EOPNOTSUPP;
244−
245− ret = kstrtobool(buf, &activate);
349+ ret = kstrtobool(buf, &enter);
246350 if (ret)
247351 return ret;
248352
249− ret = port->cap->activate_mode(port->cap, alt->mode, activate);
250− if (ret)
251− return ret;
353+ if (adev->active == enter)
354+ return size;
355+
356+ if (is_typec_port(adev->dev.parent)) {
357+ typec_altmode_update_active(adev, enter);
358+
359+ /* Make sure that the partner exits the mode before disabling */
360+ if (altmode->partner && !enter && altmode->partner->adev.active)
361+ typec_altmode_exit(&altmode->partner->adev);
362+ } else if (altmode->partner) {
363+ if (enter && !altmode->partner->adev.active) {
364+ dev_warn(dev, "port has the mode disabled\n");
365+ return -EPERM;
366+ }
367+ }
368+
369+ /* Note: If there is no driver, the mode will not be entered */
370+ if (adev->ops && adev->ops->activate) {
371+ ret = adev->ops->activate(adev, enter);
372+ if (ret)
373+ return ret;
374+ }
252375
253376 return size;
254377 }
@@ -258,7 +381,7 @@ static ssize_t · 3 unmodified lines
258381 supported_roles_show(struct device *dev, struct device_attribute *attr,
259382 char *buf)
260383 {
261− struct typec_altmode *alt = to_altmode(dev);
384+ struct altmode *alt = to_altmode(to_typec_altmode(dev));
262385 ssize_t ret;
263386
264387 switch (alt->roles) {
@@ -277,29 +400,72 @@ supported_roles_show(struct device *dev, struct device_attribute *attr, · 12 unmodified lines
277400 }
278401 static DEVICE_ATTR_RO(supported_roles);
279402
280−static void typec_altmode_release(struct device *dev)
403+static ssize_t
404+mode_show(struct device *dev, struct device_attribute *attr, char *buf)
281405 {
282− struct typec_altmode *alt = to_altmode(dev);
406+ struct typec_altmode *adev = to_typec_altmode(dev);
283407
284− kfree(alt);
408+ return sprintf(buf, "%u\n", adev->mode);
285409 }
410+static DEVICE_ATTR_RO(mode);
286411
287−static ssize_t svid_show(struct device *dev, struct device_attribute *attr,
288− char *buf)
412+static ssize_t
413+svid_show(struct device *dev, struct device_attribute *attr, char *buf)
289414 {
290− struct typec_altmode *alt = to_altmode(dev);
415+ struct typec_altmode *adev = to_typec_altmode(dev);
291416
292− return sprintf(buf, "%04x\n", alt->svid);
417+ return sprintf(buf, "%04x\n", adev->svid);
293418 }
294419 static DEVICE_ATTR_RO(svid);
295420
296421 static struct attribute *typec_altmode_attrs[] = {
422+ &dev_attr_active.attr,
423+ &dev_attr_mode.attr,
297424 &dev_attr_svid.attr,
425+ &dev_attr_vdo.attr,
298426 NULL
299427 };
300428 ATTRIBUTE_GROUPS(typec_altmode);
301429
302−static const struct device_type typec_altmode_dev_type = {
430+static int altmode_id_get(struct device *dev)
431+{
432+ struct ida *ids;
433+
434+ if (is_typec_partner(dev))
435+ ids = &to_typec_partner(dev)->mode_ids;
436+ else if (is_typec_plug(dev))
437+ ids = &to_typec_plug(dev)->mode_ids;
438+ else
439+ ids = &to_typec_port(dev)->mode_ids;
440+
441+ return ida_simple_get(ids, 0, 0, GFP_KERNEL);
442+}
443+
444+static void altmode_id_remove(struct device *dev, int id)
445+{
446+ struct ida *ids;
447+
448+ if (is_typec_partner(dev))
449+ ids = &to_typec_partner(dev)->mode_ids;
450+ else if (is_typec_plug(dev))
451+ ids = &to_typec_plug(dev)->mode_ids;
452+ else
453+ ids = &to_typec_port(dev)->mode_ids;
454+
455+ ida_simple_remove(ids, id);
456+}
457+
458+static void typec_altmode_release(struct device *dev)
459+{
460+ struct altmode *alt = to_altmode(to_typec_altmode(dev));
461+
462+ typec_altmode_put_partner(alt);
463+
464+ altmode_id_remove(alt->adev.dev.parent, alt->id);
465+ kfree(alt);
466+}
467+
468+const struct device_type typec_altmode_dev_type = {
303469 .name = "typec_alternate_mode",
304470 .groups = typec_altmode_groups,
305471 .release = typec_altmode_release,
@@ -309,58 +475,74 @@ static struct typec_altmode * · 3 unmodified lines
309475 typec_register_altmode(struct device *parent,
310476 const struct typec_altmode_desc *desc)
311477 {
312− struct typec_altmode *alt;
478+ unsigned int id = altmode_id_get(parent);
479+ bool is_port = is_typec_port(parent);
480+ struct altmode *alt;
313481 int ret;
314482
315483 alt = kzalloc(sizeof(*alt), GFP_KERNEL);
316484 if (!alt)
317485 return ERR_PTR(-ENOMEM);
318486
319− alt->svid = desc->svid;
320− alt->mode = desc->mode;
321− alt->vdo = desc->vdo;
487+ alt->adev.svid = desc->svid;
488+ alt->adev.mode = desc->mode;
489+ alt->adev.vdo = desc->vdo;
322490 alt->roles = desc->roles;
491+ alt->id = id;
323492
324493 alt->attrs[0] = &dev_attr_vdo.attr;
325494 alt->attrs[1] = &dev_attr_description.attr;
326495 alt->attrs[2] = &dev_attr_active.attr;
327496
328− if (is_typec_port(parent))
497+ if (is_port) {
329498 alt->attrs[3] = &dev_attr_supported_roles.attr;
499+ alt->adev.active = true; /* Enabled by default */
500+ }
330501
331502 sprintf(alt->group_name, "mode%d", desc->mode);
332503 alt->group.name = alt->group_name;
333504 alt->group.attrs = alt->attrs;
334505 alt->groups[0] = &alt->group;
335506
336− alt->dev.parent = parent;
337− alt->dev.groups = alt->groups;
338− alt->dev.type = &typec_altmode_dev_type;
339− dev_set_name(&alt->dev, "%s-%04x:%u", dev_name(parent),
340− alt->svid, alt->mode);
507+ alt->adev.dev.parent = parent;
508+ alt->adev.dev.groups = alt->groups;
509+ alt->adev.dev.type = &typec_altmode_dev_type;
510+ dev_set_name(&alt->adev.dev, "%s.%u", dev_name(parent), id);
511+
512+ /* Link partners and plugs with the ports */
513+ if (is_port)
514+ BLOCKING_INIT_NOTIFIER_HEAD(&alt->nh);
515+ else
516+ typec_altmode_set_partner(alt);
517+
518+ /* The partners are bind to drivers */
519+ if (is_typec_partner(parent))
520+ alt->adev.dev.bus = &typec_bus;
341521
342− ret = device_register(&alt->dev);
522+ ret = device_register(&alt->adev.dev);
343523 if (ret) {
344524 dev_err(parent, "failed to register alternate mode (%d)\n",
345525 ret);
346− put_device(&alt->dev);
526+ put_device(&alt->adev.dev);
347527 return ERR_PTR(ret);
348528 }
349529
350− return alt;
530+ return &alt->adev;
351531 }
352532
353533 /**
354534 * typec_unregister_altmode - Unregister Alternate Mode
355− * @alt: The alternate mode to be unregistered
535+ * @adev: The alternate mode to be unregistered
356536 *
357537 * Unregister device created with typec_partner_register_altmode(),
358538 * typec_plug_register_altmode() or typec_port_register_altmode().
359539 */
360−void typec_unregister_altmode(struct typec_altmode *alt)
540+void typec_unregister_altmode(struct typec_altmode *adev)
361541 {
362− if (!IS_ERR_OR_NULL(alt))
363− device_unregister(&alt->dev);
542+ if (IS_ERR_OR_NULL(adev))
543+ return;
544+ typec_mux_put(to_altmode(adev)->mux);
545+ device_unregister(&adev->dev);
364546 }
365547 EXPORT_SYMBOL_GPL(typec_unregister_altmode);
366548
@@ -398,6 +580,7 @@ static void typec_partner_release(struct device *dev) · 31 unmodified lines
398580 {
399581 struct typec_partner *partner = to_typec_partner(dev);
400582
583+ ida_destroy(&partner->mode_ids);
401584 kfree(partner);
402585 }
403586
@@ -463,6 +646,7 @@ struct typec_partner *typec_register_partner(struct typec_port *port, · 59 unmodified lines
463646 if (!partner)
464647 return ERR_PTR(-ENOMEM);
465648
649+ ida_init(&partner->mode_ids);
466650 partner->usb_pd = desc->usb_pd;
467651 partner->accessory = desc->accessory;
468652
@@ -511,6 +695,7 @@ static void typec_plug_release(struct device *dev) · 42 unmodified lines
511695 {
512696 struct typec_plug *plug = to_typec_plug(dev);
513697
698+ ida_destroy(&plug->mode_ids);
514699 kfree(plug);
515700 }
516701
@@ -563,6 +748,7 @@ struct typec_plug *typec_register_plug(struct typec_cable *cable, · 46 unmodified lines
563748
564749 sprintf(name, "plug%d", desc->index);
565750
751+ ida_init(&plug->mode_ids);
566752 plug->index = desc->index;
567753 plug->dev.class = typec_class;
568754 plug->dev.parent = &cable->dev;
@@ -1083,12 +1269,13 @@ static void typec_release(struct device *dev) · 514 unmodified lines
10831269 struct typec_port *port = to_typec_port(dev);
10841270
10851271 ida_simple_remove(&typec_index_ida, port->id);
1272+ ida_destroy(&port->mode_ids);
10861273 typec_switch_put(port->sw);
10871274 typec_mux_put(port->mux);
10881275 kfree(port);
10891276 }
10901277
1091−static const struct device_type typec_port_dev_type = {
1278+const struct device_type typec_port_dev_type = {
10921279 .name = "typec_port",
10931280 .groups = typec_groups,
10941281 .uevent = typec_uevent,
@@ -1279,11 +1466,11 @@ EXPORT_SYMBOL_GPL(typec_get_orientation); · 184 unmodified lines
12791466
12801467 /**
12811468 * typec_set_mode - Set mode of operation for USB Type-C connector
1282− * @port: USB Type-C port for the connector
1283− * @mode: Operation mode for the connector
1469+ * @port: USB Type-C connector
1470+ * @mode: Accessory Mode, USB Operation or Safe State
12841471 *
1285− * Set mode @mode for @port. This function will configure the muxes needed to
1286− * enter @mode.
1472+ * Configure @port for Accessory Mode @mode. This function will configure the
1473+ * muxes needed for @mode.
12871474 */
12881475 int typec_set_mode(struct typec_port *port, int mode)
12891476 {
@@ -1297,6 +1484,7 @@ EXPORT_SYMBOL_GPL(typec_set_mode); · 7 unmodified lines
12971484 * typec_port_register_altmode - Register USB Type-C Port Alternate Mode
12981485 * @port: USB Type-C Port that supports the alternate mode
12991486 * @desc: Description of the alternate mode
1487+ * @drvdata: Private pointer to driver specific info
13001488 *
13011489 * This routine is used to register an alternate mode that @port is capable of
13021490 * supporting.
@@ -1307,7 +1495,23 @@ struct typec_altmode * · 4 unmodified lines
13071495 typec_port_register_altmode(struct typec_port *port,
13081496 const struct typec_altmode_desc *desc)
13091497 {
1310− return typec_register_altmode(&port->dev, desc);
1498+ struct typec_altmode *adev;
1499+ struct typec_mux *mux;
1500+ char id[10];
1501+
1502+ sprintf(id, "id%04xm%02x", desc->svid, desc->mode);
1503+
1504+ mux = typec_mux_get(port->dev.parent, id);
1505+ if (IS_ERR(mux))
1506+ return ERR_CAST(mux);
1507+
1508+ adev = typec_register_altmode(&port->dev, desc);
1509+ if (IS_ERR(adev))
1510+ typec_mux_put(mux);
1511+ else
1512+ to_altmode(adev)->mux = mux;
1513+
1514+ return adev;
13111515 }
13121516 EXPORT_SYMBOL_GPL(typec_port_register_altmode);
13131517
@@ -1381,10 +1585,12 @@ struct typec_port *typec_register_port(struct device *parent, · 67 unmodified lines
13811585 break;
13821586 }
13831587
1588+ ida_init(&port->mode_ids);
1589+ mutex_init(&port->port_type_lock);
1590+
13841591 port->id = id;
13851592 port->cap = cap;
13861593 port->port_type = cap->type;
1387− mutex_init(&port->port_type_lock);
13881594 port->prefer_role = cap->prefer_role;
13891595
13901596 port->dev.class = typec_class;
@@ -1428,8 +1634,19 @@ EXPORT_SYMBOL_GPL(typec_unregister_port); · 37 unmodified lines
14281634
14291635 static int __init typec_init(void)
14301636 {
1637+ int ret;
1638+
1639+ ret = bus_register(&typec_bus);
1640+ if (ret)
1641+ return ret;
1642+
14311643 typec_class = class_create(THIS_MODULE, "typec");
1432− return PTR_ERR_OR_ZERO(typec_class);
1644+ if (IS_ERR(typec_class)) {
1645+ bus_unregister(&typec_bus);
1646+ return PTR_ERR(typec_class);
1647+ }
1648+
1649+ return 0;
14331650 }
14341651 subsys_initcall(typec_init);
14351652
@@ -1437,6 +1654,7 @@ static void __exit typec_exit(void) · 1 unmodified line
14371654 {
14381655 class_destroy(typec_class);
14391656 ida_destroy(&typec_index_ida);
1657+ bus_unregister(&typec_bus);
14401658 }
14411659 module_exit(typec_exit);
14421660
include/linux/mod_devicetable.h+15−0
@@ -746,4 +746,19 @@ struct tb_service_id { · 745 unmodified lines
746746 #define TBSVC_MATCH_PROTOCOL_VERSION 0x0004
747747 #define TBSVC_MATCH_PROTOCOL_REVISION 0x0008
748748
749+/* USB Type-C Alternate Modes */
750+
751+#define TYPEC_ANY_MODE 0x7
752+
753+/**
754+ * struct typec_device_id - USB Type-C alternate mode identifiers
755+ * @svid: Standard or Vendor ID
756+ * @mode: Mode index
757+ */
758+struct typec_device_id {
759+ __u16 svid;
760+ __u8 mode;
761+ kernel_ulong_t driver_data;
762+};
763+
749764 #endif /* LINUX_MOD_DEVICETABLE_H */
include/linux/usb/typec.h+3−11
@@ -5,21 +5,18 @@ · 4 unmodified lines
55
66 #include <linux/types.h>
77
8−/* XXX: Once we have a header for USB Power Delivery, this belongs there */
9−#define ALTMODE_MAX_MODES 6
10−
118 /* USB Type-C Specification releases */
129 #define USB_TYPEC_REV_1_0 0x100 /* 1.0 */
1310 #define USB_TYPEC_REV_1_1 0x110 /* 1.1 */
1411 #define USB_TYPEC_REV_1_2 0x120 /* 1.2 */
1512
16−struct typec_altmode;
1713 struct typec_partner;
1814 struct typec_cable;
1915 struct typec_plug;
2016 struct typec_port;
2117
2218 struct fwnode_handle;
19+struct device;
2320
2421 enum typec_port_type {
2522 TYPEC_PORT_SRC,
@@ -107,7 +104,7 @@ struct typec_altmode_desc { · 81 unmodified lines
107104 u8 mode;
108105 u32 vdo;
109106 /* Only used with ports */
110− enum typec_port_type roles;
107+ enum typec_port_data roles;
111108 };
112109
113110 struct typec_altmode
@@ -186,7 +183,6 @@ struct typec_partner_desc { · 72 unmodified lines
186183 * @dr_set: Set Data Role
187184 * @pr_set: Set Power Role
188185 * @vconn_set: Set VCONN Role
189− * @activate_mode: Enter/exit given Alternate Mode
190186 * @port_type_set: Set port type
191187 *
192188 * Static capabilities of a single USB Type-C port.
@@ -212,12 +208,8 @@ struct typec_capability { · 19 unmodified lines
212208 enum typec_role);
213209 int (*vconn_set)(const struct typec_capability *,
214210 enum typec_role);
215−
216− int (*activate_mode)(const struct typec_capability *,
217− int mode, int activate);
218211 int (*port_type_set)(const struct typec_capability *,
219− enum typec_port_type);
220−
212+ enum typec_port_type);
221213 };
222214
223215 /* Specific to try_role(). Indicates the user want's to clear the preference. */
include/linux/usb/typec_altmode.h+160−0
@@ -0,0 +1,160 @@
1+/* SPDX-License-Identifier: GPL-2.0 */
2+
3+#ifndef __USB_TYPEC_ALTMODE_H
4+#define __USB_TYPEC_ALTMODE_H
5+
6+#include <linux/mod_devicetable.h>
7+#include <linux/usb/typec.h>
8+#include <linux/device.h>
9+
10+#define MODE_DISCOVERY_MAX 6
11+
12+struct typec_altmode_ops;
13+
14+/**
15+ * struct typec_altmode - USB Type-C alternate mode device
16+ * @dev: Driver model's view of this device
17+ * @svid: Standard or Vendor ID (SVID) of the alternate mode
18+ * @mode: Index of the Mode
19+ * @vdo: VDO returned by Discover Modes USB PD command
20+ * @active: Tells has the mode been entered or not
21+ * @desc: Optional human readable description of the mode
22+ * @ops: Operations vector from the driver
23+ */
24+struct typec_altmode {
25+ struct device dev;
26+ u16 svid;
27+ int mode;
28+ u32 vdo;
29+ unsigned int active:1;
30+
31+ char *desc;
32+ const struct typec_altmode_ops *ops;
33+};
34+
35+#define to_typec_altmode(d) container_of(d, struct typec_altmode, dev)
36+
37+static inline void typec_altmode_set_drvdata(struct typec_altmode *altmode,
38+ void *data)
39+{
40+ dev_set_drvdata(&altmode->dev, data);
41+}
42+
43+static inline void *typec_altmode_get_drvdata(struct typec_altmode *altmode)
44+{
45+ return dev_get_drvdata(&altmode->dev);
46+}
47+
48+/**
49+ * struct typec_altmode_ops - Alternate mode specific operations vector
50+ * @enter: Operations to be executed with Enter Mode Command
51+ * @exit: Operations to be executed with Exit Mode Command
52+ * @attention: Callback for Attention Command
53+ * @vdm: Callback for SVID specific commands
54+ * @notify: Communication channel for platform and the alternate mode
55+ * @activate: User callback for Enter/Exit Mode
56+ */
57+struct typec_altmode_ops {
58+ int (*enter)(struct typec_altmode *altmode);
59+ int (*exit)(struct typec_altmode *altmode);
60+ void (*attention)(struct typec_altmode *altmode, u32 vdo);
61+ int (*vdm)(struct typec_altmode *altmode, const u32 hdr,
62+ const u32 *vdo, int cnt);
63+ int (*notify)(struct typec_altmode *altmode, unsigned long conf,
64+ void *data);
65+ int (*activate)(struct typec_altmode *altmode, int activate);
66+};
67+
68+int typec_altmode_enter(struct typec_altmode *altmode);
69+int typec_altmode_exit(struct typec_altmode *altmode);
70+void typec_altmode_attention(struct typec_altmode *altmode, u32 vdo);
71+int typec_altmode_vdm(struct typec_altmode *altmode,
72+ const u32 header, const u32 *vdo, int count);
73+int typec_altmode_notify(struct typec_altmode *altmode, unsigned long conf,
74+ void *data);
75+const struct typec_altmode *
76+typec_altmode_get_partner(struct typec_altmode *altmode);
77+
78+/*
79+ * These are the connector states (USB, Safe and Alt Mode) defined in USB Type-C
80+ * Specification. SVID specific connector states are expected to follow and
81+ * start from the value TYPEC_STATE_MODAL.
82+ */
83+enum {
84+ TYPEC_STATE_SAFE, /* USB Safe State */
85+ TYPEC_STATE_USB, /* USB Operation */
86+ TYPEC_STATE_MODAL, /* Alternate Modes */
87+};
88+
89+/*
90+ * For the muxes there is no difference between Accessory Modes and Alternate
91+ * Modes, so the Accessory Modes are supplied with specific modal state values
92+ * here. Unlike with Alternate Modes, where the mux will be linked with the
93+ * alternate mode device, the mux for Accessory Modes will be linked with the
94+ * port device instead.
95+ *
96+ * Port drivers can use TYPEC_MODE_AUDIO and TYPEC_MODE_DEBUG as the mode
97+ * value for typec_set_mode() when accessory modes are supported.
98+ */
99+enum {
100+ TYPEC_MODE_AUDIO = TYPEC_STATE_MODAL, /* Audio Accessory */
101+ TYPEC_MODE_DEBUG, /* Debug Accessory */
102+};
103+
104+#define TYPEC_MODAL_STATE(_state_) ((_state_) + TYPEC_STATE_MODAL)
105+
106+struct typec_altmode *typec_altmode_get_plug(struct typec_altmode *altmode,
107+ enum typec_plug_index index);
108+void typec_altmode_put_plug(struct typec_altmode *plug);
109+
110+struct typec_altmode *typec_match_altmode(struct typec_altmode **altmodes,
111+ size_t n, u16 svid, u8 mode);
112+
113+struct typec_altmode *
114+typec_altmode_register_notifier(struct device *dev, u16 svid, u8 mode,
115+ struct notifier_block *nb);
116+
117+void typec_altmode_unregister_notifier(struct typec_altmode *adev,
118+ struct notifier_block *nb);
119+
120+/**
121+ * typec_altmode_get_orientation - Get cable plug orientation
122+ * altmode: Handle to the alternate mode
123+ */
124+static inline enum typec_orientation
125+typec_altmode_get_orientation(struct typec_altmode *altmode)
126+{
127+ return typec_get_orientation(typec_altmode2port(altmode));
128+}
129+
130+/**
131+ * struct typec_altmode_driver - USB Type-C alternate mode device driver
132+ * @id_table: Null terminated array of SVIDs
133+ * @probe: Callback for device binding
134+ * @remove: Callback for device unbinding
135+ * @driver: Device driver model driver
136+ *
137+ * These drivers will be bind to the partner alternate mode devices. They will
138+ * handle all SVID specific communication.
139+ */
140+struct typec_altmode_driver {
141+ const struct typec_device_id *id_table;
142+ int (*probe)(struct typec_altmode *altmode);
143+ void (*remove)(struct typec_altmode *altmode);
144+ struct device_driver driver;
145+};
146+
147+#define to_altmode_driver(d) container_of(d, struct typec_altmode_driver, \
148+ driver)
149+
150+#define typec_altmode_register_driver(drv) \
151+ __typec_altmode_register_driver(drv, THIS_MODULE)
152+int __typec_altmode_register_driver(struct typec_altmode_driver *drv,
153+ struct module *module);
154+void typec_altmode_unregister_driver(struct typec_altmode_driver *drv);
155+
156+#define module_typec_altmode_driver(__typec_altmode_driver) \
157+ module_driver(__typec_altmode_driver, typec_altmode_register_driver, \
158+ typec_altmode_unregister_driver)
159+
160+#endif /* __USB_TYPEC_ALTMODE_H */
scripts/mod/devicetable-offsets.c+4−0
@@ -221,5 +221,9 @@ int main(void) · 220 unmodified lines
221221 DEVID_FIELD(tb_service_id, protocol_version);
222222 DEVID_FIELD(tb_service_id, protocol_revision);
223223
224+ DEVID(typec_device_id);
225+ DEVID_FIELD(typec_device_id, svid);
226+ DEVID_FIELD(typec_device_id, mode);
227+
224228 return 0;
225229 }
scripts/mod/file2alias.c+13−0
@@ -1352,6 +1352,19 @@ static int do_tbsvc_entry(const char *filename, void *symval, char *alias) · 1351 unmodified lines
13521352 }
13531353 ADD_TO_DEVTABLE("tbsvc", tb_service_id, do_tbsvc_entry);
13541354
1355+/* Looks like: typec:idNmN */
1356+static int do_typec_entry(const char *filename, void *symval, char *alias)
1357+{
1358+ DEF_FIELD(symval, typec_device_id, svid);
1359+ DEF_FIELD(symval, typec_device_id, mode);
1360+
1361+ sprintf(alias, "typec:id%04X", svid);
1362+ ADD(alias, "m", mode != TYPEC_ANY_MODE, mode);
1363+
1364+ return 1;
1365+}
1366+ADD_TO_DEVTABLE("typec", typec_device_id, do_typec_entry);
1367+
13551368 /* Does namelen bytes of name exactly match the symbol? */
13561369 static bool sym_is(const char *name, unsigned namelen, const char *symbol)
13571370 {

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t