File Index
Files and directories
Sign in
FILE

displayport.c

File preview with syntax highlighting.

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