xref: /linux/drivers/platform/chrome/cros_typec_altmode.c (revision b625e47f04274538e32e99fe6d3dc01edc93d280)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Alt-mode implementation on ChromeOS EC.
4  *
5  * Copyright 2024 Google LLC
6  * Author: Abhishek Pandit-Subedi <abhishekpandit@chromium.org>
7  */
8 #include "cros_ec_typec.h"
9 
10 #include <linux/mutex.h>
11 #include <linux/workqueue.h>
12 #include <linux/usb/typec_dp.h>
13 #include <linux/usb/typec_tbt.h>
14 #include <linux/usb/pd_vdo.h>
15 
16 #include "cros_typec_altmode.h"
17 
18 struct cros_typec_altmode_data {
19 	struct work_struct work;
20 	struct cros_typec_port *port;
21 	struct typec_altmode *alt;
22 	bool ap_mode_entry;
23 
24 	struct mutex lock;
25 	u32 header;
26 	u32 *vdo_data;
27 	u8 vdo_size;
28 
29 	u16 sid;
30 	u8 mode;
31 };
32 
33 struct cros_typec_dp_data {
34 	struct cros_typec_altmode_data adata;
35 	struct typec_displayport_data data;
36 	bool configured;
37 	bool pending_status_update;
38 };
39 
cros_typec_altmode_work(struct work_struct * work)40 static void cros_typec_altmode_work(struct work_struct *work)
41 {
42 	struct cros_typec_altmode_data *data =
43 		container_of(work, struct cros_typec_altmode_data, work);
44 
45 	mutex_lock(&data->lock);
46 
47 	if (typec_altmode_vdm(data->alt, data->header, data->vdo_data,
48 			      data->vdo_size))
49 		dev_err(&data->alt->dev, "VDM 0x%x failed\n", data->header);
50 
51 	data->header = 0;
52 	data->vdo_data = NULL;
53 	data->vdo_size = 0;
54 
55 	mutex_unlock(&data->lock);
56 }
57 
cros_typec_altmode_enter(struct typec_altmode * alt,u32 * vdo)58 static int cros_typec_altmode_enter(struct typec_altmode *alt, u32 *vdo)
59 {
60 	struct cros_typec_altmode_data *adata = typec_altmode_get_drvdata(alt);
61 	struct ec_params_typec_control req = {
62 		.port = adata->port->port_num,
63 		.command = TYPEC_CONTROL_COMMAND_ENTER_MODE,
64 	};
65 	int svdm_version;
66 	int ret;
67 
68 	if (!adata->ap_mode_entry) {
69 		dev_warn(&alt->dev,
70 			 "EC does not support AP driven mode entry\n");
71 		return -EOPNOTSUPP;
72 	}
73 
74 	if (adata->sid == USB_TYPEC_DP_SID)
75 		req.mode_to_enter = CROS_EC_ALTMODE_DP;
76 	else if (adata->sid == USB_TYPEC_TBT_SID)
77 		req.mode_to_enter = CROS_EC_ALTMODE_TBT;
78 	else
79 		return -EOPNOTSUPP;
80 
81 	ret = cros_ec_cmd(adata->port->typec_data->ec, 0, EC_CMD_TYPEC_CONTROL,
82 			  &req, sizeof(req), NULL, 0);
83 	if (ret < 0)
84 		return ret;
85 
86 	svdm_version = typec_altmode_get_svdm_version(alt);
87 	if (svdm_version < 0)
88 		return svdm_version;
89 
90 	mutex_lock(&adata->lock);
91 
92 	adata->header = VDO(adata->sid, 1, svdm_version, CMD_ENTER_MODE);
93 	adata->header |= VDO_OPOS(adata->mode);
94 	adata->header |= VDO_CMDT(CMDT_RSP_ACK);
95 	adata->vdo_data = NULL;
96 	adata->vdo_size = 1;
97 	schedule_work(&adata->work);
98 
99 	mutex_unlock(&adata->lock);
100 	return ret;
101 }
102 
cros_typec_altmode_exit(struct typec_altmode * alt)103 static int cros_typec_altmode_exit(struct typec_altmode *alt)
104 {
105 	struct cros_typec_altmode_data *adata = typec_altmode_get_drvdata(alt);
106 	struct ec_params_typec_control req = {
107 		.port = adata->port->port_num,
108 		.command = TYPEC_CONTROL_COMMAND_EXIT_MODES,
109 	};
110 	int svdm_version;
111 	int ret;
112 
113 	if (!adata->ap_mode_entry) {
114 		dev_warn(&alt->dev,
115 			 "EC does not support AP driven mode exit\n");
116 		return -EOPNOTSUPP;
117 	}
118 
119 	ret = cros_ec_cmd(adata->port->typec_data->ec, 0, EC_CMD_TYPEC_CONTROL,
120 			  &req, sizeof(req), NULL, 0);
121 
122 	if (ret < 0)
123 		return ret;
124 
125 	svdm_version = typec_altmode_get_svdm_version(alt);
126 	if (svdm_version < 0)
127 		return svdm_version;
128 
129 	mutex_lock(&adata->lock);
130 
131 	adata->header = VDO(adata->sid, 1, svdm_version, CMD_EXIT_MODE);
132 	adata->header |= VDO_OPOS(adata->mode);
133 	adata->header |= VDO_CMDT(CMDT_RSP_ACK);
134 	adata->vdo_data = NULL;
135 	adata->vdo_size = 1;
136 	schedule_work(&adata->work);
137 
138 	mutex_unlock(&adata->lock);
139 	return ret;
140 }
141 
cros_typec_displayport_vdm(struct typec_altmode * alt,u32 header,const u32 * data,int count)142 static int cros_typec_displayport_vdm(struct typec_altmode *alt, u32 header,
143 				      const u32 *data, int count)
144 {
145 	struct cros_typec_dp_data *dp_data = typec_altmode_get_drvdata(alt);
146 	struct cros_typec_altmode_data *adata = &dp_data->adata;
147 
148 
149 	int cmd_type = PD_VDO_CMDT(header);
150 	int cmd = PD_VDO_CMD(header);
151 	int svdm_version;
152 
153 	svdm_version = typec_altmode_get_svdm_version(alt);
154 	if (svdm_version < 0)
155 		return svdm_version;
156 
157 	mutex_lock(&adata->lock);
158 
159 	switch (cmd_type) {
160 	case CMDT_INIT:
161 		if (PD_VDO_SVDM_VER(header) < svdm_version) {
162 			typec_partner_set_svdm_version(adata->port->partner,
163 						       PD_VDO_SVDM_VER(header));
164 			svdm_version = PD_VDO_SVDM_VER(header);
165 		}
166 
167 		adata->header = VDO(adata->sid, 1, svdm_version, cmd);
168 		adata->header |= VDO_OPOS(adata->mode);
169 
170 		/*
171 		 * DP_CMD_CONFIGURE: We can't actually do anything with the
172 		 * provided VDO yet so just send back an ACK.
173 		 *
174 		 * DP_CMD_STATUS_UPDATE: We wait for Mux changes to send
175 		 * DPStatus Acks.
176 		 */
177 		switch (cmd) {
178 		case DP_CMD_CONFIGURE:
179 			dp_data->data.conf = *data;
180 			adata->header |= VDO_CMDT(CMDT_RSP_ACK);
181 			dp_data->configured = true;
182 			schedule_work(&adata->work);
183 			break;
184 		case DP_CMD_STATUS_UPDATE:
185 			dp_data->pending_status_update = true;
186 			break;
187 		default:
188 			adata->header |= VDO_CMDT(CMDT_RSP_ACK);
189 			schedule_work(&adata->work);
190 			break;
191 		}
192 
193 		break;
194 	default:
195 		break;
196 	}
197 
198 	mutex_unlock(&adata->lock);
199 	return 0;
200 }
201 
cros_typec_thunderbolt_vdm(struct typec_altmode * alt,u32 header,const u32 * data,int count)202 static int cros_typec_thunderbolt_vdm(struct typec_altmode *alt, u32 header,
203 				      const u32 *data, int count)
204 {
205 	struct cros_typec_altmode_data *adata = typec_altmode_get_drvdata(alt);
206 
207 	int cmd_type = PD_VDO_CMDT(header);
208 	int cmd = PD_VDO_CMD(header);
209 	int svdm_version;
210 
211 	svdm_version = typec_altmode_get_svdm_version(alt);
212 	if (svdm_version < 0)
213 		return svdm_version;
214 
215 	mutex_lock(&adata->lock);
216 
217 	switch (cmd_type) {
218 	case CMDT_INIT:
219 		if (PD_VDO_SVDM_VER(header) < svdm_version) {
220 			typec_partner_set_svdm_version(adata->port->partner,
221 						       PD_VDO_SVDM_VER(header));
222 			svdm_version = PD_VDO_SVDM_VER(header);
223 		}
224 
225 		adata->header = VDO(adata->sid, 1, svdm_version, cmd);
226 		adata->header |= VDO_OPOS(adata->mode);
227 
228 		switch (cmd) {
229 		case CMD_ENTER_MODE:
230 			/* Don't respond to the enter mode vdm because it
231 			 * triggers mux configuration. This is handled directly
232 			 * by the cros_ec_typec driver so the Thunderbolt driver
233 			 * doesn't need to be involved.
234 			 */
235 			break;
236 		default:
237 			adata->header |= VDO_CMDT(CMDT_RSP_ACK);
238 			schedule_work(&adata->work);
239 			break;
240 		}
241 
242 		break;
243 	default:
244 		break;
245 	}
246 
247 	mutex_unlock(&adata->lock);
248 	return 0;
249 }
250 
251 
cros_typec_altmode_vdm(struct typec_altmode * alt,u32 header,const u32 * data,int count)252 static int cros_typec_altmode_vdm(struct typec_altmode *alt, u32 header,
253 				      const u32 *data, int count)
254 {
255 	struct cros_typec_altmode_data *adata = typec_altmode_get_drvdata(alt);
256 
257 	if (!adata->ap_mode_entry)
258 		return -EOPNOTSUPP;
259 
260 	if (adata->sid == USB_TYPEC_DP_SID)
261 		return cros_typec_displayport_vdm(alt, header, data, count);
262 
263 	if (adata->sid == USB_TYPEC_TBT_SID)
264 		return cros_typec_thunderbolt_vdm(alt, header, data, count);
265 
266 	return -EINVAL;
267 }
268 
269 static const struct typec_altmode_ops cros_typec_altmode_ops = {
270 	.enter = cros_typec_altmode_enter,
271 	.exit = cros_typec_altmode_exit,
272 	.vdm = cros_typec_altmode_vdm,
273 };
274 
275 #if IS_ENABLED(CONFIG_TYPEC_DP_ALTMODE)
cros_typec_displayport_status_update(struct typec_altmode * altmode,struct typec_displayport_data * data)276 int cros_typec_displayport_status_update(struct typec_altmode *altmode,
277 					 struct typec_displayport_data *data)
278 {
279 	struct cros_typec_dp_data *dp_data =
280 		typec_altmode_get_drvdata(altmode);
281 	struct cros_typec_altmode_data *adata = &dp_data->adata;
282 
283 	if (!dp_data->pending_status_update) {
284 		dev_dbg(&altmode->dev,
285 			"Got DPStatus without a pending request\n");
286 		return 0;
287 	}
288 
289 	if (dp_data->configured && dp_data->data.conf != data->conf)
290 		dev_dbg(&altmode->dev,
291 			"DP Conf doesn't match. Requested 0x%04x, Actual 0x%04x\n",
292 			dp_data->data.conf, data->conf);
293 
294 	mutex_lock(&adata->lock);
295 
296 	dp_data->data = *data;
297 	dp_data->pending_status_update = false;
298 	adata->header |= VDO_CMDT(CMDT_RSP_ACK);
299 	adata->vdo_data = &dp_data->data.status;
300 	adata->vdo_size = 2;
301 	schedule_work(&adata->work);
302 
303 	mutex_unlock(&adata->lock);
304 
305 	return 0;
306 }
307 
308 struct typec_altmode *
cros_typec_register_displayport(struct cros_typec_port * port,struct typec_altmode_desc * desc,bool ap_mode_entry)309 cros_typec_register_displayport(struct cros_typec_port *port,
310 				struct typec_altmode_desc *desc,
311 				bool ap_mode_entry)
312 {
313 	struct typec_altmode *alt;
314 	struct cros_typec_dp_data *dp_data;
315 	struct cros_typec_altmode_data *adata;
316 
317 	alt = typec_port_register_altmode(port->port, desc);
318 	if (IS_ERR(alt))
319 		return alt;
320 
321 	dp_data = devm_kzalloc(&alt->dev, sizeof(*dp_data), GFP_KERNEL);
322 	if (!dp_data) {
323 		typec_unregister_altmode(alt);
324 		return ERR_PTR(-ENOMEM);
325 	}
326 
327 	adata = &dp_data->adata;
328 	INIT_WORK(&adata->work, cros_typec_altmode_work);
329 	mutex_init(&adata->lock);
330 	adata->alt = alt;
331 	adata->port = port;
332 	adata->ap_mode_entry = ap_mode_entry;
333 	adata->sid = desc->svid;
334 	adata->mode = desc->mode;
335 
336 	typec_altmode_set_ops(alt, &cros_typec_altmode_ops);
337 	typec_altmode_set_drvdata(alt, adata);
338 
339 	return alt;
340 }
341 #endif
342 
343 #if IS_ENABLED(CONFIG_TYPEC_TBT_ALTMODE)
344 struct typec_altmode *
cros_typec_register_thunderbolt(struct cros_typec_port * port,struct typec_altmode_desc * desc)345 cros_typec_register_thunderbolt(struct cros_typec_port *port,
346 				struct typec_altmode_desc *desc)
347 {
348 	struct typec_altmode *alt;
349 	struct cros_typec_altmode_data *adata;
350 
351 	alt = typec_port_register_altmode(port->port, desc);
352 	if (IS_ERR(alt))
353 		return alt;
354 
355 	adata = devm_kzalloc(&alt->dev, sizeof(*adata), GFP_KERNEL);
356 	if (!adata) {
357 		typec_unregister_altmode(alt);
358 		return ERR_PTR(-ENOMEM);
359 	}
360 
361 	INIT_WORK(&adata->work, cros_typec_altmode_work);
362 	mutex_init(&adata->lock);
363 	adata->alt = alt;
364 	adata->port = port;
365 	adata->ap_mode_entry = true;
366 	adata->sid = desc->svid;
367 	adata->mode = desc->mode;
368 
369 	typec_altmode_set_ops(alt, &cros_typec_altmode_ops);
370 	typec_altmode_set_drvdata(alt, adata);
371 
372 	return alt;
373 }
374 #endif
375