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