xref: /linux/drivers/usb/typec/ucsi/displayport.c (revision 65538a8f02fe6e4f07228a816529b039b544f051)
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 < UCSI_MAX_ALTMODES) {
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_DFP_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_UFP_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 	u64 command;
189 
190 	if (!dp->override)
191 		return 0;
192 
193 	command = UCSI_CMD_SET_NEW_CAM(dp->con->num, 1, dp->offset, dp->data.conf);
194 
195 	return ucsi_send_command(dp->con->ucsi, command, NULL, 0);
196 }
197 
ucsi_displayport_vdm(struct typec_altmode * alt,u32 header,const u32 * data,int count)198 static int ucsi_displayport_vdm(struct typec_altmode *alt,
199 				u32 header, const u32 *data, int count)
200 {
201 	struct ucsi_dp *dp = typec_altmode_get_drvdata(alt);
202 	int cmd_type = PD_VDO_CMDT(header);
203 	int cmd = PD_VDO_CMD(header);
204 	int svdm_version;
205 
206 	if (!ucsi_con_mutex_lock(dp->con))
207 		return -ENOTCONN;
208 
209 	if (!dp->override && dp->initialized) {
210 		const struct typec_altmode *p = typec_altmode_get_partner(alt);
211 
212 		dev_warn(&p->dev,
213 			 "firmware doesn't support alternate mode overriding\n");
214 		ucsi_con_mutex_unlock(dp->con);
215 		return -EOPNOTSUPP;
216 	}
217 
218 	svdm_version = typec_altmode_get_svdm_version(alt);
219 	if (svdm_version < 0) {
220 		ucsi_con_mutex_unlock(dp->con);
221 		return svdm_version;
222 	}
223 
224 	switch (cmd_type) {
225 	case CMDT_INIT:
226 		if (PD_VDO_SVDM_VER(header) < svdm_version) {
227 			typec_partner_set_svdm_version(dp->con->partner, PD_VDO_SVDM_VER(header));
228 			svdm_version = PD_VDO_SVDM_VER(header);
229 		}
230 
231 		dp->header = VDO(USB_TYPEC_DP_SID, 1, svdm_version, cmd);
232 		dp->header |= VDO_OPOS(USB_TYPEC_DP_MODE);
233 
234 		switch (cmd) {
235 		case DP_CMD_STATUS_UPDATE:
236 			if (ucsi_displayport_status_update(dp))
237 				dp->header |= VDO_CMDT(CMDT_RSP_NAK);
238 			else
239 				dp->header |= VDO_CMDT(CMDT_RSP_ACK);
240 			break;
241 		case DP_CMD_CONFIGURE:
242 			if (count < 2) {
243 				dp->header |= VDO_CMDT(CMDT_RSP_NAK);
244 				break;
245 			}
246 			dp->data.conf = *data;
247 			if (ucsi_displayport_configure(dp)) {
248 				dp->header |= VDO_CMDT(CMDT_RSP_NAK);
249 			} else {
250 				dp->header |= VDO_CMDT(CMDT_RSP_ACK);
251 				if (dp->initialized)
252 					ucsi_altmode_update_active(dp->con);
253 				else
254 					dp->initialized = true;
255 			}
256 			break;
257 		default:
258 			dp->header |= VDO_CMDT(CMDT_RSP_ACK);
259 			break;
260 		}
261 
262 		schedule_work(&dp->work);
263 		break;
264 	default:
265 		break;
266 	}
267 
268 	ucsi_con_mutex_unlock(dp->con);
269 
270 	return 0;
271 }
272 
273 static const struct typec_altmode_ops ucsi_displayport_ops = {
274 	.enter = ucsi_displayport_enter,
275 	.exit = ucsi_displayport_exit,
276 	.vdm = ucsi_displayport_vdm,
277 };
278 
ucsi_displayport_work(struct work_struct * work)279 static void ucsi_displayport_work(struct work_struct *work)
280 {
281 	struct ucsi_dp *dp = container_of(work, struct ucsi_dp, work);
282 	int ret;
283 
284 	ret = typec_altmode_vdm(dp->alt, dp->header,
285 				dp->vdo_data, dp->vdo_size);
286 	if (ret)
287 		dev_err(&dp->alt->dev, "VDM 0x%x failed\n", dp->header);
288 
289 	dp->vdo_data = NULL;
290 	dp->vdo_size = 0;
291 	dp->header = 0;
292 }
293 
ucsi_displayport_remove_partner(struct typec_altmode * alt)294 void ucsi_displayport_remove_partner(struct typec_altmode *alt)
295 {
296 	struct ucsi_dp *dp;
297 
298 	if (!alt)
299 		return;
300 
301 	dp = typec_altmode_get_drvdata(alt);
302 	if (!dp)
303 		return;
304 
305 	cancel_work_sync(&dp->work);
306 
307 	dp->data.conf = 0;
308 	dp->data.status = 0;
309 	dp->initialized = false;
310 }
311 
ucsi_register_displayport(struct ucsi_connector * con,bool override,int offset,struct typec_altmode_desc * desc)312 struct typec_altmode *ucsi_register_displayport(struct ucsi_connector *con,
313 						bool override, int offset,
314 						struct typec_altmode_desc *desc)
315 {
316 	u8 all_assignments = BIT(DP_PIN_ASSIGN_C) | BIT(DP_PIN_ASSIGN_D) |
317 			     BIT(DP_PIN_ASSIGN_E);
318 	struct typec_altmode *alt;
319 	struct ucsi_dp *dp;
320 
321 	/* We can't rely on the firmware with the capabilities. */
322 	desc->vdo |= DP_CAP_DP_SIGNALLING(0) | DP_CAP_RECEPTACLE;
323 
324 	/* Claiming that we support all pin assignments */
325 	desc->vdo |= all_assignments << 8;
326 	desc->vdo |= all_assignments << 16;
327 
328 	alt = typec_port_register_altmode(con->port, desc);
329 	if (IS_ERR(alt))
330 		return alt;
331 
332 	dp = devm_kzalloc(&alt->dev, sizeof(*dp), GFP_KERNEL);
333 	if (!dp) {
334 		typec_unregister_altmode(alt);
335 		return ERR_PTR(-ENOMEM);
336 	}
337 
338 	INIT_WORK(&dp->work, ucsi_displayport_work);
339 	dp->override = override;
340 	dp->offset = offset;
341 	dp->con = con;
342 	dp->alt = alt;
343 
344 	typec_altmode_set_ops(alt, &ucsi_displayport_ops);
345 	typec_altmode_set_drvdata(alt, dp);
346 
347 	return alt;
348 }
349