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>
14 typec_altmode_set_mux(struct altmode
*alt
, unsigned long conf
, void *data
)
16 struct typec_mux_state state
;
21 state
.alt
= &alt
->adev
;
25 return alt
->mux
->set(alt
->mux
, &state
);
28 static int typec_altmode_set_state(struct typec_altmode
*adev
,
29 unsigned long conf
, void *data
)
31 bool is_port
= is_typec_port(adev
->dev
.parent
);
32 struct altmode
*port_altmode
;
35 port_altmode
= is_port
? to_altmode(adev
) : to_altmode(adev
)->partner
;
37 ret
= typec_altmode_set_mux(port_altmode
, conf
, data
);
41 blocking_notifier_call_chain(&port_altmode
->nh
, conf
, NULL
);
46 /* -------------------------------------------------------------------------- */
50 * typec_altmode_notify - Communication between the OS and alternate mode driver
51 * @adev: Handle to the alternate mode
52 * @conf: Alternate mode specific configuration value
53 * @data: Alternate mode specific data
55 * The primary purpose for this function is to allow the alternate mode drivers
56 * to tell which pin configuration has been negotiated with the partner. That
57 * information will then be used for example to configure the muxes.
58 * Communication to the other direction is also possible, and low level device
59 * drivers can also send notifications to the alternate mode drivers. The actual
60 * communication will be specific for every SVID.
62 int typec_altmode_notify(struct typec_altmode
*adev
,
63 unsigned long conf
, void *data
)
66 struct altmode
*altmode
;
67 struct altmode
*partner
;
73 altmode
= to_altmode(adev
);
75 if (!altmode
->partner
)
78 is_port
= is_typec_port(adev
->dev
.parent
);
79 partner
= altmode
->partner
;
81 ret
= typec_altmode_set_mux(is_port
? altmode
: partner
, conf
, data
);
85 blocking_notifier_call_chain(is_port
? &altmode
->nh
: &partner
->nh
,
88 if (partner
->adev
.ops
&& partner
->adev
.ops
->notify
)
89 return partner
->adev
.ops
->notify(&partner
->adev
, conf
, data
);
93 EXPORT_SYMBOL_GPL(typec_altmode_notify
);
96 * typec_altmode_enter - Enter Mode
97 * @adev: The alternate mode
98 * @vdo: VDO for the Enter Mode command
100 * The alternate mode drivers use this function to enter mode. The port drivers
101 * use this to inform the alternate mode drivers that the partner has initiated
102 * Enter Mode command. If the alternate mode does not require VDO, @vdo must be
105 int typec_altmode_enter(struct typec_altmode
*adev
, u32
*vdo
)
107 struct altmode
*partner
= to_altmode(adev
)->partner
;
108 struct typec_altmode
*pdev
= &partner
->adev
;
111 if (!adev
|| adev
->active
)
114 if (!pdev
->ops
|| !pdev
->ops
->enter
)
117 if (is_typec_port(pdev
->dev
.parent
) && !pdev
->active
)
120 /* Moving to USB Safe State */
121 ret
= typec_altmode_set_state(adev
, TYPEC_STATE_SAFE
, NULL
);
126 return pdev
->ops
->enter(pdev
, vdo
);
128 EXPORT_SYMBOL_GPL(typec_altmode_enter
);
131 * typec_altmode_exit - Exit Mode
132 * @adev: The alternate mode
134 * The partner of @adev has initiated Exit Mode command.
136 int typec_altmode_exit(struct typec_altmode
*adev
)
138 struct altmode
*partner
= to_altmode(adev
)->partner
;
139 struct typec_altmode
*pdev
= &partner
->adev
;
142 if (!adev
|| !adev
->active
)
145 if (!pdev
->ops
|| !pdev
->ops
->enter
)
148 /* Moving to USB Safe State */
149 ret
= typec_altmode_set_state(adev
, TYPEC_STATE_SAFE
, NULL
);
153 /* Exit Mode command */
154 return pdev
->ops
->exit(pdev
);
156 EXPORT_SYMBOL_GPL(typec_altmode_exit
);
159 * typec_altmode_attention - Attention command
160 * @adev: The alternate mode
161 * @vdo: VDO for the Attention command
163 * Notifies the partner of @adev about Attention command.
165 void typec_altmode_attention(struct typec_altmode
*adev
, u32 vdo
)
167 struct typec_altmode
*pdev
= &to_altmode(adev
)->partner
->adev
;
169 if (pdev
->ops
&& pdev
->ops
->attention
)
170 pdev
->ops
->attention(pdev
, vdo
);
172 EXPORT_SYMBOL_GPL(typec_altmode_attention
);
175 * typec_altmode_vdm - Send Vendor Defined Messages (VDM) to the partner
176 * @adev: Alternate mode handle
177 * @header: VDM Header
178 * @vdo: Array of Vendor Defined Data Objects
179 * @count: Number of Data Objects
181 * The alternate mode drivers use this function for SVID specific communication
182 * with the partner. The port drivers use it to deliver the Structured VDMs
183 * received from the partners to the alternate mode drivers.
185 int typec_altmode_vdm(struct typec_altmode
*adev
,
186 const u32 header
, const u32
*vdo
, int count
)
188 struct typec_altmode
*pdev
;
189 struct altmode
*altmode
;
194 altmode
= to_altmode(adev
);
196 if (!altmode
->partner
)
199 pdev
= &altmode
->partner
->adev
;
201 if (!pdev
->ops
|| !pdev
->ops
->vdm
)
204 return pdev
->ops
->vdm(pdev
, header
, vdo
, count
);
206 EXPORT_SYMBOL_GPL(typec_altmode_vdm
);
208 const struct typec_altmode
*
209 typec_altmode_get_partner(struct typec_altmode
*adev
)
211 return adev
? &to_altmode(adev
)->partner
->adev
: NULL
;
213 EXPORT_SYMBOL_GPL(typec_altmode_get_partner
);
215 /* -------------------------------------------------------------------------- */
216 /* API for the alternate mode drivers */
219 * typec_altmode_get_plug - Find cable plug alternate mode
220 * @adev: Handle to partner alternate mode
221 * @index: Cable plug index
223 * Increment reference count for cable plug alternate mode device. Returns
224 * handle to the cable plug alternate mode, or NULL if none is found.
226 struct typec_altmode
*typec_altmode_get_plug(struct typec_altmode
*adev
,
227 enum typec_plug_index index
)
229 struct altmode
*port
= to_altmode(adev
)->partner
;
231 if (port
->plug
[index
]) {
232 get_device(&port
->plug
[index
]->adev
.dev
);
233 return &port
->plug
[index
]->adev
;
238 EXPORT_SYMBOL_GPL(typec_altmode_get_plug
);
241 * typec_altmode_put_plug - Decrement cable plug alternate mode reference count
242 * @plug: Handle to the cable plug alternate mode
244 void typec_altmode_put_plug(struct typec_altmode
*plug
)
247 put_device(&plug
->dev
);
249 EXPORT_SYMBOL_GPL(typec_altmode_put_plug
);
251 int __typec_altmode_register_driver(struct typec_altmode_driver
*drv
,
252 struct module
*module
)
257 drv
->driver
.owner
= module
;
258 drv
->driver
.bus
= &typec_bus
;
260 return driver_register(&drv
->driver
);
262 EXPORT_SYMBOL_GPL(__typec_altmode_register_driver
);
264 void typec_altmode_unregister_driver(struct typec_altmode_driver
*drv
)
266 driver_unregister(&drv
->driver
);
268 EXPORT_SYMBOL_GPL(typec_altmode_unregister_driver
);
270 /* -------------------------------------------------------------------------- */
271 /* API for the port drivers */
274 * typec_match_altmode - Match SVID and mode to an array of alternate modes
275 * @altmodes: Array of alternate modes
276 * @n: Number of elements in the array, or -1 for NULL terminated arrays
277 * @svid: Standard or Vendor ID to match with
278 * @mode: Mode to match with
280 * Return pointer to an alternate mode with SVID matching @svid, or NULL when no
283 struct typec_altmode
*typec_match_altmode(struct typec_altmode
**altmodes
,
284 size_t n
, u16 svid
, u8 mode
)
288 for (i
= 0; i
< n
; i
++) {
291 if (altmodes
[i
]->svid
== svid
&& altmodes
[i
]->mode
== mode
)
297 EXPORT_SYMBOL_GPL(typec_match_altmode
);
299 /* -------------------------------------------------------------------------- */
302 description_show(struct device
*dev
, struct device_attribute
*attr
, char *buf
)
304 struct typec_altmode
*alt
= to_typec_altmode(dev
);
306 return sprintf(buf
, "%s\n", alt
->desc
? alt
->desc
: "");
308 static DEVICE_ATTR_RO(description
);
310 static struct attribute
*typec_attrs
[] = {
311 &dev_attr_description
.attr
,
314 ATTRIBUTE_GROUPS(typec
);
316 static int typec_match(struct device
*dev
, struct device_driver
*driver
)
318 struct typec_altmode_driver
*drv
= to_altmode_driver(driver
);
319 struct typec_altmode
*altmode
= to_typec_altmode(dev
);
320 const struct typec_device_id
*id
;
322 for (id
= drv
->id_table
; id
->svid
; id
++)
323 if (id
->svid
== altmode
->svid
&&
324 (id
->mode
== TYPEC_ANY_MODE
|| id
->mode
== altmode
->mode
))
329 static int typec_uevent(struct device
*dev
, struct kobj_uevent_env
*env
)
331 struct typec_altmode
*altmode
= to_typec_altmode(dev
);
333 if (add_uevent_var(env
, "SVID=%04X", altmode
->svid
))
336 if (add_uevent_var(env
, "MODE=%u", altmode
->mode
))
339 return add_uevent_var(env
, "MODALIAS=typec:id%04Xm%02X",
340 altmode
->svid
, altmode
->mode
);
343 static int typec_altmode_create_links(struct altmode
*alt
)
345 struct device
*port_dev
= &alt
->partner
->adev
.dev
;
346 struct device
*dev
= &alt
->adev
.dev
;
349 err
= sysfs_create_link(&dev
->kobj
, &port_dev
->kobj
, "port");
353 err
= sysfs_create_link(&port_dev
->kobj
, &dev
->kobj
, "partner");
355 sysfs_remove_link(&dev
->kobj
, "port");
360 static void typec_altmode_remove_links(struct altmode
*alt
)
362 sysfs_remove_link(&alt
->partner
->adev
.dev
.kobj
, "partner");
363 sysfs_remove_link(&alt
->adev
.dev
.kobj
, "port");
366 static int typec_probe(struct device
*dev
)
368 struct typec_altmode_driver
*drv
= to_altmode_driver(dev
->driver
);
369 struct typec_altmode
*adev
= to_typec_altmode(dev
);
370 struct altmode
*altmode
= to_altmode(adev
);
373 /* Fail if the port does not support the alternate mode */
374 if (!altmode
->partner
)
377 ret
= typec_altmode_create_links(altmode
);
379 dev_warn(dev
, "failed to create symlinks\n");
383 ret
= drv
->probe(adev
);
385 typec_altmode_remove_links(altmode
);
390 static int typec_remove(struct device
*dev
)
392 struct typec_altmode_driver
*drv
= to_altmode_driver(dev
->driver
);
393 struct typec_altmode
*adev
= to_typec_altmode(dev
);
394 struct altmode
*altmode
= to_altmode(adev
);
396 typec_altmode_remove_links(altmode
);
399 drv
->remove(to_typec_altmode(dev
));
402 WARN_ON(typec_altmode_set_state(adev
, TYPEC_STATE_SAFE
, NULL
));
403 typec_altmode_update_active(adev
, false);
412 struct bus_type typec_bus
= {
414 .dev_groups
= typec_groups
,
415 .match
= typec_match
,
416 .uevent
= typec_uevent
,
417 .probe
= typec_probe
,
418 .remove
= typec_remove
,