1
2
3
4
5
6
7
8
9#include <linux/usb/typec_dp.h>
10#include <linux/usb/pd_vdo.h>
11
12#include "ucsi.h"
13
14#define UCSI_CMD_SET_NEW_CAM(_con_num_, _enter_, _cam_, _am_) \
15 (UCSI_SET_NEW_CAM | ((_con_num_) << 16) | ((_enter_) << 23) | \
16 ((_cam_) << 24) | ((u64)(_am_) << 32))
17
18struct ucsi_dp {
19 struct typec_displayport_data data;
20 struct ucsi_connector *con;
21 struct typec_altmode *alt;
22 struct work_struct work;
23 int offset;
24
25 bool override;
26 bool initialized;
27
28 u32 header;
29 u32 *vdo_data;
30 u8 vdo_size;
31};
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48static int ucsi_displayport_enter(struct typec_altmode *alt, u32 *vdo)
49{
50 struct ucsi_dp *dp = typec_altmode_get_drvdata(alt);
51 struct ucsi *ucsi = dp->con->ucsi;
52 u64 command;
53 u8 cur = 0;
54 int ret;
55
56 mutex_lock(&dp->con->lock);
57
58 if (!dp->override && dp->initialized) {
59 const struct typec_altmode *p = typec_altmode_get_partner(alt);
60
61 dev_warn(&p->dev,
62 "firmware doesn't support alternate mode overriding\n");
63 ret = -EOPNOTSUPP;
64 goto err_unlock;
65 }
66
67 command = UCSI_GET_CURRENT_CAM | UCSI_CONNECTOR_NUMBER(dp->con->num);
68 ret = ucsi_send_command(ucsi, command, &cur, sizeof(cur));
69 if (ret < 0) {
70 if (ucsi->version > 0x0100)
71 goto err_unlock;
72 cur = 0xff;
73 }
74
75 if (cur != 0xff) {
76 ret = dp->con->port_altmode[cur] == alt ? 0 : -EBUSY;
77 goto err_unlock;
78 }
79
80
81
82
83
84
85
86 dp->header = VDO(USB_TYPEC_DP_SID, 1, CMD_ENTER_MODE);
87 dp->header |= VDO_OPOS(USB_TYPEC_DP_MODE);
88 dp->header |= VDO_CMDT(CMDT_RSP_ACK);
89
90 dp->vdo_data = NULL;
91 dp->vdo_size = 1;
92
93 schedule_work(&dp->work);
94 ret = 0;
95err_unlock:
96 mutex_unlock(&dp->con->lock);
97
98 return ret;
99}
100
101static int ucsi_displayport_exit(struct typec_altmode *alt)
102{
103 struct ucsi_dp *dp = typec_altmode_get_drvdata(alt);
104 u64 command;
105 int ret = 0;
106
107 mutex_lock(&dp->con->lock);
108
109 if (!dp->override) {
110 const struct typec_altmode *p = typec_altmode_get_partner(alt);
111
112 dev_warn(&p->dev,
113 "firmware doesn't support alternate mode overriding\n");
114 ret = -EOPNOTSUPP;
115 goto out_unlock;
116 }
117
118 command = UCSI_CMD_SET_NEW_CAM(dp->con->num, 0, dp->offset, 0);
119 ret = ucsi_send_command(dp->con->ucsi, command, NULL, 0);
120 if (ret < 0)
121 goto out_unlock;
122
123 dp->header = VDO(USB_TYPEC_DP_SID, 1, CMD_EXIT_MODE);
124 dp->header |= VDO_OPOS(USB_TYPEC_DP_MODE);
125 dp->header |= VDO_CMDT(CMDT_RSP_ACK);
126
127 dp->vdo_data = NULL;
128 dp->vdo_size = 1;
129
130 schedule_work(&dp->work);
131
132out_unlock:
133 mutex_unlock(&dp->con->lock);
134
135 return ret;
136}
137
138
139
140
141
142static int ucsi_displayport_status_update(struct ucsi_dp *dp)
143{
144 u32 cap = dp->alt->vdo;
145
146 dp->data.status = DP_STATUS_ENABLED;
147
148
149
150
151
152 if (DP_CAP_CAPABILITY(cap) & DP_CAP_UFP_D) {
153 dp->data.status |= DP_STATUS_CON_UFP_D;
154
155 if (DP_CAP_UFP_D_PIN_ASSIGN(cap) & BIT(DP_PIN_ASSIGN_D))
156 dp->data.status |= DP_STATUS_PREFER_MULTI_FUNC;
157 } else {
158 dp->data.status |= DP_STATUS_CON_DFP_D;
159
160 if (DP_CAP_DFP_D_PIN_ASSIGN(cap) & BIT(DP_PIN_ASSIGN_D))
161 dp->data.status |= DP_STATUS_PREFER_MULTI_FUNC;
162 }
163
164 dp->vdo_data = &dp->data.status;
165 dp->vdo_size = 2;
166
167 return 0;
168}
169
170static int ucsi_displayport_configure(struct ucsi_dp *dp)
171{
172 u32 pins = DP_CONF_GET_PIN_ASSIGN(dp->data.conf);
173 u64 command;
174
175 if (!dp->override)
176 return 0;
177
178 command = UCSI_CMD_SET_NEW_CAM(dp->con->num, 1, dp->offset, pins);
179
180 return ucsi_send_command(dp->con->ucsi, command, NULL, 0);
181}
182
183static int ucsi_displayport_vdm(struct typec_altmode *alt,
184 u32 header, const u32 *data, int count)
185{
186 struct ucsi_dp *dp = typec_altmode_get_drvdata(alt);
187 int cmd_type = PD_VDO_CMDT(header);
188 int cmd = PD_VDO_CMD(header);
189
190 mutex_lock(&dp->con->lock);
191
192 if (!dp->override && dp->initialized) {
193 const struct typec_altmode *p = typec_altmode_get_partner(alt);
194
195 dev_warn(&p->dev,
196 "firmware doesn't support alternate mode overriding\n");
197 mutex_unlock(&dp->con->lock);
198 return -EOPNOTSUPP;
199 }
200
201 switch (cmd_type) {
202 case CMDT_INIT:
203 dp->header = VDO(USB_TYPEC_DP_SID, 1, cmd);
204 dp->header |= VDO_OPOS(USB_TYPEC_DP_MODE);
205
206 switch (cmd) {
207 case DP_CMD_STATUS_UPDATE:
208 if (ucsi_displayport_status_update(dp))
209 dp->header |= VDO_CMDT(CMDT_RSP_NAK);
210 else
211 dp->header |= VDO_CMDT(CMDT_RSP_ACK);
212 break;
213 case DP_CMD_CONFIGURE:
214 dp->data.conf = *data;
215 if (ucsi_displayport_configure(dp)) {
216 dp->header |= VDO_CMDT(CMDT_RSP_NAK);
217 } else {
218 dp->header |= VDO_CMDT(CMDT_RSP_ACK);
219 if (dp->initialized)
220 ucsi_altmode_update_active(dp->con);
221 else
222 dp->initialized = true;
223 }
224 break;
225 default:
226 dp->header |= VDO_CMDT(CMDT_RSP_ACK);
227 break;
228 }
229
230 schedule_work(&dp->work);
231 break;
232 default:
233 break;
234 }
235
236 mutex_unlock(&dp->con->lock);
237
238 return 0;
239}
240
241static const struct typec_altmode_ops ucsi_displayport_ops = {
242 .enter = ucsi_displayport_enter,
243 .exit = ucsi_displayport_exit,
244 .vdm = ucsi_displayport_vdm,
245};
246
247static void ucsi_displayport_work(struct work_struct *work)
248{
249 struct ucsi_dp *dp = container_of(work, struct ucsi_dp, work);
250 int ret;
251
252 mutex_lock(&dp->con->lock);
253
254 ret = typec_altmode_vdm(dp->alt, dp->header,
255 dp->vdo_data, dp->vdo_size);
256 if (ret)
257 dev_err(&dp->alt->dev, "VDM 0x%x failed\n", dp->header);
258
259 dp->vdo_data = NULL;
260 dp->vdo_size = 0;
261 dp->header = 0;
262
263 mutex_unlock(&dp->con->lock);
264}
265
266void ucsi_displayport_remove_partner(struct typec_altmode *alt)
267{
268 struct ucsi_dp *dp;
269
270 if (!alt)
271 return;
272
273 dp = typec_altmode_get_drvdata(alt);
274 if (!dp)
275 return;
276
277 dp->data.conf = 0;
278 dp->data.status = 0;
279 dp->initialized = false;
280}
281
282struct typec_altmode *ucsi_register_displayport(struct ucsi_connector *con,
283 bool override, int offset,
284 struct typec_altmode_desc *desc)
285{
286 u8 all_assignments = BIT(DP_PIN_ASSIGN_C) | BIT(DP_PIN_ASSIGN_D) |
287 BIT(DP_PIN_ASSIGN_E);
288 struct typec_altmode *alt;
289 struct ucsi_dp *dp;
290
291
292 desc->vdo |= DP_CAP_DP_SIGNALING | DP_CAP_RECEPTACLE;
293
294
295 desc->vdo |= all_assignments << 8;
296 desc->vdo |= all_assignments << 16;
297
298 alt = typec_port_register_altmode(con->port, desc);
299 if (IS_ERR(alt))
300 return alt;
301
302 dp = devm_kzalloc(&alt->dev, sizeof(*dp), GFP_KERNEL);
303 if (!dp) {
304 typec_unregister_altmode(alt);
305 return ERR_PTR(-ENOMEM);
306 }
307
308 INIT_WORK(&dp->work, ucsi_displayport_work);
309 dp->override = override;
310 dp->offset = offset;
311 dp->con = con;
312 dp->alt = alt;
313
314 alt->ops = &ucsi_displayport_ops;
315 typec_altmode_set_drvdata(alt, dp);
316
317 return alt;
318}
319