1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * UCSI DisplayPort Alternate Mode Support
4 *
5 * Copyright (C) 2018, Intel Corporation
6 * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
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
18 struct 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 * Note. Alternate mode control is optional feature in UCSI. It means that even
35 * if the system supports alternate modes, the OS may not be aware of them.
36 *
37 * In most cases however, the OS will be able to see the supported alternate
38 * modes, but it may still not be able to configure them, not even enter or exit
39 * them. That is because UCSI defines alt mode details and alt mode "overriding"
40 * as separate options.
41 *
42 * In case alt mode details are supported, but overriding is not, the driver
43 * will still display the supported pin assignments and configuration, but any
44 * changes the user attempts to do will lead into failure with return value of
45 * -EOPNOTSUPP.
46 */
47
ucsi_displayport_enter(struct typec_altmode * alt,u32 * vdo)48 static 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 int svdm_version;
53 u64 command;
54 u8 cur = 0;
55 int ret;
56
57 if (!ucsi_con_mutex_lock(dp->con))
58 return -ENOTCONN;
59
60 if (!dp->override && dp->initialized) {
61 const struct typec_altmode *p = typec_altmode_get_partner(alt);
62
63 dev_warn(&p->dev,
64 "firmware doesn't support alternate mode overriding\n");
65 ret = -EOPNOTSUPP;
66 goto err_unlock;
67 }
68
69 command = UCSI_GET_CURRENT_CAM | UCSI_CONNECTOR_NUMBER(dp->con->num);
70 ret = ucsi_send_command(ucsi, command, &cur, sizeof(cur));
71 if (ret < 0) {
72 if (ucsi->version > 0x0100)
73 goto err_unlock;
74 cur = 0xff;
75 }
76
77 if (cur != 0xff) {
78 ret = dp->con->port_altmode[cur] == alt ? 0 : -EBUSY;
79 goto err_unlock;
80 }
81
82 /*
83 * We can't send the New CAM command yet to the PPM as it needs the
84 * configuration value as well. Pretending that we have now entered the
85 * mode, and letting the alt mode driver continue.
86 */
87
88 svdm_version = typec_altmode_get_svdm_version(alt);
89 if (svdm_version < 0) {
90 ret = svdm_version;
91 goto err_unlock;
92 }
93
94 dp->header = VDO(USB_TYPEC_DP_SID, 1, svdm_version, CMD_ENTER_MODE);
95 dp->header |= VDO_OPOS(USB_TYPEC_DP_MODE);
96 dp->header |= VDO_CMDT(CMDT_RSP_ACK);
97
98 dp->vdo_data = NULL;
99 dp->vdo_size = 1;
100
101 schedule_work(&dp->work);
102 ret = 0;
103 err_unlock:
104 ucsi_con_mutex_unlock(dp->con);
105
106 return ret;
107 }
108
ucsi_displayport_exit(struct typec_altmode * alt)109 static int ucsi_displayport_exit(struct typec_altmode *alt)
110 {
111 struct ucsi_dp *dp = typec_altmode_get_drvdata(alt);
112 int svdm_version;
113 u64 command;
114 int ret = 0;
115
116 if (!ucsi_con_mutex_lock(dp->con))
117 return -ENOTCONN;
118
119 if (!dp->override) {
120 const struct typec_altmode *p = typec_altmode_get_partner(alt);
121
122 dev_warn(&p->dev,
123 "firmware doesn't support alternate mode overriding\n");
124 ret = -EOPNOTSUPP;
125 goto out_unlock;
126 }
127
128 command = UCSI_CMD_SET_NEW_CAM(dp->con->num, 0, dp->offset, 0);
129 ret = ucsi_send_command(dp->con->ucsi, command, NULL, 0);
130 if (ret < 0)
131 goto out_unlock;
132
133 svdm_version = typec_altmode_get_svdm_version(alt);
134 if (svdm_version < 0) {
135 ret = svdm_version;
136 goto out_unlock;
137 }
138
139 dp->header = VDO(USB_TYPEC_DP_SID, 1, svdm_version, CMD_EXIT_MODE);
140 dp->header |= VDO_OPOS(USB_TYPEC_DP_MODE);
141 dp->header |= VDO_CMDT(CMDT_RSP_ACK);
142
143 dp->vdo_data = NULL;
144 dp->vdo_size = 1;
145
146 schedule_work(&dp->work);
147
148 out_unlock:
149 ucsi_con_mutex_unlock(dp->con);
150
151 return ret;
152 }
153
154 /*
155 * We do not actually have access to the Status Update VDO, so we have to guess
156 * things.
157 */
ucsi_displayport_status_update(struct ucsi_dp * dp)158 static int ucsi_displayport_status_update(struct ucsi_dp *dp)
159 {
160 u32 cap = dp->alt->vdo;
161
162 dp->data.status = DP_STATUS_ENABLED;
163
164 /*
165 * If pin assignement D is supported, claiming always
166 * that Multi-function is preferred.
167 */
168 if (DP_CAP_CAPABILITY(cap) & DP_CAP_UFP_D) {
169 dp->data.status |= DP_STATUS_CON_UFP_D;
170
171 if (DP_CAP_UFP_D_PIN_ASSIGN(cap) & BIT(DP_PIN_ASSIGN_D))
172 dp->data.status |= DP_STATUS_PREFER_MULTI_FUNC;
173 } else {
174 dp->data.status |= DP_STATUS_CON_DFP_D;
175
176 if (DP_CAP_DFP_D_PIN_ASSIGN(cap) & BIT(DP_PIN_ASSIGN_D))
177 dp->data.status |= DP_STATUS_PREFER_MULTI_FUNC;
178 }
179
180 dp->vdo_data = &dp->data.status;
181 dp->vdo_size = 2;
182
183 return 0;
184 }
185
ucsi_displayport_configure(struct ucsi_dp * dp)186 static int ucsi_displayport_configure(struct ucsi_dp *dp)
187 {
188 u32 pins = DP_CONF_GET_PIN_ASSIGN(dp->data.conf);
189 u64 command;
190
191 if (!dp->override)
192 return 0;
193
194 command = UCSI_CMD_SET_NEW_CAM(dp->con->num, 1, dp->offset, pins);
195
196 return ucsi_send_command(dp->con->ucsi, command, NULL, 0);
197 }
198
ucsi_displayport_vdm(struct typec_altmode * alt,u32 header,const u32 * data,int count)199 static int ucsi_displayport_vdm(struct typec_altmode *alt,
200 u32 header, const u32 *data, int count)
201 {
202 struct ucsi_dp *dp = typec_altmode_get_drvdata(alt);
203 int cmd_type = PD_VDO_CMDT(header);
204 int cmd = PD_VDO_CMD(header);
205 int svdm_version;
206
207 if (!ucsi_con_mutex_lock(dp->con))
208 return -ENOTCONN;
209
210 if (!dp->override && dp->initialized) {
211 const struct typec_altmode *p = typec_altmode_get_partner(alt);
212
213 dev_warn(&p->dev,
214 "firmware doesn't support alternate mode overriding\n");
215 ucsi_con_mutex_unlock(dp->con);
216 return -EOPNOTSUPP;
217 }
218
219 svdm_version = typec_altmode_get_svdm_version(alt);
220 if (svdm_version < 0) {
221 ucsi_con_mutex_unlock(dp->con);
222 return svdm_version;
223 }
224
225 switch (cmd_type) {
226 case CMDT_INIT:
227 if (PD_VDO_SVDM_VER(header) < svdm_version) {
228 typec_partner_set_svdm_version(dp->con->partner, PD_VDO_SVDM_VER(header));
229 svdm_version = PD_VDO_SVDM_VER(header);
230 }
231
232 dp->header = VDO(USB_TYPEC_DP_SID, 1, svdm_version, cmd);
233 dp->header |= VDO_OPOS(USB_TYPEC_DP_MODE);
234
235 switch (cmd) {
236 case DP_CMD_STATUS_UPDATE:
237 if (ucsi_displayport_status_update(dp))
238 dp->header |= VDO_CMDT(CMDT_RSP_NAK);
239 else
240 dp->header |= VDO_CMDT(CMDT_RSP_ACK);
241 break;
242 case DP_CMD_CONFIGURE:
243 dp->data.conf = *data;
244 if (ucsi_displayport_configure(dp)) {
245 dp->header |= VDO_CMDT(CMDT_RSP_NAK);
246 } else {
247 dp->header |= VDO_CMDT(CMDT_RSP_ACK);
248 if (dp->initialized)
249 ucsi_altmode_update_active(dp->con);
250 else
251 dp->initialized = true;
252 }
253 break;
254 default:
255 dp->header |= VDO_CMDT(CMDT_RSP_ACK);
256 break;
257 }
258
259 schedule_work(&dp->work);
260 break;
261 default:
262 break;
263 }
264
265 ucsi_con_mutex_unlock(dp->con);
266
267 return 0;
268 }
269
270 static const struct typec_altmode_ops ucsi_displayport_ops = {
271 .enter = ucsi_displayport_enter,
272 .exit = ucsi_displayport_exit,
273 .vdm = ucsi_displayport_vdm,
274 };
275
ucsi_displayport_work(struct work_struct * work)276 static void ucsi_displayport_work(struct work_struct *work)
277 {
278 struct ucsi_dp *dp = container_of(work, struct ucsi_dp, work);
279 int ret;
280
281 ret = typec_altmode_vdm(dp->alt, dp->header,
282 dp->vdo_data, dp->vdo_size);
283 if (ret)
284 dev_err(&dp->alt->dev, "VDM 0x%x failed\n", dp->header);
285
286 dp->vdo_data = NULL;
287 dp->vdo_size = 0;
288 dp->header = 0;
289 }
290
ucsi_displayport_remove_partner(struct typec_altmode * alt)291 void ucsi_displayport_remove_partner(struct typec_altmode *alt)
292 {
293 struct ucsi_dp *dp;
294
295 if (!alt)
296 return;
297
298 dp = typec_altmode_get_drvdata(alt);
299 if (!dp)
300 return;
301
302 cancel_work_sync(&dp->work);
303
304 dp->data.conf = 0;
305 dp->data.status = 0;
306 dp->initialized = false;
307 }
308
ucsi_register_displayport(struct ucsi_connector * con,bool override,int offset,struct typec_altmode_desc * desc)309 struct typec_altmode *ucsi_register_displayport(struct ucsi_connector *con,
310 bool override, int offset,
311 struct typec_altmode_desc *desc)
312 {
313 u8 all_assignments = BIT(DP_PIN_ASSIGN_C) | BIT(DP_PIN_ASSIGN_D) |
314 BIT(DP_PIN_ASSIGN_E);
315 struct typec_altmode *alt;
316 struct ucsi_dp *dp;
317
318 /* We can't rely on the firmware with the capabilities. */
319 desc->vdo |= DP_CAP_DP_SIGNALLING(0) | DP_CAP_RECEPTACLE;
320
321 /* Claiming that we support all pin assignments */
322 desc->vdo |= all_assignments << 8;
323 desc->vdo |= all_assignments << 16;
324
325 alt = typec_port_register_altmode(con->port, desc);
326 if (IS_ERR(alt))
327 return alt;
328
329 dp = devm_kzalloc(&alt->dev, sizeof(*dp), GFP_KERNEL);
330 if (!dp) {
331 typec_unregister_altmode(alt);
332 return ERR_PTR(-ENOMEM);
333 }
334
335 INIT_WORK(&dp->work, ucsi_displayport_work);
336 dp->override = override;
337 dp->offset = offset;
338 dp->con = con;
339 dp->alt = alt;
340
341 typec_altmode_set_ops(alt, &ucsi_displayport_ops);
342 typec_altmode_set_drvdata(alt, dp);
343
344 return alt;
345 }
346