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