• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 	mutex_lock(&dp->con->lock);
58 
59 	if (!dp->override && dp->initialized) {
60 		const struct typec_altmode *p = typec_altmode_get_partner(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, &cur, 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(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(&dp->work);
101 	ret = 0;
102 err_unlock:
103 	mutex_unlock(&dp->con->lock);
104 
105 	return ret;
106 }
107 
ucsi_displayport_exit(struct typec_altmode * alt)108 static int ucsi_displayport_exit(struct typec_altmode *alt)
109 {
110 	struct ucsi_dp *dp = typec_altmode_get_drvdata(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(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(dp->con->ucsi, command, NULL, 0);
128 	if (ret < 0)
129 		goto out_unlock;
130 
131 	svdm_version = typec_altmode_get_svdm_version(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(&dp->work);
145 
146 out_unlock:
147 	mutex_unlock(&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  */
ucsi_displayport_status_update(struct ucsi_dp * dp)156 static 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 
ucsi_displayport_configure(struct ucsi_dp * dp)184 static 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(dp->con->ucsi, command, NULL, 0);
195 }
196 
ucsi_displayport_vdm(struct typec_altmode * alt,u32 header,const u32 * data,int count)197 static 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(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(alt);
209 
210 		dev_warn(&p->dev,
211 			 "firmware doesn't support alternate mode overriding\n");
212 		mutex_unlock(&dp->con->lock);
213 		return -EOPNOTSUPP;
214 	}
215 
216 	svdm_version = typec_altmode_get_svdm_version(alt);
217 	if (svdm_version < 0) {
218 		mutex_unlock(&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(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(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(&dp->work);
257 		break;
258 	default:
259 		break;
260 	}
261 
262 	mutex_unlock(&dp->con->lock);
263 
264 	return 0;
265 }
266 
267 static 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 
ucsi_displayport_work(struct work_struct * work)273 static 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 	ret = typec_altmode_vdm(dp->alt, dp->header,
279 				dp->vdo_data, dp->vdo_size);
280 	if (ret)
281 		dev_err(&dp->alt->dev, "VDM 0x%x failed\n", dp->header);
282 
283 	dp->vdo_data = NULL;
284 	dp->vdo_size = 0;
285 	dp->header = 0;
286 }
287 
ucsi_displayport_remove_partner(struct typec_altmode * alt)288 void ucsi_displayport_remove_partner(struct typec_altmode *alt)
289 {
290 	struct ucsi_dp *dp;
291 
292 	if (!alt)
293 		return;
294 
295 	dp = typec_altmode_get_drvdata(alt);
296 	if (!dp)
297 		return;
298 
299 	dp->data.conf = 0;
300 	dp->data.status = 0;
301 	dp->initialized = false;
302 }
303 
ucsi_register_displayport(struct ucsi_connector * con,bool override,int offset,struct typec_altmode_desc * desc)304 struct typec_altmode *ucsi_register_displayport(struct ucsi_connector *con,
305 						bool override, int offset,
306 						struct typec_altmode_desc *desc)
307 {
308 	u8 all_assignments = BIT(DP_PIN_ASSIGN_C) | BIT(DP_PIN_ASSIGN_D) |
309 			     BIT(DP_PIN_ASSIGN_E);
310 	struct typec_altmode *alt;
311 	struct ucsi_dp *dp;
312 
313 	/* We can't rely on the firmware with the capabilities. */
314 	desc->vdo |= DP_CAP_DP_SIGNALING | DP_CAP_RECEPTACLE;
315 
316 	/* Claiming that we support all pin assignments */
317 	desc->vdo |= all_assignments << 8;
318 	desc->vdo |= all_assignments << 16;
319 
320 	alt = typec_port_register_altmode(con->port, desc);
321 	if (IS_ERR(alt))
322 		return alt;
323 
324 	dp = devm_kzalloc(&alt->dev, sizeof(*dp), GFP_KERNEL);
325 	if (!dp) {
326 		typec_unregister_altmode(alt);
327 		return ERR_PTR(-ENOMEM);
328 	}
329 
330 	INIT_WORK(&dp->work, ucsi_displayport_work);
331 	dp->override = override;
332 	dp->offset = offset;
333 	dp->con = con;
334 	dp->alt = alt;
335 
336 	alt->ops = &ucsi_displayport_ops;
337 	typec_altmode_set_drvdata(alt, dp);
338 
339 	return alt;
340 }
341