1 // SPDX-License-Identifier: GPL-2.0
3 * Bus for USB Type-C Alternate Modes
5 * Copyright (C) 2018 Intel Corporation
6 * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
9 #include <linux/usb/pd_vdo.h>
13 static inline int typec_altmode_set_mux(struct altmode
*alt
, u8 state
)
15 return alt
->mux
? alt
->mux
->set(alt
->mux
, state
) : 0;
18 static int typec_altmode_set_state(struct typec_altmode
*adev
, int state
)
20 bool is_port
= is_typec_port(adev
->dev
.parent
);
21 struct altmode
*port_altmode
;
24 port_altmode
= is_port
? to_altmode(adev
) : to_altmode(adev
)->partner
;
26 ret
= typec_altmode_set_mux(port_altmode
, state
);
30 blocking_notifier_call_chain(&port_altmode
->nh
, state
, NULL
);
35 /* -------------------------------------------------------------------------- */
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
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.
51 int typec_altmode_notify(struct typec_altmode
*adev
,
52 unsigned long conf
, void *data
)
55 struct altmode
*altmode
;
56 struct altmode
*partner
;
62 altmode
= to_altmode(adev
);
64 if (!altmode
->partner
)
67 is_port
= is_typec_port(adev
->dev
.parent
);
68 partner
= altmode
->partner
;
70 ret
= typec_altmode_set_mux(is_port
? altmode
: partner
, (u8
)conf
);
74 blocking_notifier_call_chain(is_port
? &altmode
->nh
: &partner
->nh
,
77 if (partner
->adev
.ops
&& partner
->adev
.ops
->notify
)
78 return partner
->adev
.ops
->notify(&partner
->adev
, conf
, data
);
82 EXPORT_SYMBOL_GPL(typec_altmode_notify
);
85 * typec_altmode_enter - Enter Mode
86 * @adev: The alternate mode
88 * The alternate mode drivers use this function to enter mode. The port drivers
89 * use this to inform the alternate mode drivers that the partner has initiated
92 int typec_altmode_enter(struct typec_altmode
*adev
)
94 struct altmode
*partner
= to_altmode(adev
)->partner
;
95 struct typec_altmode
*pdev
= &partner
->adev
;
98 if (!adev
|| adev
->active
)
101 if (!pdev
->ops
|| !pdev
->ops
->enter
)
104 /* Moving to USB Safe State */
105 ret
= typec_altmode_set_state(adev
, TYPEC_STATE_SAFE
);
110 return pdev
->ops
->enter(pdev
);
112 EXPORT_SYMBOL_GPL(typec_altmode_enter
);
115 * typec_altmode_exit - Exit Mode
116 * @adev: The alternate mode
118 * The partner of @adev has initiated Exit Mode command.
120 int typec_altmode_exit(struct typec_altmode
*adev
)
122 struct altmode
*partner
= to_altmode(adev
)->partner
;
123 struct typec_altmode
*pdev
= &partner
->adev
;
126 if (!adev
|| !adev
->active
)
129 if (!pdev
->ops
|| !pdev
->ops
->enter
)
132 /* Moving to USB Safe State */
133 ret
= typec_altmode_set_state(adev
, TYPEC_STATE_SAFE
);
137 /* Exit Mode command */
138 return pdev
->ops
->exit(pdev
);
140 EXPORT_SYMBOL_GPL(typec_altmode_exit
);
143 * typec_altmode_attention - Attention command
144 * @adev: The alternate mode
145 * @vdo: VDO for the Attention command
147 * Notifies the partner of @adev about Attention command.
149 void typec_altmode_attention(struct typec_altmode
*adev
, u32 vdo
)
151 struct typec_altmode
*pdev
= &to_altmode(adev
)->partner
->adev
;
153 if (pdev
->ops
&& pdev
->ops
->attention
)
154 pdev
->ops
->attention(pdev
, vdo
);
156 EXPORT_SYMBOL_GPL(typec_altmode_attention
);
159 * typec_altmode_vdm - Send Vendor Defined Messages (VDM) to the partner
160 * @adev: Alternate mode handle
161 * @header: VDM Header
162 * @vdo: Array of Vendor Defined Data Objects
163 * @count: Number of Data Objects
165 * The alternate mode drivers use this function for SVID specific communication
166 * with the partner. The port drivers use it to deliver the Structured VDMs
167 * received from the partners to the alternate mode drivers.
169 int typec_altmode_vdm(struct typec_altmode
*adev
,
170 const u32 header
, const u32
*vdo
, int count
)
172 struct typec_altmode
*pdev
;
173 struct altmode
*altmode
;
178 altmode
= to_altmode(adev
);
180 if (!altmode
->partner
)
183 pdev
= &altmode
->partner
->adev
;
185 if (!pdev
->ops
|| !pdev
->ops
->vdm
)
188 return pdev
->ops
->vdm(pdev
, header
, vdo
, count
);
190 EXPORT_SYMBOL_GPL(typec_altmode_vdm
);
192 const struct typec_altmode
*
193 typec_altmode_get_partner(struct typec_altmode
*adev
)
195 if (!adev
|| !to_altmode(adev
)->partner
)
198 return &to_altmode(adev
)->partner
->adev
;
200 EXPORT_SYMBOL_GPL(typec_altmode_get_partner
);
202 /* -------------------------------------------------------------------------- */
203 /* API for the alternate mode drivers */
206 * typec_altmode_get_plug - Find cable plug alternate mode
207 * @adev: Handle to partner alternate mode
208 * @index: Cable plug index
210 * Increment reference count for cable plug alternate mode device. Returns
211 * handle to the cable plug alternate mode, or NULL if none is found.
213 struct typec_altmode
*typec_altmode_get_plug(struct typec_altmode
*adev
,
214 enum typec_plug_index index
)
216 struct altmode
*port
= to_altmode(adev
)->partner
;
218 if (port
->plug
[index
]) {
219 get_device(&port
->plug
[index
]->adev
.dev
);
220 return &port
->plug
[index
]->adev
;
225 EXPORT_SYMBOL_GPL(typec_altmode_get_plug
);
228 * typec_altmode_put_plug - Decrement cable plug alternate mode reference count
229 * @plug: Handle to the cable plug alternate mode
231 void typec_altmode_put_plug(struct typec_altmode
*plug
)
234 put_device(&plug
->dev
);
236 EXPORT_SYMBOL_GPL(typec_altmode_put_plug
);
238 int __typec_altmode_register_driver(struct typec_altmode_driver
*drv
,
239 struct module
*module
)
244 drv
->driver
.owner
= module
;
245 drv
->driver
.bus
= &typec_bus
;
247 return driver_register(&drv
->driver
);
249 EXPORT_SYMBOL_GPL(__typec_altmode_register_driver
);
251 void typec_altmode_unregister_driver(struct typec_altmode_driver
*drv
)
253 driver_unregister(&drv
->driver
);
255 EXPORT_SYMBOL_GPL(typec_altmode_unregister_driver
);
257 /* -------------------------------------------------------------------------- */
258 /* API for the port drivers */
261 * typec_match_altmode - Match SVID and mode to an array of alternate modes
262 * @altmodes: Array of alternate modes
263 * @n: Number of elements in the array, or -1 for NULL terminated arrays
264 * @svid: Standard or Vendor ID to match with
265 * @mode: Mode to match with
267 * Return pointer to an alternate mode with SVID matching @svid, or NULL when no
270 struct typec_altmode
*typec_match_altmode(struct typec_altmode
**altmodes
,
271 size_t n
, u16 svid
, u8 mode
)
275 for (i
= 0; i
< n
; i
++) {
278 if (altmodes
[i
]->svid
== svid
&& altmodes
[i
]->mode
== mode
)
284 EXPORT_SYMBOL_GPL(typec_match_altmode
);
286 /* -------------------------------------------------------------------------- */
289 description_show(struct device
*dev
, struct device_attribute
*attr
, char *buf
)
291 struct typec_altmode
*alt
= to_typec_altmode(dev
);
293 return sprintf(buf
, "%s\n", alt
->desc
? alt
->desc
: "");
295 static DEVICE_ATTR_RO(description
);
297 static struct attribute
*typec_attrs
[] = {
298 &dev_attr_description
.attr
,
301 ATTRIBUTE_GROUPS(typec
);
303 static int typec_match(struct device
*dev
, struct device_driver
*driver
)
305 struct typec_altmode_driver
*drv
= to_altmode_driver(driver
);
306 struct typec_altmode
*altmode
= to_typec_altmode(dev
);
307 const struct typec_device_id
*id
;
309 for (id
= drv
->id_table
; id
->svid
; id
++)
310 if (id
->svid
== altmode
->svid
&&
311 (id
->mode
== TYPEC_ANY_MODE
|| id
->mode
== altmode
->mode
))
316 static int typec_uevent(struct device
*dev
, struct kobj_uevent_env
*env
)
318 struct typec_altmode
*altmode
= to_typec_altmode(dev
);
320 if (add_uevent_var(env
, "SVID=%04X", altmode
->svid
))
323 if (add_uevent_var(env
, "MODE=%u", altmode
->mode
))
326 return add_uevent_var(env
, "MODALIAS=typec:id%04Xm%02X",
327 altmode
->svid
, altmode
->mode
);
330 static int typec_altmode_create_links(struct altmode
*alt
)
332 struct device
*port_dev
= &alt
->partner
->adev
.dev
;
333 struct device
*dev
= &alt
->adev
.dev
;
336 err
= sysfs_create_link(&dev
->kobj
, &port_dev
->kobj
, "port");
340 err
= sysfs_create_link(&port_dev
->kobj
, &dev
->kobj
, "partner");
342 sysfs_remove_link(&dev
->kobj
, "port");
347 static void typec_altmode_remove_links(struct altmode
*alt
)
349 sysfs_remove_link(&alt
->partner
->adev
.dev
.kobj
, "partner");
350 sysfs_remove_link(&alt
->adev
.dev
.kobj
, "port");
353 static int typec_probe(struct device
*dev
)
355 struct typec_altmode_driver
*drv
= to_altmode_driver(dev
->driver
);
356 struct typec_altmode
*adev
= to_typec_altmode(dev
);
357 struct altmode
*altmode
= to_altmode(adev
);
360 /* Fail if the port does not support the alternate mode */
361 if (!altmode
->partner
)
364 ret
= typec_altmode_create_links(altmode
);
366 dev_warn(dev
, "failed to create symlinks\n");
370 ret
= drv
->probe(adev
);
372 typec_altmode_remove_links(altmode
);
377 static int typec_remove(struct device
*dev
)
379 struct typec_altmode_driver
*drv
= to_altmode_driver(dev
->driver
);
380 struct typec_altmode
*adev
= to_typec_altmode(dev
);
381 struct altmode
*altmode
= to_altmode(adev
);
383 typec_altmode_remove_links(altmode
);
386 drv
->remove(to_typec_altmode(dev
));
389 WARN_ON(typec_altmode_set_state(adev
, TYPEC_STATE_SAFE
));
390 typec_altmode_update_active(adev
, false);
399 struct bus_type typec_bus
= {
401 .dev_groups
= typec_groups
,
402 .match
= typec_match
,
403 .uevent
= typec_uevent
,
404 .probe
= typec_probe
,
405 .remove
= typec_remove
,