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

source code of linux/drivers/usb/typec/ucsi/displayport.c