1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * USB Power Delivery Vendor Defined Message (VDM) support code. |
4 | * |
5 | * Copyright 2023 Google LLC |
6 | * Author: Prashant Malani <pmalani@chromium.org> |
7 | */ |
8 | |
9 | #include <linux/module.h> |
10 | #include <linux/platform_data/cros_ec_commands.h> |
11 | #include <linux/usb/pd_vdo.h> |
12 | |
13 | #include "cros_ec_typec.h" |
14 | #include "cros_typec_vdm.h" |
15 | |
16 | /* |
17 | * Retrieves pending VDM attention messages from the EC and forwards them to the altmode driver |
18 | * based on SVID. |
19 | */ |
20 | void cros_typec_handle_vdm_attention(struct cros_typec_data *typec, int port_num) |
21 | { |
22 | struct ec_response_typec_vdm_response resp; |
23 | struct ec_params_typec_vdm_response req = { |
24 | .port = port_num, |
25 | }; |
26 | struct typec_altmode *amode; |
27 | u16 svid; |
28 | u32 hdr; |
29 | int ret; |
30 | |
31 | do { |
32 | ret = cros_ec_cmd(ec_dev: typec->ec, version: 0, EC_CMD_TYPEC_VDM_RESPONSE, outdata: &req, |
33 | outsize: sizeof(req), indata: &resp, insize: sizeof(resp)); |
34 | if (ret < 0) { |
35 | dev_warn(typec->dev, "Failed VDM response fetch, port: %d\n" , port_num); |
36 | return; |
37 | } |
38 | |
39 | hdr = resp.vdm_response[0]; |
40 | svid = PD_VDO_VID(hdr); |
41 | dev_dbg(typec->dev, "Received VDM Attention header: %x, port: %d\n" , hdr, port_num); |
42 | |
43 | amode = typec_match_altmode(altmodes: typec->ports[port_num]->port_altmode, |
44 | n: CROS_EC_ALTMODE_MAX, svid, PD_VDO_OPOS(hdr)); |
45 | if (!amode) { |
46 | dev_err(typec->dev, |
47 | "Received VDM for unregistered altmode (SVID:%x), port: %d\n" , |
48 | svid, port_num); |
49 | return; |
50 | } |
51 | |
52 | typec_altmode_attention(altmode: amode, vdo: resp.vdm_attention[1]); |
53 | } while (resp.vdm_attention_left); |
54 | } |
55 | |
56 | /* |
57 | * Retrieves a VDM response from the EC and forwards it to the altmode driver based on SVID. |
58 | */ |
59 | void cros_typec_handle_vdm_response(struct cros_typec_data *typec, int port_num) |
60 | { |
61 | struct ec_response_typec_vdm_response resp; |
62 | struct ec_params_typec_vdm_response req = { |
63 | .port = port_num, |
64 | }; |
65 | struct typec_altmode *amode; |
66 | u16 svid; |
67 | u32 hdr; |
68 | int ret; |
69 | |
70 | ret = cros_ec_cmd(ec_dev: typec->ec, version: 0, EC_CMD_TYPEC_VDM_RESPONSE, outdata: &req, |
71 | outsize: sizeof(req), indata: &resp, insize: sizeof(resp)); |
72 | if (ret < 0) { |
73 | dev_warn(typec->dev, "Failed VDM response fetch, port: %d\n" , port_num); |
74 | return; |
75 | } |
76 | |
77 | hdr = resp.vdm_response[0]; |
78 | svid = PD_VDO_VID(hdr); |
79 | dev_dbg(typec->dev, "Received VDM header: %x, port: %d\n" , hdr, port_num); |
80 | |
81 | amode = typec_match_altmode(altmodes: typec->ports[port_num]->port_altmode, n: CROS_EC_ALTMODE_MAX, |
82 | svid, PD_VDO_OPOS(hdr)); |
83 | if (!amode) { |
84 | dev_err(typec->dev, "Received VDM for unregistered altmode (SVID:%x), port: %d\n" , |
85 | svid, port_num); |
86 | return; |
87 | } |
88 | |
89 | ret = typec_altmode_vdm(altmode: amode, header: hdr, vdo: &resp.vdm_response[1], count: resp.vdm_data_objects); |
90 | if (ret) |
91 | dev_err(typec->dev, "Failed to forward VDM to altmode (SVID:%x), port: %d\n" , |
92 | svid, port_num); |
93 | } |
94 | |
95 | static int cros_typec_port_amode_enter(struct typec_altmode *amode, u32 *vdo) |
96 | { |
97 | struct cros_typec_port *port = typec_altmode_get_drvdata(altmode: amode); |
98 | struct ec_params_typec_control req = { |
99 | .port = port->port_num, |
100 | .command = TYPEC_CONTROL_COMMAND_SEND_VDM_REQ, |
101 | }; |
102 | struct typec_vdm_req vdm_req = {}; |
103 | u32 hdr; |
104 | |
105 | hdr = VDO(amode->svid, 1, SVDM_VER_2_0, CMD_ENTER_MODE); |
106 | hdr |= VDO_OPOS(amode->mode); |
107 | |
108 | vdm_req.vdm_data[0] = hdr; |
109 | vdm_req.vdm_data_objects = 1; |
110 | vdm_req.partner_type = TYPEC_PARTNER_SOP; |
111 | req.vdm_req_params = vdm_req; |
112 | |
113 | dev_dbg(port->typec_data->dev, "Sending EnterMode VDM, hdr: %x, port: %d\n" , |
114 | hdr, port->port_num); |
115 | |
116 | return cros_ec_cmd(ec_dev: port->typec_data->ec, version: 0, EC_CMD_TYPEC_CONTROL, outdata: &req, |
117 | outsize: sizeof(req), NULL, insize: 0); |
118 | } |
119 | |
120 | static int cros_typec_port_amode_vdm(struct typec_altmode *amode, const u32 hdr, |
121 | const u32 *vdo, int cnt) |
122 | { |
123 | struct cros_typec_port *port = typec_altmode_get_drvdata(altmode: amode); |
124 | struct ec_params_typec_control req = { |
125 | .port = port->port_num, |
126 | .command = TYPEC_CONTROL_COMMAND_SEND_VDM_REQ, |
127 | }; |
128 | struct typec_vdm_req vdm_req = {}; |
129 | int i; |
130 | |
131 | vdm_req.vdm_data[0] = hdr; |
132 | vdm_req.vdm_data_objects = cnt; |
133 | for (i = 1; i < cnt; i++) |
134 | vdm_req.vdm_data[i] = vdo[i-1]; |
135 | vdm_req.partner_type = TYPEC_PARTNER_SOP; |
136 | req.vdm_req_params = vdm_req; |
137 | |
138 | dev_dbg(port->typec_data->dev, "Sending VDM, hdr: %x, num_objects: %d, port: %d\n" , |
139 | hdr, cnt, port->port_num); |
140 | |
141 | return cros_ec_cmd(ec_dev: port->typec_data->ec, version: 0, EC_CMD_TYPEC_CONTROL, outdata: &req, |
142 | outsize: sizeof(req), NULL, insize: 0); |
143 | } |
144 | |
145 | const struct typec_altmode_ops port_amode_ops = { |
146 | .enter = cros_typec_port_amode_enter, |
147 | .vdm = cros_typec_port_amode_vdm, |
148 | }; |
149 | |