File Index
Files and directories
Sign in
FILE

cros_typec_altmode.c

File preview with syntax highlighting.

linux-6.19.14/drivers/platform/chrome/cros_typec_altmode.c C
  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
 18struct 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
 33struct 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
 40static 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
 58static 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
103static 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
142static 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
202static 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
252static 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
269static 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)
276int 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
308struct typec_altmode *
309cros_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)
344struct typec_altmode *
345cros_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	adata->alt = alt;
363	adata->port = port;
364	adata->ap_mode_entry = true;
365	adata->sid = desc->svid;
366	adata->mode = desc->mode;
367
368	typec_altmode_set_ops(alt, &cros_typec_altmode_ops);
369	typec_altmode_set_drvdata(alt, adata);
370
371	return alt;
372}
373#endif
~/ Read-only file preview.