Commit | Line | Data |
---|---|---|
fdc6b21e PM |
1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* | |
3 | * Copyright 2020 Google LLC | |
4 | * | |
5 | * This driver provides the ability to view and manage Type C ports through the | |
6 | * Chrome OS EC. | |
7 | */ | |
8 | ||
9 | #include <linux/acpi.h> | |
10 | #include <linux/module.h> | |
11 | #include <linux/of.h> | |
12 | #include <linux/platform_data/cros_ec_commands.h> | |
7110f5f0 | 13 | #include <linux/platform_data/cros_usbpd_notify.h> |
fdc6b21e | 14 | #include <linux/platform_device.h> |
f6f66811 | 15 | #include <linux/usb/pd_vdo.h> |
410457b9 | 16 | #include <linux/usb/typec_dp.h> |
5b30bd35 | 17 | #include <linux/usb/typec_tbt.h> |
69058096 PM |
18 | |
19 | #include "cros_ec_typec.h" | |
493e699b | 20 | #include "cros_typec_vdm.h" |
fdc6b21e PM |
21 | |
22 | #define DRV_NAME "cros-ec-typec" | |
23 | ||
1903adae | 24 | #define DP_PORT_VDO (DP_CONF_SET_PIN_ASSIGN(BIT(DP_PIN_ASSIGN_C) | BIT(DP_PIN_ASSIGN_D)) | \ |
8d2b28df | 25 | DP_CAP_DFP_D | DP_CAP_RECEPTACLE) |
1ff5d97f | 26 | |
fdc6b21e PM |
27 | static int cros_typec_parse_port_props(struct typec_capability *cap, |
28 | struct fwnode_handle *fwnode, | |
29 | struct device *dev) | |
30 | { | |
31 | const char *buf; | |
32 | int ret; | |
33 | ||
34 | memset(cap, 0, sizeof(*cap)); | |
35 | ret = fwnode_property_read_string(fwnode, "power-role", &buf); | |
36 | if (ret) { | |
37 | dev_err(dev, "power-role not found: %d\n", ret); | |
38 | return ret; | |
39 | } | |
40 | ||
41 | ret = typec_find_port_power_role(buf); | |
42 | if (ret < 0) | |
43 | return ret; | |
44 | cap->type = ret; | |
45 | ||
46 | ret = fwnode_property_read_string(fwnode, "data-role", &buf); | |
47 | if (ret) { | |
48 | dev_err(dev, "data-role not found: %d\n", ret); | |
49 | return ret; | |
50 | } | |
51 | ||
52 | ret = typec_find_port_data_role(buf); | |
53 | if (ret < 0) | |
54 | return ret; | |
55 | cap->data = ret; | |
56 | ||
53eeb073 | 57 | /* Try-power-role is optional. */ |
fdc6b21e PM |
58 | ret = fwnode_property_read_string(fwnode, "try-power-role", &buf); |
59 | if (ret) { | |
53eeb073 PM |
60 | dev_warn(dev, "try-power-role not found: %d\n", ret); |
61 | cap->prefer_role = TYPEC_NO_PREFERRED_ROLE; | |
62 | } else { | |
63 | ret = typec_find_power_role(buf); | |
64 | if (ret < 0) | |
65 | return ret; | |
66 | cap->prefer_role = ret; | |
fdc6b21e PM |
67 | } |
68 | ||
fdc6b21e PM |
69 | cap->fwnode = fwnode; |
70 | ||
71 | return 0; | |
72 | } | |
73 | ||
f28adb41 PM |
74 | static int cros_typec_get_switch_handles(struct cros_typec_port *port, |
75 | struct fwnode_handle *fwnode, | |
76 | struct device *dev) | |
77 | { | |
13aba1e5 VD |
78 | int ret = 0; |
79 | ||
3524fe31 | 80 | port->mux = fwnode_typec_mux_get(fwnode); |
f28adb41 | 81 | if (IS_ERR(port->mux)) { |
13aba1e5 | 82 | ret = PTR_ERR(port->mux); |
2b055bf8 | 83 | dev_err_probe(dev, ret, "Mux handle not found\n"); |
f28adb41 PM |
84 | goto mux_err; |
85 | } | |
86 | ||
1a8912ca PM |
87 | port->retimer = fwnode_typec_retimer_get(fwnode); |
88 | if (IS_ERR(port->retimer)) { | |
13aba1e5 | 89 | ret = PTR_ERR(port->retimer); |
2b055bf8 | 90 | dev_err_probe(dev, ret, "Retimer handle not found\n"); |
1a8912ca PM |
91 | goto retimer_sw_err; |
92 | } | |
93 | ||
f28adb41 PM |
94 | port->ori_sw = fwnode_typec_switch_get(fwnode); |
95 | if (IS_ERR(port->ori_sw)) { | |
13aba1e5 | 96 | ret = PTR_ERR(port->ori_sw); |
2b055bf8 | 97 | dev_err_probe(dev, ret, "Orientation switch handle not found\n"); |
f28adb41 PM |
98 | goto ori_sw_err; |
99 | } | |
100 | ||
101 | port->role_sw = fwnode_usb_role_switch_get(fwnode); | |
102 | if (IS_ERR(port->role_sw)) { | |
13aba1e5 | 103 | ret = PTR_ERR(port->role_sw); |
2b055bf8 | 104 | dev_err_probe(dev, ret, "USB role switch handle not found\n"); |
f28adb41 PM |
105 | goto role_sw_err; |
106 | } | |
107 | ||
108 | return 0; | |
109 | ||
110 | role_sw_err: | |
f28adb41 | 111 | typec_switch_put(port->ori_sw); |
9a8aadcf | 112 | port->ori_sw = NULL; |
d5f66527 | 113 | ori_sw_err: |
1a8912ca | 114 | typec_retimer_put(port->retimer); |
9a8aadcf | 115 | port->retimer = NULL; |
1a8912ca | 116 | retimer_sw_err: |
f28adb41 | 117 | typec_mux_put(port->mux); |
9a8aadcf | 118 | port->mux = NULL; |
d5f66527 | 119 | mux_err: |
13aba1e5 | 120 | return ret; |
f28adb41 PM |
121 | } |
122 | ||
698d4d35 PM |
123 | static int cros_typec_add_partner(struct cros_typec_data *typec, int port_num, |
124 | bool pd_en) | |
125 | { | |
126 | struct cros_typec_port *port = typec->ports[port_num]; | |
127 | struct typec_partner_desc p_desc = { | |
128 | .usb_pd = pd_en, | |
129 | }; | |
130 | int ret = 0; | |
131 | ||
132 | /* | |
133 | * Fill an initial PD identity, which will then be updated with info | |
134 | * from the EC. | |
135 | */ | |
136 | p_desc.identity = &port->p_identity; | |
137 | ||
138 | port->partner = typec_register_partner(port->port, &p_desc); | |
139 | if (IS_ERR(port->partner)) { | |
140 | ret = PTR_ERR(port->partner); | |
141 | port->partner = NULL; | |
142 | } | |
143 | ||
144 | return ret; | |
145 | } | |
146 | ||
15630909 PM |
147 | static void cros_typec_unregister_altmodes(struct cros_typec_data *typec, int port_num, |
148 | bool is_partner) | |
de0f4948 PM |
149 | { |
150 | struct cros_typec_port *port = typec->ports[port_num]; | |
151 | struct cros_typec_altmode_node *node, *tmp; | |
15630909 | 152 | struct list_head *head; |
de0f4948 | 153 | |
15630909 PM |
154 | head = is_partner ? &port->partner_mode_list : &port->plug_mode_list; |
155 | list_for_each_entry_safe(node, tmp, head, list) { | |
de0f4948 PM |
156 | list_del(&node->list); |
157 | typec_unregister_altmode(node->amode); | |
158 | devm_kfree(typec->dev, node); | |
159 | } | |
160 | } | |
161 | ||
1a8912ca PM |
162 | /* |
163 | * Map the Type-C Mux state to retimer state and call the retimer set function. We need this | |
164 | * because we re-use the Type-C mux state for retimers. | |
165 | */ | |
166 | static int cros_typec_retimer_set(struct typec_retimer *retimer, struct typec_mux_state state) | |
167 | { | |
168 | struct typec_retimer_state rstate = { | |
169 | .alt = state.alt, | |
170 | .mode = state.mode, | |
171 | .data = state.data, | |
172 | }; | |
173 | ||
174 | return typec_retimer_set(retimer, &rstate); | |
175 | } | |
176 | ||
b4b06c97 | 177 | static int cros_typec_usb_disconnect_state(struct cros_typec_port *port) |
698d4d35 | 178 | { |
698d4d35 PM |
179 | port->state.alt = NULL; |
180 | port->state.mode = TYPEC_STATE_USB; | |
181 | port->state.data = NULL; | |
182 | ||
183 | usb_role_switch_set_role(port->role_sw, USB_ROLE_NONE); | |
184 | typec_switch_set(port->ori_sw, TYPEC_ORIENTATION_NONE); | |
1a8912ca | 185 | cros_typec_retimer_set(port->retimer, port->state); |
b4b06c97 RM |
186 | |
187 | return typec_mux_set(port->mux, &port->state); | |
188 | } | |
189 | ||
190 | static void cros_typec_remove_partner(struct cros_typec_data *typec, | |
191 | int port_num) | |
192 | { | |
193 | struct cros_typec_port *port = typec->ports[port_num]; | |
194 | ||
639ff208 PM |
195 | if (!port->partner) |
196 | return; | |
197 | ||
b4b06c97 RM |
198 | cros_typec_unregister_altmodes(typec, port_num, true); |
199 | ||
348a2e8c PM |
200 | typec_partner_set_usb_power_delivery(port->partner, NULL); |
201 | usb_power_delivery_unregister_capabilities(port->partner_sink_caps); | |
202 | port->partner_sink_caps = NULL; | |
203 | usb_power_delivery_unregister_capabilities(port->partner_src_caps); | |
204 | port->partner_src_caps = NULL; | |
205 | usb_power_delivery_unregister(port->partner_pd); | |
206 | port->partner_pd = NULL; | |
207 | ||
b4b06c97 | 208 | cros_typec_usb_disconnect_state(port); |
b579f139 | 209 | port->mux_flags = USB_PD_MUX_NONE; |
698d4d35 PM |
210 | |
211 | typec_unregister_partner(port->partner); | |
212 | port->partner = NULL; | |
514acf1c | 213 | memset(&port->p_identity, 0, sizeof(port->p_identity)); |
a906f45d | 214 | port->sop_disc_done = false; |
698d4d35 PM |
215 | } |
216 | ||
8b46a212 PM |
217 | static void cros_typec_remove_cable(struct cros_typec_data *typec, |
218 | int port_num) | |
219 | { | |
220 | struct cros_typec_port *port = typec->ports[port_num]; | |
221 | ||
639ff208 PM |
222 | if (!port->cable) |
223 | return; | |
224 | ||
15630909 PM |
225 | cros_typec_unregister_altmodes(typec, port_num, false); |
226 | ||
f4edab68 PM |
227 | typec_unregister_plug(port->plug); |
228 | port->plug = NULL; | |
8b46a212 PM |
229 | typec_unregister_cable(port->cable); |
230 | port->cable = NULL; | |
231 | memset(&port->c_identity, 0, sizeof(port->c_identity)); | |
232 | port->sop_prime_disc_done = false; | |
233 | } | |
234 | ||
1ff5d97f PM |
235 | static void cros_typec_unregister_port_altmodes(struct cros_typec_port *port) |
236 | { | |
237 | int i; | |
238 | ||
239 | for (i = 0; i < CROS_EC_ALTMODE_MAX; i++) | |
240 | typec_unregister_altmode(port->port_altmode[i]); | |
241 | } | |
242 | ||
5fed73b8 PM |
243 | static void cros_unregister_ports(struct cros_typec_data *typec) |
244 | { | |
245 | int i; | |
246 | ||
247 | for (i = 0; i < typec->num_ports; i++) { | |
248 | if (!typec->ports[i]) | |
249 | continue; | |
7ab5a673 | 250 | |
639ff208 PM |
251 | cros_typec_remove_partner(typec, i); |
252 | cros_typec_remove_cable(typec, i); | |
8b46a212 | 253 | |
f28adb41 PM |
254 | usb_role_switch_put(typec->ports[i]->role_sw); |
255 | typec_switch_put(typec->ports[i]->ori_sw); | |
256 | typec_mux_put(typec->ports[i]->mux); | |
1ff5d97f | 257 | cros_typec_unregister_port_altmodes(typec->ports[i]); |
5fed73b8 PM |
258 | typec_unregister_port(typec->ports[i]->port); |
259 | } | |
260 | } | |
261 | ||
410457b9 | 262 | /* |
1ff5d97f PM |
263 | * Register port alt modes with known values till we start retrieving |
264 | * port capabilities from the EC. | |
410457b9 | 265 | */ |
1ff5d97f | 266 | static int cros_typec_register_port_altmodes(struct cros_typec_data *typec, |
410457b9 PM |
267 | int port_num) |
268 | { | |
269 | struct cros_typec_port *port = typec->ports[port_num]; | |
1ff5d97f PM |
270 | struct typec_altmode_desc desc; |
271 | struct typec_altmode *amode; | |
410457b9 PM |
272 | |
273 | /* All PD capable CrOS devices are assumed to support DP altmode. */ | |
a88f6ef6 SB |
274 | desc.svid = USB_TYPEC_DP_SID; |
275 | desc.mode = USB_TYPEC_DP_MODE; | |
276 | desc.vdo = DP_PORT_VDO; | |
1ff5d97f PM |
277 | amode = typec_port_register_altmode(port->port, &desc); |
278 | if (IS_ERR(amode)) | |
279 | return PTR_ERR(amode); | |
280 | port->port_altmode[CROS_EC_ALTMODE_DP] = amode; | |
c856e3ff | 281 | typec_altmode_set_drvdata(amode, port); |
493e699b | 282 | amode->ops = &port_amode_ops; |
410457b9 | 283 | |
5b30bd35 PM |
284 | /* |
285 | * Register TBT compatibility alt mode. The EC will not enter the mode | |
286 | * if it doesn't support it, so it's safe to register it unconditionally | |
287 | * here for now. | |
288 | */ | |
1ff5d97f | 289 | memset(&desc, 0, sizeof(desc)); |
a88f6ef6 SB |
290 | desc.svid = USB_TYPEC_TBT_SID; |
291 | desc.mode = TYPEC_ANY_MODE; | |
1ff5d97f PM |
292 | amode = typec_port_register_altmode(port->port, &desc); |
293 | if (IS_ERR(amode)) | |
294 | return PTR_ERR(amode); | |
295 | port->port_altmode[CROS_EC_ALTMODE_TBT] = amode; | |
c856e3ff | 296 | typec_altmode_set_drvdata(amode, port); |
493e699b | 297 | amode->ops = &port_amode_ops; |
5b30bd35 | 298 | |
410457b9 PM |
299 | port->state.alt = NULL; |
300 | port->state.mode = TYPEC_STATE_USB; | |
301 | port->state.data = NULL; | |
1ff5d97f PM |
302 | |
303 | return 0; | |
410457b9 PM |
304 | } |
305 | ||
fdc6b21e PM |
306 | static int cros_typec_init_ports(struct cros_typec_data *typec) |
307 | { | |
308 | struct device *dev = typec->dev; | |
309 | struct typec_capability *cap; | |
310 | struct fwnode_handle *fwnode; | |
5fed73b8 | 311 | struct cros_typec_port *cros_port; |
fdc6b21e PM |
312 | const char *port_prop; |
313 | int ret; | |
fdc6b21e PM |
314 | int nports; |
315 | u32 port_num = 0; | |
316 | ||
317 | nports = device_get_child_node_count(dev); | |
318 | if (nports == 0) { | |
319 | dev_err(dev, "No port entries found.\n"); | |
320 | return -ENODEV; | |
321 | } | |
322 | ||
323 | if (nports > typec->num_ports) { | |
324 | dev_err(dev, "More ports listed than can be supported.\n"); | |
325 | return -EINVAL; | |
326 | } | |
327 | ||
328 | /* DT uses "reg" to specify port number. */ | |
329 | port_prop = dev->of_node ? "reg" : "port-number"; | |
330 | device_for_each_child_node(dev, fwnode) { | |
331 | if (fwnode_property_read_u32(fwnode, port_prop, &port_num)) { | |
332 | ret = -EINVAL; | |
333 | dev_err(dev, "No port-number for port, aborting.\n"); | |
334 | goto unregister_ports; | |
335 | } | |
336 | ||
337 | if (port_num >= typec->num_ports) { | |
338 | dev_err(dev, "Invalid port number.\n"); | |
339 | ret = -EINVAL; | |
340 | goto unregister_ports; | |
341 | } | |
342 | ||
343 | dev_dbg(dev, "Registering port %d\n", port_num); | |
344 | ||
5fed73b8 PM |
345 | cros_port = devm_kzalloc(dev, sizeof(*cros_port), GFP_KERNEL); |
346 | if (!cros_port) { | |
fdc6b21e PM |
347 | ret = -ENOMEM; |
348 | goto unregister_ports; | |
349 | } | |
350 | ||
4dc9355c PM |
351 | cros_port->port_num = port_num; |
352 | cros_port->typec_data = typec; | |
5fed73b8 PM |
353 | typec->ports[port_num] = cros_port; |
354 | cap = &cros_port->caps; | |
fdc6b21e PM |
355 | |
356 | ret = cros_typec_parse_port_props(cap, fwnode, dev); | |
357 | if (ret < 0) | |
358 | goto unregister_ports; | |
359 | ||
5fed73b8 PM |
360 | cros_port->port = typec_register_port(dev, cap); |
361 | if (IS_ERR(cros_port->port)) { | |
5fed73b8 | 362 | ret = PTR_ERR(cros_port->port); |
ce838f7d | 363 | dev_err_probe(dev, ret, "Failed to register port %d\n", port_num); |
fdc6b21e PM |
364 | goto unregister_ports; |
365 | } | |
f28adb41 PM |
366 | |
367 | ret = cros_typec_get_switch_handles(cros_port, fwnode, dev); | |
13aba1e5 VD |
368 | if (ret) { |
369 | dev_dbg(dev, "No switch control for port %d, err: %d\n", port_num, ret); | |
370 | if (ret == -EPROBE_DEFER) | |
371 | goto unregister_ports; | |
372 | } | |
410457b9 | 373 | |
1ff5d97f PM |
374 | ret = cros_typec_register_port_altmodes(typec, port_num); |
375 | if (ret) { | |
376 | dev_err(dev, "Failed to register port altmodes\n"); | |
377 | goto unregister_ports; | |
378 | } | |
f6f66811 | 379 | |
c097f229 PM |
380 | cros_port->disc_data = devm_kzalloc(dev, EC_PROTO2_MAX_RESPONSE_SIZE, GFP_KERNEL); |
381 | if (!cros_port->disc_data) { | |
f6f66811 PM |
382 | ret = -ENOMEM; |
383 | goto unregister_ports; | |
384 | } | |
de0f4948 PM |
385 | |
386 | INIT_LIST_HEAD(&cros_port->partner_mode_list); | |
15630909 | 387 | INIT_LIST_HEAD(&cros_port->plug_mode_list); |
fdc6b21e PM |
388 | } |
389 | ||
390 | return 0; | |
391 | ||
392 | unregister_ports: | |
5fed73b8 | 393 | cros_unregister_ports(typec); |
fdc6b21e PM |
394 | return ret; |
395 | } | |
396 | ||
7e7def15 PM |
397 | static int cros_typec_usb_safe_state(struct cros_typec_port *port) |
398 | { | |
1a8912ca | 399 | int ret; |
7e7def15 PM |
400 | port->state.mode = TYPEC_STATE_SAFE; |
401 | ||
1a8912ca PM |
402 | ret = cros_typec_retimer_set(port->retimer, port->state); |
403 | if (!ret) | |
404 | ret = typec_mux_set(port->mux, &port->state); | |
405 | ||
406 | return ret; | |
7e7def15 PM |
407 | } |
408 | ||
aad6ad1b UP |
409 | /** |
410 | * cros_typec_get_cable_vdo() - Get Cable VDO of the connected cable | |
411 | * @port: Type-C port data | |
412 | * @svid: Standard or Vendor ID to match | |
413 | * | |
414 | * Returns the Cable VDO if match is found and returns 0 if match is not found. | |
415 | */ | |
416 | static int cros_typec_get_cable_vdo(struct cros_typec_port *port, u16 svid) | |
417 | { | |
418 | struct list_head *head = &port->plug_mode_list; | |
419 | struct cros_typec_altmode_node *node; | |
420 | u32 ret = 0; | |
421 | ||
422 | list_for_each_entry(node, head, list) { | |
423 | if (node->amode->svid == svid) | |
424 | return node->amode->vdo; | |
425 | } | |
426 | ||
427 | return ret; | |
428 | } | |
429 | ||
5b30bd35 PM |
430 | /* |
431 | * Spoof the VDOs that were likely communicated by the partner for TBT alt | |
432 | * mode. | |
433 | */ | |
434 | static int cros_typec_enable_tbt(struct cros_typec_data *typec, | |
435 | int port_num, | |
436 | struct ec_response_usb_pd_control_v2 *pd_ctrl) | |
437 | { | |
438 | struct cros_typec_port *port = typec->ports[port_num]; | |
439 | struct typec_thunderbolt_data data; | |
440 | int ret; | |
441 | ||
442 | if (typec->pd_ctrl_ver < 2) { | |
443 | dev_err(typec->dev, | |
444 | "PD_CTRL version too old: %d\n", typec->pd_ctrl_ver); | |
445 | return -ENOTSUPP; | |
446 | } | |
447 | ||
448 | /* Device Discover Mode VDO */ | |
449 | data.device_mode = TBT_MODE; | |
450 | ||
451 | if (pd_ctrl->control_flags & USB_PD_CTRL_TBT_LEGACY_ADAPTER) | |
452 | data.device_mode = TBT_SET_ADAPTER(TBT_ADAPTER_TBT3); | |
453 | ||
454 | /* Cable Discover Mode VDO */ | |
455 | data.cable_mode = TBT_MODE; | |
aad6ad1b UP |
456 | |
457 | data.cable_mode |= cros_typec_get_cable_vdo(port, USB_TYPEC_TBT_SID); | |
458 | ||
5b30bd35 PM |
459 | data.cable_mode |= TBT_SET_CABLE_SPEED(pd_ctrl->cable_speed); |
460 | ||
461 | if (pd_ctrl->control_flags & USB_PD_CTRL_OPTICAL_CABLE) | |
462 | data.cable_mode |= TBT_CABLE_OPTICAL; | |
463 | ||
464 | if (pd_ctrl->control_flags & USB_PD_CTRL_ACTIVE_LINK_UNIDIR) | |
465 | data.cable_mode |= TBT_CABLE_LINK_TRAINING; | |
466 | ||
5384cffd | 467 | data.cable_mode |= TBT_SET_CABLE_ROUNDED(pd_ctrl->cable_gen); |
5b30bd35 PM |
468 | |
469 | /* Enter Mode VDO */ | |
470 | data.enter_vdo = TBT_SET_CABLE_SPEED(pd_ctrl->cable_speed); | |
471 | ||
472 | if (pd_ctrl->control_flags & USB_PD_CTRL_ACTIVE_CABLE) | |
473 | data.enter_vdo |= TBT_ENTER_MODE_ACTIVE_CABLE; | |
474 | ||
475 | if (!port->state.alt) { | |
1ff5d97f | 476 | port->state.alt = port->port_altmode[CROS_EC_ALTMODE_TBT]; |
5b30bd35 PM |
477 | ret = cros_typec_usb_safe_state(port); |
478 | if (ret) | |
479 | return ret; | |
480 | } | |
481 | ||
482 | port->state.data = &data; | |
483 | port->state.mode = TYPEC_TBT_MODE; | |
484 | ||
485 | return typec_mux_set(port->mux, &port->state); | |
486 | } | |
487 | ||
410457b9 PM |
488 | /* Spoof the VDOs that were likely communicated by the partner. */ |
489 | static int cros_typec_enable_dp(struct cros_typec_data *typec, | |
490 | int port_num, | |
491 | struct ec_response_usb_pd_control_v2 *pd_ctrl) | |
492 | { | |
493 | struct cros_typec_port *port = typec->ports[port_num]; | |
494 | struct typec_displayport_data dp_data; | |
70ca6c73 UP |
495 | u32 cable_tbt_vdo; |
496 | u32 cable_dp_vdo; | |
410457b9 PM |
497 | int ret; |
498 | ||
499 | if (typec->pd_ctrl_ver < 2) { | |
500 | dev_err(typec->dev, | |
501 | "PD_CTRL version too old: %d\n", typec->pd_ctrl_ver); | |
502 | return -ENOTSUPP; | |
503 | } | |
504 | ||
c5bb32f5 PM |
505 | if (!pd_ctrl->dp_mode) { |
506 | dev_err(typec->dev, "No valid DP mode provided.\n"); | |
507 | return -EINVAL; | |
508 | } | |
509 | ||
410457b9 PM |
510 | /* Status VDO. */ |
511 | dp_data.status = DP_STATUS_ENABLED; | |
512 | if (port->mux_flags & USB_PD_MUX_HPD_IRQ) | |
513 | dp_data.status |= DP_STATUS_IRQ_HPD; | |
514 | if (port->mux_flags & USB_PD_MUX_HPD_LVL) | |
515 | dp_data.status |= DP_STATUS_HPD_STATE; | |
516 | ||
517 | /* Configuration VDO. */ | |
518 | dp_data.conf = DP_CONF_SET_PIN_ASSIGN(pd_ctrl->dp_mode); | |
519 | if (!port->state.alt) { | |
1ff5d97f | 520 | port->state.alt = port->port_altmode[CROS_EC_ALTMODE_DP]; |
410457b9 PM |
521 | ret = cros_typec_usb_safe_state(port); |
522 | if (ret) | |
523 | return ret; | |
524 | } | |
525 | ||
526 | port->state.data = &dp_data; | |
527 | port->state.mode = TYPEC_MODAL_STATE(ffs(pd_ctrl->dp_mode)); | |
528 | ||
70ca6c73 UP |
529 | /* Get cable VDO for cables with DPSID to check DPAM2.1 is supported */ |
530 | cable_dp_vdo = cros_typec_get_cable_vdo(port, USB_TYPEC_DP_SID); | |
531 | ||
532 | /** | |
533 | * Get cable VDO for thunderbolt cables and cables with DPSID but does not | |
534 | * support DPAM2.1. | |
535 | */ | |
536 | cable_tbt_vdo = cros_typec_get_cable_vdo(port, USB_TYPEC_TBT_SID); | |
537 | ||
538 | if (cable_dp_vdo & DP_CAP_DPAM_VERSION) { | |
539 | dp_data.conf |= cable_dp_vdo; | |
540 | } else if (cable_tbt_vdo) { | |
541 | dp_data.conf |= TBT_CABLE_SPEED(cable_tbt_vdo) << DP_CONF_SIGNALLING_SHIFT; | |
542 | ||
543 | /* Cable Type */ | |
544 | if (cable_tbt_vdo & TBT_CABLE_OPTICAL) | |
545 | dp_data.conf |= DP_CONF_CABLE_TYPE_OPTICAL << DP_CONF_CABLE_TYPE_SHIFT; | |
546 | else if (cable_tbt_vdo & TBT_CABLE_RETIMER) | |
547 | dp_data.conf |= DP_CONF_CABLE_TYPE_RE_TIMER << DP_CONF_CABLE_TYPE_SHIFT; | |
548 | else if (cable_tbt_vdo & TBT_CABLE_ACTIVE_PASSIVE) | |
549 | dp_data.conf |= DP_CONF_CABLE_TYPE_RE_DRIVER << DP_CONF_CABLE_TYPE_SHIFT; | |
550 | } else if (PD_IDH_PTYPE(port->c_identity.id_header) == IDH_PTYPE_PCABLE) { | |
551 | dp_data.conf |= VDO_TYPEC_CABLE_SPEED(port->c_identity.vdo[0]) << | |
552 | DP_CONF_SIGNALLING_SHIFT; | |
553 | } | |
554 | ||
1a8912ca PM |
555 | ret = cros_typec_retimer_set(port->retimer, port->state); |
556 | if (!ret) | |
557 | ret = typec_mux_set(port->mux, &port->state); | |
558 | ||
559 | return ret; | |
410457b9 PM |
560 | } |
561 | ||
46c5bbd2 HK |
562 | static int cros_typec_enable_usb4(struct cros_typec_data *typec, |
563 | int port_num, | |
564 | struct ec_response_usb_pd_control_v2 *pd_ctrl) | |
565 | { | |
566 | struct cros_typec_port *port = typec->ports[port_num]; | |
567 | struct enter_usb_data data; | |
568 | ||
569 | data.eudo = EUDO_USB_MODE_USB4 << EUDO_USB_MODE_SHIFT; | |
570 | ||
571 | /* Cable Speed */ | |
572 | data.eudo |= pd_ctrl->cable_speed << EUDO_CABLE_SPEED_SHIFT; | |
573 | ||
574 | /* Cable Type */ | |
575 | if (pd_ctrl->control_flags & USB_PD_CTRL_OPTICAL_CABLE) | |
576 | data.eudo |= EUDO_CABLE_TYPE_OPTICAL << EUDO_CABLE_TYPE_SHIFT; | |
aad6ad1b | 577 | else if (cros_typec_get_cable_vdo(port, USB_TYPEC_TBT_SID) & TBT_CABLE_RETIMER) |
46c5bbd2 | 578 | data.eudo |= EUDO_CABLE_TYPE_RE_TIMER << EUDO_CABLE_TYPE_SHIFT; |
aad6ad1b UP |
579 | else if (pd_ctrl->control_flags & USB_PD_CTRL_ACTIVE_CABLE) |
580 | data.eudo |= EUDO_CABLE_TYPE_RE_DRIVER << EUDO_CABLE_TYPE_SHIFT; | |
46c5bbd2 HK |
581 | |
582 | data.active_link_training = !!(pd_ctrl->control_flags & | |
583 | USB_PD_CTRL_ACTIVE_LINK_UNIDIR); | |
584 | ||
585 | port->state.alt = NULL; | |
586 | port->state.data = &data; | |
587 | port->state.mode = TYPEC_MODE_USB4; | |
588 | ||
589 | return typec_mux_set(port->mux, &port->state); | |
590 | } | |
591 | ||
447b4eb6 | 592 | static int cros_typec_configure_mux(struct cros_typec_data *typec, int port_num, |
447b4eb6 | 593 | struct ec_response_usb_pd_control_v2 *pd_ctrl) |
7e7def15 PM |
594 | { |
595 | struct cros_typec_port *port = typec->ports[port_num]; | |
0d8495dc PM |
596 | struct ec_response_usb_pd_mux_info resp; |
597 | struct ec_params_usb_pd_mux_info req = { | |
598 | .port = port_num, | |
599 | }; | |
8553a979 | 600 | struct ec_params_usb_pd_mux_ack mux_ack; |
7e7def15 PM |
601 | enum typec_orientation orientation; |
602 | int ret; | |
603 | ||
b1d288d9 PM |
604 | ret = cros_ec_cmd(typec->ec, 0, EC_CMD_USB_PD_MUX_INFO, |
605 | &req, sizeof(req), &resp, sizeof(resp)); | |
0d8495dc PM |
606 | if (ret < 0) { |
607 | dev_warn(typec->dev, "Failed to get mux info for port: %d, err = %d\n", | |
608 | port_num, ret); | |
609 | return ret; | |
610 | } | |
611 | ||
53a0023c | 612 | /* No change needs to be made, let's exit early. */ |
0d8495dc | 613 | if (port->mux_flags == resp.flags && port->role == pd_ctrl->role) |
53a0023c PM |
614 | return 0; |
615 | ||
0d8495dc | 616 | port->mux_flags = resp.flags; |
53a0023c PM |
617 | port->role = pd_ctrl->role; |
618 | ||
0d8495dc | 619 | if (port->mux_flags == USB_PD_MUX_NONE) { |
b4b06c97 RM |
620 | ret = cros_typec_usb_disconnect_state(port); |
621 | goto mux_ack; | |
622 | } | |
7e7def15 | 623 | |
0d8495dc | 624 | if (port->mux_flags & USB_PD_MUX_POLARITY_INVERTED) |
7e7def15 PM |
625 | orientation = TYPEC_ORIENTATION_REVERSE; |
626 | else | |
627 | orientation = TYPEC_ORIENTATION_NORMAL; | |
628 | ||
629 | ret = typec_switch_set(port->ori_sw, orientation); | |
630 | if (ret) | |
631 | return ret; | |
632 | ||
a7723365 AS |
633 | ret = usb_role_switch_set_role(typec->ports[port_num]->role_sw, |
634 | pd_ctrl->role & PD_CTRL_RESP_ROLE_DATA | |
635 | ? USB_ROLE_HOST : USB_ROLE_DEVICE); | |
636 | if (ret) | |
637 | return ret; | |
638 | ||
0d8495dc | 639 | if (port->mux_flags & USB_PD_MUX_USB4_ENABLED) { |
46c5bbd2 | 640 | ret = cros_typec_enable_usb4(typec, port_num, pd_ctrl); |
0d8495dc | 641 | } else if (port->mux_flags & USB_PD_MUX_TBT_COMPAT_ENABLED) { |
5b30bd35 | 642 | ret = cros_typec_enable_tbt(typec, port_num, pd_ctrl); |
0d8495dc | 643 | } else if (port->mux_flags & USB_PD_MUX_DP_ENABLED) { |
410457b9 | 644 | ret = cros_typec_enable_dp(typec, port_num, pd_ctrl); |
0d8495dc | 645 | } else if (port->mux_flags & USB_PD_MUX_SAFE_MODE) { |
7e7def15 | 646 | ret = cros_typec_usb_safe_state(port); |
0d8495dc | 647 | } else if (port->mux_flags & USB_PD_MUX_USB_ENABLED) { |
410457b9 PM |
648 | port->state.alt = NULL; |
649 | port->state.mode = TYPEC_STATE_USB; | |
1a8912ca PM |
650 | |
651 | ret = cros_typec_retimer_set(port->retimer, port->state); | |
652 | if (!ret) | |
653 | ret = typec_mux_set(port->mux, &port->state); | |
410457b9 | 654 | } else { |
6ae9b5ff PM |
655 | dev_dbg(typec->dev, |
656 | "Unrecognized mode requested, mux flags: %x\n", | |
0d8495dc | 657 | port->mux_flags); |
9d33ea33 | 658 | } |
7e7def15 | 659 | |
b4b06c97 | 660 | mux_ack: |
8553a979 UP |
661 | if (!typec->needs_mux_ack) |
662 | return ret; | |
663 | ||
664 | /* Sending Acknowledgment to EC */ | |
665 | mux_ack.port = port_num; | |
666 | ||
b1d288d9 PM |
667 | if (cros_ec_cmd(typec->ec, 0, EC_CMD_USB_PD_MUX_ACK, &mux_ack, |
668 | sizeof(mux_ack), NULL, 0) < 0) | |
8553a979 UP |
669 | dev_warn(typec->dev, |
670 | "Failed to send Mux ACK to EC for port: %d\n", | |
671 | port_num); | |
672 | ||
a7723365 | 673 | return ret; |
ad7c0510 PM |
674 | } |
675 | ||
0498710b PM |
676 | static void cros_typec_set_port_params_v0(struct cros_typec_data *typec, |
677 | int port_num, struct ec_response_usb_pd_control *resp) | |
678 | { | |
679 | struct typec_port *port = typec->ports[port_num]->port; | |
680 | enum typec_orientation polarity; | |
681 | ||
682 | if (!resp->enabled) | |
683 | polarity = TYPEC_ORIENTATION_NONE; | |
684 | else if (!resp->polarity) | |
685 | polarity = TYPEC_ORIENTATION_NORMAL; | |
686 | else | |
687 | polarity = TYPEC_ORIENTATION_REVERSE; | |
688 | ||
689 | typec_set_pwr_role(port, resp->role ? TYPEC_SOURCE : TYPEC_SINK); | |
690 | typec_set_orientation(port, polarity); | |
691 | } | |
692 | ||
693 | static void cros_typec_set_port_params_v1(struct cros_typec_data *typec, | |
694 | int port_num, struct ec_response_usb_pd_control_v1 *resp) | |
695 | { | |
696 | struct typec_port *port = typec->ports[port_num]->port; | |
697 | enum typec_orientation polarity; | |
698 | bool pd_en; | |
699 | int ret; | |
700 | ||
701 | if (!(resp->enabled & PD_CTRL_RESP_ENABLED_CONNECTED)) | |
702 | polarity = TYPEC_ORIENTATION_NONE; | |
703 | else if (!resp->polarity) | |
704 | polarity = TYPEC_ORIENTATION_NORMAL; | |
705 | else | |
706 | polarity = TYPEC_ORIENTATION_REVERSE; | |
707 | typec_set_orientation(port, polarity); | |
708 | typec_set_data_role(port, resp->role & PD_CTRL_RESP_ROLE_DATA ? | |
709 | TYPEC_HOST : TYPEC_DEVICE); | |
710 | typec_set_pwr_role(port, resp->role & PD_CTRL_RESP_ROLE_POWER ? | |
711 | TYPEC_SOURCE : TYPEC_SINK); | |
712 | typec_set_vconn_role(port, resp->role & PD_CTRL_RESP_ROLE_VCONN ? | |
713 | TYPEC_SOURCE : TYPEC_SINK); | |
714 | ||
715 | /* Register/remove partners when a connect/disconnect occurs. */ | |
716 | if (resp->enabled & PD_CTRL_RESP_ENABLED_CONNECTED) { | |
717 | if (typec->ports[port_num]->partner) | |
718 | return; | |
719 | ||
720 | pd_en = resp->enabled & PD_CTRL_RESP_ENABLED_PD_CAPABLE; | |
721 | ret = cros_typec_add_partner(typec, port_num, pd_en); | |
722 | if (ret) | |
723 | dev_warn(typec->dev, | |
724 | "Failed to register partner on port: %d\n", | |
725 | port_num); | |
726 | } else { | |
639ff208 PM |
727 | cros_typec_remove_partner(typec, port_num); |
728 | cros_typec_remove_cable(typec, port_num); | |
9d33ea33 | 729 | } |
0498710b PM |
730 | } |
731 | ||
15630909 PM |
732 | /* |
733 | * Helper function to register partner/plug altmodes. | |
734 | */ | |
735 | static int cros_typec_register_altmodes(struct cros_typec_data *typec, int port_num, | |
736 | bool is_partner) | |
de0f4948 PM |
737 | { |
738 | struct cros_typec_port *port = typec->ports[port_num]; | |
c097f229 | 739 | struct ec_response_typec_discovery *sop_disc = port->disc_data; |
de0f4948 PM |
740 | struct cros_typec_altmode_node *node; |
741 | struct typec_altmode_desc desc; | |
742 | struct typec_altmode *amode; | |
59922976 | 743 | int num_altmodes = 0; |
de0f4948 PM |
744 | int ret = 0; |
745 | int i, j; | |
746 | ||
747 | for (i = 0; i < sop_disc->svid_count; i++) { | |
748 | for (j = 0; j < sop_disc->svids[i].mode_count; j++) { | |
749 | memset(&desc, 0, sizeof(desc)); | |
750 | desc.svid = sop_disc->svids[i].svid; | |
4e477663 | 751 | desc.mode = j + 1; |
de0f4948 PM |
752 | desc.vdo = sop_disc->svids[i].mode_vdo[j]; |
753 | ||
15630909 PM |
754 | if (is_partner) |
755 | amode = typec_partner_register_altmode(port->partner, &desc); | |
756 | else | |
757 | amode = typec_plug_register_altmode(port->plug, &desc); | |
758 | ||
de0f4948 PM |
759 | if (IS_ERR(amode)) { |
760 | ret = PTR_ERR(amode); | |
761 | goto err_cleanup; | |
762 | } | |
763 | ||
764 | /* If no memory is available we should unregister and exit. */ | |
765 | node = devm_kzalloc(typec->dev, sizeof(*node), GFP_KERNEL); | |
766 | if (!node) { | |
767 | ret = -ENOMEM; | |
768 | typec_unregister_altmode(amode); | |
769 | goto err_cleanup; | |
770 | } | |
771 | ||
772 | node->amode = amode; | |
15630909 PM |
773 | |
774 | if (is_partner) | |
775 | list_add_tail(&node->list, &port->partner_mode_list); | |
776 | else | |
777 | list_add_tail(&node->list, &port->plug_mode_list); | |
59922976 | 778 | num_altmodes++; |
de0f4948 PM |
779 | } |
780 | } | |
781 | ||
15630909 PM |
782 | if (is_partner) |
783 | ret = typec_partner_set_num_altmodes(port->partner, num_altmodes); | |
784 | else | |
785 | ret = typec_plug_set_num_altmodes(port->plug, num_altmodes); | |
786 | ||
59922976 | 787 | if (ret < 0) { |
15630909 PM |
788 | dev_err(typec->dev, "Unable to set %s num_altmodes for port: %d\n", |
789 | is_partner ? "partner" : "plug", port_num); | |
59922976 PM |
790 | goto err_cleanup; |
791 | } | |
792 | ||
de0f4948 | 793 | return 0; |
7e7def15 | 794 | |
de0f4948 | 795 | err_cleanup: |
15630909 | 796 | cros_typec_unregister_altmodes(typec, port_num, is_partner); |
a7723365 | 797 | return ret; |
ad7c0510 PM |
798 | } |
799 | ||
8fab2755 PM |
800 | /* |
801 | * Parse the PD identity data from the EC PD discovery responses and copy that to the supplied | |
802 | * PD identity struct. | |
803 | */ | |
804 | static void cros_typec_parse_pd_identity(struct usb_pd_identity *id, | |
805 | struct ec_response_typec_discovery *disc) | |
806 | { | |
807 | int i; | |
808 | ||
809 | /* First, update the PD identity VDOs for the partner. */ | |
810 | if (disc->identity_count > 0) | |
811 | id->id_header = disc->discovery_vdo[0]; | |
812 | if (disc->identity_count > 1) | |
813 | id->cert_stat = disc->discovery_vdo[1]; | |
814 | if (disc->identity_count > 2) | |
815 | id->product = disc->discovery_vdo[2]; | |
816 | ||
817 | /* Copy the remaining identity VDOs till a maximum of 6. */ | |
818 | for (i = 3; i < disc->identity_count && i < VDO_MAX_OBJECTS; i++) | |
819 | id->vdo[i - 3] = disc->discovery_vdo[i]; | |
820 | } | |
821 | ||
3b3dd1f0 | 822 | static int cros_typec_handle_sop_prime_disc(struct cros_typec_data *typec, int port_num, u16 pd_revision) |
8b46a212 PM |
823 | { |
824 | struct cros_typec_port *port = typec->ports[port_num]; | |
825 | struct ec_response_typec_discovery *disc = port->disc_data; | |
f4edab68 PM |
826 | struct typec_cable_desc c_desc = {}; |
827 | struct typec_plug_desc p_desc; | |
8b46a212 PM |
828 | struct ec_params_typec_discovery req = { |
829 | .port = port_num, | |
830 | .partner_type = TYPEC_PARTNER_SOP_PRIME, | |
831 | }; | |
72d6e32b | 832 | u32 cable_plug_type; |
8b46a212 PM |
833 | int ret = 0; |
834 | ||
835 | memset(disc, 0, EC_PROTO2_MAX_RESPONSE_SIZE); | |
b1d288d9 PM |
836 | ret = cros_ec_cmd(typec->ec, 0, EC_CMD_TYPEC_DISCOVERY, &req, sizeof(req), |
837 | disc, EC_PROTO2_MAX_RESPONSE_SIZE); | |
8b46a212 PM |
838 | if (ret < 0) { |
839 | dev_err(typec->dev, "Failed to get SOP' discovery data for port: %d\n", port_num); | |
840 | goto sop_prime_disc_exit; | |
841 | } | |
842 | ||
843 | /* Parse the PD identity data, even if only 0s were returned. */ | |
844 | cros_typec_parse_pd_identity(&port->c_identity, disc); | |
845 | ||
72d6e32b PM |
846 | if (disc->identity_count != 0) { |
847 | cable_plug_type = VDO_TYPEC_CABLE_TYPE(port->c_identity.vdo[0]); | |
848 | switch (cable_plug_type) { | |
849 | case CABLE_ATYPE: | |
f4edab68 | 850 | c_desc.type = USB_PLUG_TYPE_A; |
72d6e32b PM |
851 | break; |
852 | case CABLE_BTYPE: | |
f4edab68 | 853 | c_desc.type = USB_PLUG_TYPE_B; |
72d6e32b PM |
854 | break; |
855 | case CABLE_CTYPE: | |
f4edab68 | 856 | c_desc.type = USB_PLUG_TYPE_C; |
72d6e32b PM |
857 | break; |
858 | case CABLE_CAPTIVE: | |
f4edab68 | 859 | c_desc.type = USB_PLUG_CAPTIVE; |
72d6e32b PM |
860 | break; |
861 | default: | |
f4edab68 | 862 | c_desc.type = USB_PLUG_NONE; |
72d6e32b | 863 | } |
f4edab68 | 864 | c_desc.active = PD_IDH_PTYPE(port->c_identity.id_header) == IDH_PTYPE_ACABLE; |
72d6e32b | 865 | } |
8b46a212 | 866 | |
f4edab68 | 867 | c_desc.identity = &port->c_identity; |
3b3dd1f0 | 868 | c_desc.pd_revision = pd_revision; |
8b46a212 | 869 | |
f4edab68 | 870 | port->cable = typec_register_cable(port->port, &c_desc); |
8b46a212 PM |
871 | if (IS_ERR(port->cable)) { |
872 | ret = PTR_ERR(port->cable); | |
873 | port->cable = NULL; | |
f4edab68 PM |
874 | goto sop_prime_disc_exit; |
875 | } | |
876 | ||
877 | p_desc.index = TYPEC_PLUG_SOP_P; | |
878 | port->plug = typec_register_plug(port->cable, &p_desc); | |
879 | if (IS_ERR(port->plug)) { | |
880 | ret = PTR_ERR(port->plug); | |
881 | port->plug = NULL; | |
882 | goto sop_prime_disc_exit; | |
8b46a212 PM |
883 | } |
884 | ||
15630909 PM |
885 | ret = cros_typec_register_altmodes(typec, port_num, false); |
886 | if (ret < 0) { | |
887 | dev_err(typec->dev, "Failed to register plug altmodes, port: %d\n", port_num); | |
888 | goto sop_prime_disc_exit; | |
889 | } | |
890 | ||
f4edab68 PM |
891 | return 0; |
892 | ||
8b46a212 | 893 | sop_prime_disc_exit: |
f4edab68 | 894 | cros_typec_remove_cable(typec, port_num); |
8b46a212 PM |
895 | return ret; |
896 | } | |
897 | ||
cefc011f | 898 | static int cros_typec_handle_sop_disc(struct cros_typec_data *typec, int port_num, u16 pd_revision) |
f6f66811 PM |
899 | { |
900 | struct cros_typec_port *port = typec->ports[port_num]; | |
c097f229 | 901 | struct ec_response_typec_discovery *sop_disc = port->disc_data; |
f6f66811 PM |
902 | struct ec_params_typec_discovery req = { |
903 | .port = port_num, | |
904 | .partner_type = TYPEC_PARTNER_SOP, | |
905 | }; | |
906 | int ret = 0; | |
f6f66811 PM |
907 | |
908 | if (!port->partner) { | |
909 | dev_err(typec->dev, | |
910 | "SOP Discovery received without partner registered, port: %d\n", | |
911 | port_num); | |
912 | ret = -EINVAL; | |
913 | goto disc_exit; | |
914 | } | |
915 | ||
64eaa0fa | 916 | typec_partner_set_pd_revision(port->partner, pd_revision); |
cefc011f | 917 | |
f6f66811 | 918 | memset(sop_disc, 0, EC_PROTO2_MAX_RESPONSE_SIZE); |
b1d288d9 PM |
919 | ret = cros_ec_cmd(typec->ec, 0, EC_CMD_TYPEC_DISCOVERY, &req, sizeof(req), |
920 | sop_disc, EC_PROTO2_MAX_RESPONSE_SIZE); | |
f6f66811 PM |
921 | if (ret < 0) { |
922 | dev_err(typec->dev, "Failed to get SOP discovery data for port: %d\n", port_num); | |
923 | goto disc_exit; | |
924 | } | |
925 | ||
8fab2755 | 926 | cros_typec_parse_pd_identity(&port->p_identity, sop_disc); |
f6f66811 PM |
927 | |
928 | ret = typec_partner_set_identity(port->partner); | |
de0f4948 PM |
929 | if (ret < 0) { |
930 | dev_err(typec->dev, "Failed to update partner PD identity, port: %d\n", port_num); | |
931 | goto disc_exit; | |
932 | } | |
933 | ||
15630909 | 934 | ret = cros_typec_register_altmodes(typec, port_num, true); |
de0f4948 PM |
935 | if (ret < 0) { |
936 | dev_err(typec->dev, "Failed to register partner altmodes, port: %d\n", port_num); | |
937 | goto disc_exit; | |
938 | } | |
f6f66811 PM |
939 | |
940 | disc_exit: | |
941 | return ret; | |
942 | } | |
943 | ||
c8ec21c6 PM |
944 | static int cros_typec_send_clear_event(struct cros_typec_data *typec, int port_num, u32 events_mask) |
945 | { | |
946 | struct ec_params_typec_control req = { | |
947 | .port = port_num, | |
948 | .command = TYPEC_CONTROL_COMMAND_CLEAR_EVENTS, | |
949 | .clear_events_mask = events_mask, | |
950 | }; | |
951 | ||
b1d288d9 PM |
952 | return cros_ec_cmd(typec->ec, 0, EC_CMD_TYPEC_CONTROL, &req, |
953 | sizeof(req), NULL, 0); | |
c8ec21c6 PM |
954 | } |
955 | ||
348a2e8c PM |
956 | static void cros_typec_register_partner_pdos(struct cros_typec_data *typec, |
957 | struct ec_response_typec_status *resp, int port_num) | |
958 | { | |
959 | struct usb_power_delivery_capabilities_desc caps_desc = {}; | |
960 | struct usb_power_delivery_desc desc = { | |
961 | .revision = (le16_to_cpu(resp->sop_revision) & 0xff00) >> 4, | |
962 | }; | |
963 | struct cros_typec_port *port = typec->ports[port_num]; | |
964 | ||
965 | if (!port->partner || port->partner_pd) | |
966 | return; | |
967 | ||
968 | /* If no caps are available, don't bother creating a device. */ | |
969 | if (!resp->source_cap_count && !resp->sink_cap_count) | |
970 | return; | |
971 | ||
ab3593ee | 972 | port->partner_pd = typec_partner_usb_power_delivery_register(port->partner, &desc); |
348a2e8c PM |
973 | if (IS_ERR(port->partner_pd)) { |
974 | dev_warn(typec->dev, "Failed to register partner PD device, port: %d\n", port_num); | |
975 | return; | |
976 | } | |
977 | ||
978 | typec_partner_set_usb_power_delivery(port->partner, port->partner_pd); | |
979 | ||
980 | memcpy(caps_desc.pdo, resp->source_cap_pdos, sizeof(u32) * resp->source_cap_count); | |
981 | caps_desc.role = TYPEC_SOURCE; | |
982 | port->partner_src_caps = usb_power_delivery_register_capabilities(port->partner_pd, | |
983 | &caps_desc); | |
984 | if (IS_ERR(port->partner_src_caps)) | |
985 | dev_warn(typec->dev, "Failed to register source caps, port: %d\n", port_num); | |
986 | ||
987 | memset(&caps_desc, 0, sizeof(caps_desc)); | |
988 | memcpy(caps_desc.pdo, resp->sink_cap_pdos, sizeof(u32) * resp->sink_cap_count); | |
989 | caps_desc.role = TYPEC_SINK; | |
990 | port->partner_sink_caps = usb_power_delivery_register_capabilities(port->partner_pd, | |
991 | &caps_desc); | |
992 | if (IS_ERR(port->partner_sink_caps)) | |
993 | dev_warn(typec->dev, "Failed to register sink caps, port: %d\n", port_num); | |
994 | } | |
995 | ||
80f8cef6 PM |
996 | static void cros_typec_handle_status(struct cros_typec_data *typec, int port_num) |
997 | { | |
998 | struct ec_response_typec_status resp; | |
999 | struct ec_params_typec_status req = { | |
1000 | .port = port_num, | |
1001 | }; | |
1002 | int ret; | |
1003 | ||
b1d288d9 PM |
1004 | ret = cros_ec_cmd(typec->ec, 0, EC_CMD_TYPEC_STATUS, &req, sizeof(req), |
1005 | &resp, sizeof(resp)); | |
80f8cef6 PM |
1006 | if (ret < 0) { |
1007 | dev_warn(typec->dev, "EC_CMD_TYPEC_STATUS failed for port: %d\n", port_num); | |
1008 | return; | |
1009 | } | |
1010 | ||
944b3a63 PM |
1011 | /* If we got a hard reset, unregister everything and return. */ |
1012 | if (resp.events & PD_STATUS_EVENT_HARD_RESET) { | |
1013 | cros_typec_remove_partner(typec, port_num); | |
1014 | cros_typec_remove_cable(typec, port_num); | |
1015 | ||
1016 | ret = cros_typec_send_clear_event(typec, port_num, | |
1017 | PD_STATUS_EVENT_HARD_RESET); | |
1018 | if (ret < 0) | |
1019 | dev_warn(typec->dev, | |
1020 | "Failed hard reset event clear, port: %d\n", port_num); | |
1021 | return; | |
1022 | } | |
1023 | ||
80f8cef6 | 1024 | /* Handle any events appropriately. */ |
a906f45d | 1025 | if (resp.events & PD_STATUS_EVENT_SOP_DISC_DONE && !typec->ports[port_num]->sop_disc_done) { |
cefc011f BL |
1026 | u16 sop_revision; |
1027 | ||
1028 | /* Convert BCD to the format preferred by the TypeC framework */ | |
1029 | sop_revision = (le16_to_cpu(resp.sop_revision) & 0xff00) >> 4; | |
1030 | ret = cros_typec_handle_sop_disc(typec, port_num, sop_revision); | |
a906f45d | 1031 | if (ret < 0) |
f6f66811 | 1032 | dev_err(typec->dev, "Couldn't parse SOP Disc data, port: %d\n", port_num); |
c8ec21c6 | 1033 | else { |
a906f45d | 1034 | typec->ports[port_num]->sop_disc_done = true; |
c8ec21c6 PM |
1035 | ret = cros_typec_send_clear_event(typec, port_num, |
1036 | PD_STATUS_EVENT_SOP_DISC_DONE); | |
1037 | if (ret < 0) | |
1038 | dev_warn(typec->dev, | |
1039 | "Failed SOP Disc event clear, port: %d\n", port_num); | |
1040 | } | |
0371616d BL |
1041 | if (resp.sop_connected) |
1042 | typec_set_pwr_opmode(typec->ports[port_num]->port, TYPEC_PWR_MODE_PD); | |
348a2e8c PM |
1043 | |
1044 | cros_typec_register_partner_pdos(typec, &resp, port_num); | |
80f8cef6 | 1045 | } |
8b46a212 PM |
1046 | |
1047 | if (resp.events & PD_STATUS_EVENT_SOP_PRIME_DISC_DONE && | |
1048 | !typec->ports[port_num]->sop_prime_disc_done) { | |
3b3dd1f0 BL |
1049 | u16 sop_prime_revision; |
1050 | ||
1051 | /* Convert BCD to the format preferred by the TypeC framework */ | |
1052 | sop_prime_revision = (le16_to_cpu(resp.sop_prime_revision) & 0xff00) >> 4; | |
1053 | ret = cros_typec_handle_sop_prime_disc(typec, port_num, sop_prime_revision); | |
8b46a212 PM |
1054 | if (ret < 0) |
1055 | dev_err(typec->dev, "Couldn't parse SOP' Disc data, port: %d\n", port_num); | |
c8ec21c6 | 1056 | else { |
8b46a212 | 1057 | typec->ports[port_num]->sop_prime_disc_done = true; |
c8ec21c6 PM |
1058 | ret = cros_typec_send_clear_event(typec, port_num, |
1059 | PD_STATUS_EVENT_SOP_PRIME_DISC_DONE); | |
1060 | if (ret < 0) | |
1061 | dev_warn(typec->dev, | |
1062 | "Failed SOP Disc event clear, port: %d\n", port_num); | |
1063 | } | |
8b46a212 | 1064 | } |
50ed638b PM |
1065 | |
1066 | if (resp.events & PD_STATUS_EVENT_VDM_REQ_REPLY) { | |
1067 | cros_typec_handle_vdm_response(typec, port_num); | |
1068 | ret = cros_typec_send_clear_event(typec, port_num, PD_STATUS_EVENT_VDM_REQ_REPLY); | |
1069 | if (ret < 0) | |
1070 | dev_warn(typec->dev, "Failed VDM Reply event clear, port: %d\n", port_num); | |
1071 | } | |
f54c013e PM |
1072 | |
1073 | if (resp.events & PD_STATUS_EVENT_VDM_ATTENTION) { | |
1074 | cros_typec_handle_vdm_attention(typec, port_num); | |
1075 | ret = cros_typec_send_clear_event(typec, port_num, PD_STATUS_EVENT_VDM_ATTENTION); | |
1076 | if (ret < 0) | |
b0d8a677 | 1077 | dev_warn(typec->dev, "Failed VDM attention event clear, port: %d\n", |
f54c013e PM |
1078 | port_num); |
1079 | } | |
80f8cef6 PM |
1080 | } |
1081 | ||
ad7c0510 PM |
1082 | static int cros_typec_port_update(struct cros_typec_data *typec, int port_num) |
1083 | { | |
1084 | struct ec_params_usb_pd_control req; | |
410457b9 | 1085 | struct ec_response_usb_pd_control_v2 resp; |
ad7c0510 PM |
1086 | int ret; |
1087 | ||
1088 | if (port_num < 0 || port_num >= typec->num_ports) { | |
1089 | dev_err(typec->dev, "cannot get status for invalid port %d\n", | |
1090 | port_num); | |
1091 | return -EINVAL; | |
1092 | } | |
1093 | ||
1094 | req.port = port_num; | |
1095 | req.role = USB_PD_CTRL_ROLE_NO_CHANGE; | |
1096 | req.mux = USB_PD_CTRL_MUX_NO_CHANGE; | |
1097 | req.swap = USB_PD_CTRL_SWAP_NONE; | |
1098 | ||
b1d288d9 PM |
1099 | ret = cros_ec_cmd(typec->ec, typec->pd_ctrl_ver, |
1100 | EC_CMD_USB_PD_CONTROL, &req, sizeof(req), | |
1101 | &resp, sizeof(resp)); | |
ad7c0510 PM |
1102 | if (ret < 0) |
1103 | return ret; | |
1104 | ||
af34f115 PM |
1105 | /* Update the switches if they exist, according to requested state */ |
1106 | ret = cros_typec_configure_mux(typec, port_num, &resp); | |
1107 | if (ret) | |
1108 | dev_warn(typec->dev, "Configure muxes failed, err = %d\n", ret); | |
1109 | ||
ad7c0510 PM |
1110 | dev_dbg(typec->dev, "Enabled %d: 0x%hhx\n", port_num, resp.enabled); |
1111 | dev_dbg(typec->dev, "Role %d: 0x%hhx\n", port_num, resp.role); | |
1112 | dev_dbg(typec->dev, "Polarity %d: 0x%hhx\n", port_num, resp.polarity); | |
1113 | dev_dbg(typec->dev, "State %d: %s\n", port_num, resp.state); | |
1114 | ||
2ee97377 | 1115 | if (typec->pd_ctrl_ver != 0) |
410457b9 PM |
1116 | cros_typec_set_port_params_v1(typec, port_num, |
1117 | (struct ec_response_usb_pd_control_v1 *)&resp); | |
ad7c0510 PM |
1118 | else |
1119 | cros_typec_set_port_params_v0(typec, port_num, | |
1120 | (struct ec_response_usb_pd_control *) &resp); | |
1121 | ||
80f8cef6 PM |
1122 | if (typec->typec_cmd_supported) |
1123 | cros_typec_handle_status(typec, port_num); | |
1124 | ||
af34f115 | 1125 | return 0; |
ad7c0510 PM |
1126 | } |
1127 | ||
1128 | static int cros_typec_get_cmd_version(struct cros_typec_data *typec) | |
1129 | { | |
1130 | struct ec_params_get_cmd_versions_v1 req_v1; | |
1131 | struct ec_response_get_cmd_versions resp; | |
1132 | int ret; | |
1133 | ||
1134 | /* We're interested in the PD control command version. */ | |
1135 | req_v1.cmd = EC_CMD_USB_PD_CONTROL; | |
b1d288d9 PM |
1136 | ret = cros_ec_cmd(typec->ec, 1, EC_CMD_GET_CMD_VERSIONS, |
1137 | &req_v1, sizeof(req_v1), &resp, sizeof(resp)); | |
ad7c0510 PM |
1138 | if (ret < 0) |
1139 | return ret; | |
1140 | ||
2ee97377 PM |
1141 | if (resp.version_mask & EC_VER_MASK(2)) |
1142 | typec->pd_ctrl_ver = 2; | |
1143 | else if (resp.version_mask & EC_VER_MASK(1)) | |
1144 | typec->pd_ctrl_ver = 1; | |
ad7c0510 | 1145 | else |
2ee97377 | 1146 | typec->pd_ctrl_ver = 0; |
ad7c0510 | 1147 | |
c6e939c6 AB |
1148 | dev_dbg(typec->dev, "PD Control has version mask 0x%02x\n", |
1149 | typec->pd_ctrl_ver & 0xff); | |
ad7c0510 PM |
1150 | |
1151 | return 0; | |
1152 | } | |
1153 | ||
83cbc69d | 1154 | static void cros_typec_port_work(struct work_struct *work) |
7110f5f0 | 1155 | { |
83cbc69d | 1156 | struct cros_typec_data *typec = container_of(work, struct cros_typec_data, port_work); |
7110f5f0 PM |
1157 | int ret, i; |
1158 | ||
1159 | for (i = 0; i < typec->num_ports; i++) { | |
1160 | ret = cros_typec_port_update(typec, i); | |
1161 | if (ret < 0) | |
1162 | dev_warn(typec->dev, "Update failed for port: %d\n", i); | |
1163 | } | |
83cbc69d PM |
1164 | } |
1165 | ||
1166 | static int cros_ec_typec_event(struct notifier_block *nb, | |
1167 | unsigned long host_event, void *_notify) | |
1168 | { | |
1169 | struct cros_typec_data *typec = container_of(nb, struct cros_typec_data, nb); | |
1170 | ||
a59e1221 | 1171 | flush_work(&typec->port_work); |
83cbc69d | 1172 | schedule_work(&typec->port_work); |
7110f5f0 PM |
1173 | |
1174 | return NOTIFY_OK; | |
1175 | } | |
1176 | ||
fdc6b21e PM |
1177 | #ifdef CONFIG_ACPI |
1178 | static const struct acpi_device_id cros_typec_acpi_id[] = { | |
1179 | { "GOOG0014", 0 }, | |
1180 | {} | |
1181 | }; | |
1182 | MODULE_DEVICE_TABLE(acpi, cros_typec_acpi_id); | |
1183 | #endif | |
1184 | ||
1185 | #ifdef CONFIG_OF | |
1186 | static const struct of_device_id cros_typec_of_match[] = { | |
1187 | { .compatible = "google,cros-ec-typec", }, | |
1188 | {} | |
1189 | }; | |
1190 | MODULE_DEVICE_TABLE(of, cros_typec_of_match); | |
1191 | #endif | |
1192 | ||
1193 | static int cros_typec_probe(struct platform_device *pdev) | |
1194 | { | |
a8db7a3f | 1195 | struct cros_ec_dev *ec_dev = NULL; |
fdc6b21e PM |
1196 | struct device *dev = &pdev->dev; |
1197 | struct cros_typec_data *typec; | |
1198 | struct ec_response_usb_pd_ports resp; | |
ad7c0510 | 1199 | int ret, i; |
fdc6b21e PM |
1200 | |
1201 | typec = devm_kzalloc(dev, sizeof(*typec), GFP_KERNEL); | |
1202 | if (!typec) | |
1203 | return -ENOMEM; | |
1204 | ||
1205 | typec->dev = dev; | |
ffebd905 | 1206 | |
fdc6b21e | 1207 | typec->ec = dev_get_drvdata(pdev->dev.parent); |
ffebd905 PM |
1208 | if (!typec->ec) { |
1209 | dev_err(dev, "couldn't find parent EC device\n"); | |
1210 | return -ENODEV; | |
1211 | } | |
1212 | ||
fdc6b21e PM |
1213 | platform_set_drvdata(pdev, typec); |
1214 | ||
ad7c0510 PM |
1215 | ret = cros_typec_get_cmd_version(typec); |
1216 | if (ret < 0) { | |
1217 | dev_err(dev, "failed to get PD command version info\n"); | |
1218 | return ret; | |
1219 | } | |
1220 | ||
a8db7a3f | 1221 | ec_dev = dev_get_drvdata(&typec->ec->ec->dev); |
7464ff8b AO |
1222 | if (!ec_dev) |
1223 | return -EPROBE_DEFER; | |
1224 | ||
73eff860 PM |
1225 | typec->typec_cmd_supported = cros_ec_check_features(ec_dev, EC_FEATURE_TYPEC_CMD); |
1226 | typec->needs_mux_ack = cros_ec_check_features(ec_dev, EC_FEATURE_TYPEC_MUX_REQUIRE_AP_ACK); | |
80f8cef6 | 1227 | |
b1d288d9 PM |
1228 | ret = cros_ec_cmd(typec->ec, 0, EC_CMD_USB_PD_PORTS, NULL, 0, |
1229 | &resp, sizeof(resp)); | |
fdc6b21e PM |
1230 | if (ret < 0) |
1231 | return ret; | |
1232 | ||
1233 | typec->num_ports = resp.num_ports; | |
1234 | if (typec->num_ports > EC_USB_PD_MAX_PORTS) { | |
1235 | dev_warn(typec->dev, | |
1236 | "Too many ports reported: %d, limiting to max: %d\n", | |
1237 | typec->num_ports, EC_USB_PD_MAX_PORTS); | |
1238 | typec->num_ports = EC_USB_PD_MAX_PORTS; | |
1239 | } | |
1240 | ||
1241 | ret = cros_typec_init_ports(typec); | |
1242 | if (ret < 0) | |
1243 | return ret; | |
1244 | ||
83cbc69d PM |
1245 | INIT_WORK(&typec->port_work, cros_typec_port_work); |
1246 | ||
1247 | /* | |
1248 | * Safe to call port update here, since we haven't registered the | |
1249 | * PD notifier yet. | |
1250 | */ | |
ad7c0510 PM |
1251 | for (i = 0; i < typec->num_ports; i++) { |
1252 | ret = cros_typec_port_update(typec, i); | |
1253 | if (ret < 0) | |
1254 | goto unregister_ports; | |
1255 | } | |
1256 | ||
7110f5f0 PM |
1257 | typec->nb.notifier_call = cros_ec_typec_event; |
1258 | ret = cros_usbpd_register_notify(&typec->nb); | |
1259 | if (ret < 0) | |
1260 | goto unregister_ports; | |
1261 | ||
fdc6b21e | 1262 | return 0; |
ad7c0510 PM |
1263 | |
1264 | unregister_ports: | |
5fed73b8 | 1265 | cros_unregister_ports(typec); |
ad7c0510 | 1266 | return ret; |
fdc6b21e PM |
1267 | } |
1268 | ||
20b73687 PM |
1269 | static int __maybe_unused cros_typec_suspend(struct device *dev) |
1270 | { | |
1271 | struct cros_typec_data *typec = dev_get_drvdata(dev); | |
1272 | ||
1273 | cancel_work_sync(&typec->port_work); | |
1274 | ||
1275 | return 0; | |
1276 | } | |
1277 | ||
1278 | static int __maybe_unused cros_typec_resume(struct device *dev) | |
1279 | { | |
1280 | struct cros_typec_data *typec = dev_get_drvdata(dev); | |
1281 | ||
1282 | /* Refresh port state. */ | |
1283 | schedule_work(&typec->port_work); | |
1284 | ||
1285 | return 0; | |
1286 | } | |
1287 | ||
1288 | static const struct dev_pm_ops cros_typec_pm_ops = { | |
1289 | SET_SYSTEM_SLEEP_PM_OPS(cros_typec_suspend, cros_typec_resume) | |
1290 | }; | |
1291 | ||
fdc6b21e PM |
1292 | static struct platform_driver cros_typec_driver = { |
1293 | .driver = { | |
1294 | .name = DRV_NAME, | |
1295 | .acpi_match_table = ACPI_PTR(cros_typec_acpi_id), | |
1296 | .of_match_table = of_match_ptr(cros_typec_of_match), | |
20b73687 | 1297 | .pm = &cros_typec_pm_ops, |
fdc6b21e PM |
1298 | }, |
1299 | .probe = cros_typec_probe, | |
1300 | }; | |
1301 | ||
1302 | module_platform_driver(cros_typec_driver); | |
1303 | ||
1304 | MODULE_AUTHOR("Prashant Malani <pmalani@chromium.org>"); | |
1305 | MODULE_DESCRIPTION("Chrome OS EC Type C control"); | |
1306 | MODULE_LICENSE("GPL"); |