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
;
34 port_altmode
= is_port
? to_altmode(adev
) : to_altmode(adev
)->partner
;
36 return typec_altmode_set_mux(port_altmode
, conf
, data
);
39 /* -------------------------------------------------------------------------- */
43 * typec_altmode_notify - Communication between the OS and alternate mode driver
44 * @adev: Handle to the alternate mode
45 * @conf: Alternate mode specific configuration value
46 * @data: Alternate mode specific data
48 * The primary purpose for this function is to allow the alternate mode drivers
49 * to tell which pin configuration has been negotiated with the partner. That
50 * information will then be used for example to configure the muxes.
51 * Communication to the other direction is also possible, and low level device
52 * drivers can also send notifications to the alternate mode drivers. The actual
53 * communication will be specific for every SVID.
55 int typec_altmode_notify(struct typec_altmode
*adev
,
56 unsigned long conf
, void *data
)
59 struct altmode
*altmode
;
60 struct altmode
*partner
;
66 altmode
= to_altmode(adev
);
68 if (!altmode
->partner
)
71 is_port
= is_typec_port(adev
->dev
.parent
);
72 partner
= altmode
->partner
;
74 ret
= typec_altmode_set_mux(is_port
? altmode
: partner
, conf
, data
);
78 if (partner
->adev
.ops
&& partner
->adev
.ops
->notify
)
79 return partner
->adev
.ops
->notify(&partner
->adev
, conf
, data
);
83 EXPORT_SYMBOL_GPL(typec_altmode_notify
);
86 * typec_altmode_enter - Enter Mode
87 * @adev: The alternate mode
88 * @vdo: VDO for the Enter Mode command
90 * The alternate mode drivers use this function to enter mode. The port drivers
91 * use this to inform the alternate mode drivers that the partner has initiated
92 * Enter Mode command. If the alternate mode does not require VDO, @vdo must be
95 int typec_altmode_enter(struct typec_altmode
*adev
, u32
*vdo
)
97 struct altmode
*partner
= to_altmode(adev
)->partner
;
98 struct typec_altmode
*pdev
= &partner
->adev
;
101 if (!adev
|| adev
->active
)
104 if (!pdev
->ops
|| !pdev
->ops
->enter
)
107 if (is_typec_port(pdev
->dev
.parent
) && !pdev
->active
)
110 /* Moving to USB Safe State */
111 ret
= typec_altmode_set_state(adev
, TYPEC_STATE_SAFE
, NULL
);
116 return pdev
->ops
->enter(pdev
, vdo
);
118 EXPORT_SYMBOL_GPL(typec_altmode_enter
);
121 * typec_altmode_exit - Exit Mode
122 * @adev: The alternate mode
124 * The partner of @adev has initiated Exit Mode command.
126 int typec_altmode_exit(struct typec_altmode
*adev
)
128 struct altmode
*partner
= to_altmode(adev
)->partner
;
129 struct typec_altmode
*pdev
= &partner
->adev
;
132 if (!adev
|| !adev
->active
)
135 if (!pdev
->ops
|| !pdev
->ops
->enter
)
138 /* Moving to USB Safe State */
139 ret
= typec_altmode_set_state(adev
, TYPEC_STATE_SAFE
, NULL
);
143 /* Exit Mode command */
144 return pdev
->ops
->exit(pdev
);
146 EXPORT_SYMBOL_GPL(typec_altmode_exit
);
149 * typec_altmode_attention - Attention command
150 * @adev: The alternate mode
151 * @vdo: VDO for the Attention command
153 * Notifies the partner of @adev about Attention command.
155 void typec_altmode_attention(struct typec_altmode
*adev
, u32 vdo
)
157 struct typec_altmode
*pdev
= &to_altmode(adev
)->partner
->adev
;
159 if (pdev
->ops
&& pdev
->ops
->attention
)
160 pdev
->ops
->attention(pdev
, vdo
);
162 EXPORT_SYMBOL_GPL(typec_altmode_attention
);
165 * typec_altmode_vdm - Send Vendor Defined Messages (VDM) to the partner
166 * @adev: Alternate mode handle
167 * @header: VDM Header
168 * @vdo: Array of Vendor Defined Data Objects
169 * @count: Number of Data Objects
171 * The alternate mode drivers use this function for SVID specific communication
172 * with the partner. The port drivers use it to deliver the Structured VDMs
173 * received from the partners to the alternate mode drivers.
175 int typec_altmode_vdm(struct typec_altmode
*adev
,
176 const u32 header
, const u32
*vdo
, int count
)
178 struct typec_altmode
*pdev
;
179 struct altmode
*altmode
;
184 altmode
= to_altmode(adev
);
186 if (!altmode
->partner
)
189 pdev
= &altmode
->partner
->adev
;
191 if (!pdev
->ops
|| !pdev
->ops
->vdm
)
194 return pdev
->ops
->vdm(pdev
, header
, vdo
, count
);
196 EXPORT_SYMBOL_GPL(typec_altmode_vdm
);
198 const struct typec_altmode
*
199 typec_altmode_get_partner(struct typec_altmode
*adev
)
201 if (!adev
|| !to_altmode(adev
)->partner
)
204 return &to_altmode(adev
)->partner
->adev
;
206 EXPORT_SYMBOL_GPL(typec_altmode_get_partner
);
208 /* -------------------------------------------------------------------------- */
209 /* API for the alternate mode drivers */
212 * typec_altmode_get_plug - Find cable plug alternate mode
213 * @adev: Handle to partner alternate mode
214 * @index: Cable plug index
216 * Increment reference count for cable plug alternate mode device. Returns
217 * handle to the cable plug alternate mode, or NULL if none is found.
219 struct typec_altmode
*typec_altmode_get_plug(struct typec_altmode
*adev
,
220 enum typec_plug_index index
)
222 struct altmode
*port
= to_altmode(adev
)->partner
;
224 if (port
->plug
[index
]) {
225 get_device(&port
->plug
[index
]->adev
.dev
);
226 return &port
->plug
[index
]->adev
;
231 EXPORT_SYMBOL_GPL(typec_altmode_get_plug
);
234 * typec_altmode_put_plug - Decrement cable plug alternate mode reference count
235 * @plug: Handle to the cable plug alternate mode
237 void typec_altmode_put_plug(struct typec_altmode
*plug
)
240 put_device(&plug
->dev
);
242 EXPORT_SYMBOL_GPL(typec_altmode_put_plug
);
244 int __typec_altmode_register_driver(struct typec_altmode_driver
*drv
,
245 struct module
*module
)
250 drv
->driver
.owner
= module
;
251 drv
->driver
.bus
= &typec_bus
;
253 return driver_register(&drv
->driver
);
255 EXPORT_SYMBOL_GPL(__typec_altmode_register_driver
);
257 void typec_altmode_unregister_driver(struct typec_altmode_driver
*drv
)
259 driver_unregister(&drv
->driver
);
261 EXPORT_SYMBOL_GPL(typec_altmode_unregister_driver
);
263 /* -------------------------------------------------------------------------- */
264 /* API for the port drivers */
267 * typec_match_altmode - Match SVID and mode to an array of alternate modes
268 * @altmodes: Array of alternate modes
269 * @n: Number of elements in the array, or -1 for NULL terminated arrays
270 * @svid: Standard or Vendor ID to match with
271 * @mode: Mode to match with
273 * Return pointer to an alternate mode with SVID matching @svid, or NULL when no
276 struct typec_altmode
*typec_match_altmode(struct typec_altmode
**altmodes
,
277 size_t n
, u16 svid
, u8 mode
)
281 for (i
= 0; i
< n
; i
++) {
284 if (altmodes
[i
]->svid
== svid
&& altmodes
[i
]->mode
== mode
)
290 EXPORT_SYMBOL_GPL(typec_match_altmode
);
292 /* -------------------------------------------------------------------------- */
295 description_show(struct device
*dev
, struct device_attribute
*attr
, char *buf
)
297 struct typec_altmode
*alt
= to_typec_altmode(dev
);
299 return sprintf(buf
, "%s\n", alt
->desc
? alt
->desc
: "");
301 static DEVICE_ATTR_RO(description
);
303 static struct attribute
*typec_attrs
[] = {
304 &dev_attr_description
.attr
,
307 ATTRIBUTE_GROUPS(typec
);
309 static int typec_match(struct device
*dev
, struct device_driver
*driver
)
311 struct typec_altmode_driver
*drv
= to_altmode_driver(driver
);
312 struct typec_altmode
*altmode
= to_typec_altmode(dev
);
313 const struct typec_device_id
*id
;
315 for (id
= drv
->id_table
; id
->svid
; id
++)
316 if (id
->svid
== altmode
->svid
&&
317 (id
->mode
== TYPEC_ANY_MODE
|| id
->mode
== altmode
->mode
))
322 static int typec_uevent(struct device
*dev
, struct kobj_uevent_env
*env
)
324 struct typec_altmode
*altmode
= to_typec_altmode(dev
);
326 if (add_uevent_var(env
, "SVID=%04X", altmode
->svid
))
329 if (add_uevent_var(env
, "MODE=%u", altmode
->mode
))
332 return add_uevent_var(env
, "MODALIAS=typec:id%04Xm%02X",
333 altmode
->svid
, altmode
->mode
);
336 static int typec_altmode_create_links(struct altmode
*alt
)
338 struct device
*port_dev
= &alt
->partner
->adev
.dev
;
339 struct device
*dev
= &alt
->adev
.dev
;
342 err
= sysfs_create_link(&dev
->kobj
, &port_dev
->kobj
, "port");
346 err
= sysfs_create_link(&port_dev
->kobj
, &dev
->kobj
, "partner");
348 sysfs_remove_link(&dev
->kobj
, "port");
353 static void typec_altmode_remove_links(struct altmode
*alt
)
355 sysfs_remove_link(&alt
->partner
->adev
.dev
.kobj
, "partner");
356 sysfs_remove_link(&alt
->adev
.dev
.kobj
, "port");
359 static int typec_probe(struct device
*dev
)
361 struct typec_altmode_driver
*drv
= to_altmode_driver(dev
->driver
);
362 struct typec_altmode
*adev
= to_typec_altmode(dev
);
363 struct altmode
*altmode
= to_altmode(adev
);
366 /* Fail if the port does not support the alternate mode */
367 if (!altmode
->partner
)
370 ret
= typec_altmode_create_links(altmode
);
372 dev_warn(dev
, "failed to create symlinks\n");
376 ret
= drv
->probe(adev
);
378 typec_altmode_remove_links(altmode
);
383 static int typec_remove(struct device
*dev
)
385 struct typec_altmode_driver
*drv
= to_altmode_driver(dev
->driver
);
386 struct typec_altmode
*adev
= to_typec_altmode(dev
);
387 struct altmode
*altmode
= to_altmode(adev
);
389 typec_altmode_remove_links(altmode
);
392 drv
->remove(to_typec_altmode(dev
));
395 WARN_ON(typec_altmode_set_state(adev
, TYPEC_STATE_SAFE
, NULL
));
396 typec_altmode_update_active(adev
, false);
405 struct bus_type typec_bus
= {
407 .dev_groups
= typec_groups
,
408 .match
= typec_match
,
409 .uevent
= typec_uevent
,
410 .probe
= typec_probe
,
411 .remove
= typec_remove
,